内容简介:动画是交互的关键元素,好的动画效果可以吸引更多的用户,因此掌握动画是一种很重要也很基础的技能。在 Android 3.0 之后,官方主推的就是属性动画,因此本文着重对属性动画进行说明,主要包括基本用法、原理以及应用。属性动画是对属性进行动画的,那么什么是属性呢?可以理解成 Java Bean 的属性,一个有属性动画有两个要素:
动画是交互的关键元素,好的动画效果可以吸引更多的用户,因此掌握动画是一种很重要也很基础的技能。在 Android 3.0 之后,官方主推的就是属性动画,因此本文着重对属性动画进行说明,主要包括基本用法、原理以及应用。
属性动画的原理
原理
属性动画是对属性进行动画的,那么什么是属性呢?可以理解成 Java Bean 的属性,一个有 getXxx/setXxx
方法的字段,因为内部原理是通过反射去改变这些属性的值的,从而达到动画的效果。 如下图所示:
属性动画有两个要素:
-
时间:时间确定了动画时长
-
属性:动画改变的参数,需要设置起始状态和终止状态
已知动画时长以及起始和终止状态,对于中间怎么变换,属性动画引入了 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 对属性动画封装的一些 API。这边主要介绍两种:StateListAnimator 和模拟物理世界的 FlingAnimation 和 SpringAnimaion。
StateListAnimator
StateListAnimator 和 Selector 类似,设置 View 每种状态对应的动画,比如 View 被按下后是一种动画,手指松开后又是一种状态。
这边再以一个例子介绍,仿抖音按住拍的效果,可以先看下效果:
可以看到,当按下去后,有个呼吸的效果;手指释放后,恢复到初始状态。这种效果可以通过 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,效果图如下:
通过上面动图可以看到,每一个 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,和属性动画的区别是不需要设置时间和结束状态了,因为可以通过物理规律计算得到终态以及中间每一时刻的状态。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 带你了解vue计算属性的实现原理以及vuex的实现原理
- [Vue.js进阶]从源码角度剖析计算属性的原理
- 科普 | 分布式共识的工作原理,Part-1:分布式系统的定义及属性
- iOS 面试题·Block 的原理,Block 的属性修饰词为什么用 copy,使用 Block 时有哪些要注意的?
- CSS 属性篇(七):Display属性
- JavaScript对象之数据属性与访问器属性
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。