Android 属性动画的原理及应用

栏目: IOS · Android · 发布时间: 4年前

内容简介:动画是交互的关键元素,好的动画效果可以吸引更多的用户,因此掌握动画是一种很重要也很基础的技能。在 Android 3.0 之后,官方主推的就是属性动画,因此本文着重对属性动画进行说明,主要包括基本用法、原理以及应用。属性动画是对属性进行动画的,那么什么是属性呢?可以理解成 Java Bean 的属性,一个有属性动画有两个要素:

动画是交互的关键元素,好的动画效果可以吸引更多的用户,因此掌握动画是一种很重要也很基础的技能。在 Android 3.0 之后,官方主推的就是属性动画,因此本文着重对属性动画进行说明,主要包括基本用法、原理以及应用。

属性动画的原理

原理

属性动画是对属性进行动画的,那么什么是属性呢?可以理解成 Java Bean 的属性,一个有 getXxx/setXxx 方法的字段,因为内部原理是通过反射去改变这些属性的值的,从而达到动画的效果。 如下图所示:

Android 属性动画的原理及应用

属性动画有两个要素:

  1. 时间:时间确定了动画时长

  2. 属性:动画改变的参数,需要设置起始状态和终止状态

已知动画时长以及起始和终止状态,对于中间怎么变换,属性动画引入了 TimeInterceptor 的概念。

举个例子,假设总时长为 M,每一帧刷新时间间隔为 m,那么一共有 M / m 帧,那么每一帧的时刻是固定的,分别是 i * m (i 表示第几帧),TimeInterceptor 的工作就是将时间比例转换为属性比例。 Android 官方提供了不少 Interceptor,具体可以参考 inloop.github.io/interpolato… ,里面可以看到 时间比例 —> 属性比例 的转换。

当知道了每一时刻 i * m 的属性后,通过反射去改变属性值,从而达到了动画的目的。

那么问题来了,每一时刻已知的数据有属性比例、起始属性和结束属性,如何计算每一时刻的属性呢?属性动画引入了 TypeEvaluator,该类型根据已知数据计算得到该中间时刻的属性值。因为属性动画是支持对属性的动画,因此肯定会有很多自定义的属性, Android 巧妙地设计了 TypeEvaluator,将属性的具体计算交给用户自己。官方提供了一些基本类型的 Evaluator,比如 IntEvalutar、FloatEvaluator 等。

基本用法

属性动画的 API 主要包括:

创建一个属性动画,核心是时长、起始和结束参数。为了能够计算属性以及不同的变化,需要提供 TypeEvaluator 和 TimeInterceptor。如果需要监听动画过程,可以设置监听器。属性动画的使用无外乎这些东西。

下面以一个例子进行介绍。

定义了一个 Point 和 PointView 类,其中 PointView 有一个属性 Point,根据 Point 的位置去绘制一个红点。因为 Point 是一个自定义属性,因此需要提供一个 TypeEvaluator 进行中间帧属性的计算。另外还提供了一个类似弹簧效果的 Interceptor。定义如下:

data class Point(var x: Float, var y: Float)

class PointView
@JvmOverloads
constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defaultResId: Int = 0)
    : View(context, attrs, defStyleAttr, defaultResId) {
    var point: Point = Point(40f, 40f)
        set(value) {
            field = value
            invalidate()
        }

    val paint: Paint = Paint().apply {
        isAntiAlias = true
        isDither = true
        color = Color.RED
    }
    
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        canvas?.drawCircle(point.x, point.y, 30f, paint)
    }
}

class PointEvaluator : TypeEvaluator<Point> {
    override fun evaluate(fraction: Float, startValue: Point, endValue: Point): Point {
        val x = startValue.x + (endValue.x - startValue.x) * fraction
        val y = startValue.y + (endValue.y - startValue.y) * fraction
        return Point(x, y)
    }
}

class SpringInterceptor(private val factor: Float) : TimeInterpolator {
    override fun getInterpolation(input: Float): Float {
        return (Math.pow(2.0, -10.0 * input) * Math.sin((input - factor / 4) * (2 * Math.PI) / factor) + 1).toFloat()
    }
}
复制代码

准备工作做完后,再来创建动画,如下:

ObjectAnimator.ofObject(pointView, "point", PointEvaluator(), Point(500f, 600f)).apply {
        duration = 2000
        interpolator = SpringInterceptor(0.6f)
        start()
}
复制代码

动画效果如下图所示:

Android 属性动画的原理及应用

关于属性动画更详细的介绍,可以参考我写的其他几篇文章:

介绍完了属性动画的原理和基本使用方法后,下面将介绍几个 Android 官方中属性动画的应用。

属性动画的应用

所谓官方应用,指的是 Android 对属性动画封装的一些 API。这边主要介绍两种:StateListAnimator 和模拟物理世界的 FlingAnimation 和 SpringAnimaion。

StateListAnimator

StateListAnimator 和 Selector 类似,设置 View 每种状态对应的动画,比如 View 被按下后是一种动画,手指松开后又是一种状态。

这边再以一个例子介绍,仿抖音按住拍的效果,可以先看下效果:

Android 属性动画的原理及应用

可以看到,当按下去后,有个呼吸的效果;手指释放后,恢复到初始状态。这种效果可以通过 StateListAnimator 实现。

由于呼吸效果有个环,因此增加了一个 innerRaduisFactor 的参数,初始情况下为 0,按下的时候为 0.75-0.9。

这里看下 StateListAnimator 的定义,具体代码可以参考 GitHub:wangli135/ClimbDemo/jetpackdemo

val breathAnimator: StateListAnimator by lazy {
        //按下状态的外环动画,外环整体尺寸从1x变成1.5x
        val pressedOuterAnim = AnimatorSet().apply {
            play(ObjectAnimator.ofFloat(this@BreathView, SCALE_X, 1.0f, 1.5f))
                    .with(ObjectAnimator.ofFloat(this@BreathView, SCALE_Y, 1.0f, 1.5f))
        }
        //按下状态的内环动画,内环参数由0.75变成0.9,并且是个往返循环的
        val pressedInnerAnim = ObjectAnimator.ofFloat(this, "innerRaduisFactor", MAX_INNER_RADIUS_FACTOR, MIN_INNER_RADIUS_FACTOR).apply {
            repeatMode = ValueAnimator.REVERSE
            repeatCount = ValueAnimator.INFINITE
            duration = 1000
        }
        //按下状态的动画,两个组合起来的
        val pressedAnim = AnimatorSet().apply {
            play(pressedOuterAnim).before(pressedInnerAnim)
        }
        //释放状态的外环动画,恢复到1x尺寸
        val normalOuterAnim = AnimatorSet().apply {
            play(ObjectAnimator.ofFloat(this@BreathView, SCALE_X, 1.0f))
                    .with(ObjectAnimator.ofFloat(this@BreathView, SCALE_Y, 1.0f))
        }
        //释放状态的内环动画,内环参数恢复到0
        val normalInnerAnim = ObjectAnimator.ofFloat(this, "innerRaduisFactor", 0f)
        //释放状态的动画,两个组合起来的
        val normalAnim = AnimatorSet().apply {
            play(normalOuterAnim).before(normalInnerAnim)
        }
        //设置按下状态、释放状态的动画
        StateListAnimator().apply {
            addState(intArrayOf(android.R.attr.state_pressed), pressedAnim)
            addState(intArrayOf(-android.R.attr.state_pressed), normalAnim)
        }
    }
复制代码

创建好 StateListAnimator 对象后,通过 addState() 方法添加每种状态下的动画。对于相对的状态,用 - 号表示,比如上面的 -android.R.attr.state_pressed

可以看到 StateListAnimator 和 Selector Drawable 是十分类似的,只不过一个是根据状态显示动画,另一个是根据状态显示图片。

关于更详细的介绍,可以参考我写的其他两篇文章:

模拟物理世界的动画 — DynamicAnimation

属性动画设置了时长、起始和结束的属性值,一旦在中间状态终止动画,那么属性动画的结束将会显得很急促,有种戛然而止的感觉。如果使用DynamicAnimation ,则不会有这种感觉。

DynamicAnimation 的使用需要引入 support-dynamic-animation 库,主要包括两种动画:

  • FlingAnimation:基于摩擦力的动画,给定初始速度以及摩擦力,那么根据物理学,可以运动多久、每一时刻的速度都是可以根据牛顿力学定律计算得到的

  • SpringAnimation:基于弹力的动画,给定初始速度以及阻尼,可以弹性定律,也是可以计算出来后面每一时刻的状态的

SpringAnimation 有一种 Chained Spring 效果,一种联动的效果,下面看个 demo,效果图如下:

Android 属性动画的原理及应用

通过上面动图可以看到,每一个 button 都是弹性运动的。同时,发布文字按钮跟随发布视频运动运动,发布视频按钮跟随发布图片按钮运动。上面的效果是 SpringAnimation+UpdateListener 实现的,具体代码如下:

val springForce = SpringForce(-600f).apply {
            dampingRatio = SpringForce.DAMPING_RATIO_HIGH_BOUNCY
            stiffness = SpringForce.STIFFNESS_LOW
        }
        //发布文字按钮的动画
        val tvTextSpringAnimation = SpringAnimation(tvPublishText,
                DynamicAnimation.TRANSLATION_Y)
        //发布视频按钮的动画
        val tvVideoSpringAnimation = SpringAnimation(tvPublishVideo,
                DynamicAnimation.TRANSLATION_Y).apply {
            addUpdateListener { animation, value, velocity ->
                //发布文字按钮移动到某一位置
                tvTextSpringAnimation.animateToFinalPosition(value)
            }
        }
        //发布图片按钮的动画
        val tvPicSpringAnimation = SpringAnimation(tvPublishPic,
                DynamicAnimation.TRANSLATION_Y).apply {
            spring = springForce
            addUpdateListener { dynamicAnimation, value, velocity ->
                //发布视频按钮移动到某一位置
                tvVideoSpringAnimation.animateToFinalPosition(value)
            }
        }
        fab.setOnClickListener {
            tvPicSpringAnimation.start()
        }
复制代码

关于 FlingAnimation 和 SpringAnimation 更详细的使用,可以参考我写的其他两篇文章:

总结

属性动画的核心是总时长+起始和结束状态,每一帧时间间隔是相同的,为了有不同的效果,有了 TimeInterceptor,根据时间比例得到状态比例(即状态应该变化多少);而为了应对不同属性的变化,引入了 TypeEvaluator,可以自定义属性的计算,比如上文 demo 中的 PointEvaluator。这样整个属性动画就运转起来了。

官方对属性动画进行了一些封装,比如 StateListAnimator, 它是一种类似 SelectorDrawable 的动画,可以设置 View 不同状态下的动画,可以实现比如抖音按住拍的呼吸效果;官方在 support-dynamic-animation 库中又提出了两种模拟物理事件的动画,模拟摩擦力的 FlingAnimation 和模拟弹力的 SpringAnimation,和属性动画的区别是不需要设置时间和结束状态了,因为可以通过物理规律计算得到终态以及中间每一时刻的状态。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Designing for Emotion

Designing for Emotion

Aarron Walter / Happy Cog / 2011-10-18 / USD 18.00

Make your users fall in love with your site via the precepts packed into this brief, charming book by MailChimp user experience design lead Aarron Walter. From classic psychology to case studies, high......一起来看看 《Designing for Emotion》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

MD5 加密
MD5 加密

MD5 加密工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试