1, 使用ViewPropertyAnimator可以实现一些简单的动画效果
从图中可以看到, View
的每个方法都对应了 ViewPropertyAnimator
的两个方法,其中一个是带有 -By
后缀的,例如,View.setTranslationX()
对应了 ViewPropertyAnimator.translationX()
和 ViewPropertyAnimator.translationXBy()
这两个方法。其中带有 -By()
后缀的是增量版本的方法,例如,translationX(100)
表示用动画把 View
的 translationX
值渐变为 100
,而 translationXBy(100)
则表示用动画把 View
的 translationX
值渐变地增加 100
。
如下面方法会让climView在1秒的时间里完成移位,旋转,透明度的变化。
1 private fun testAnimator(){ 2 val climeView = binding.climeView 3 climeView.animate() 4 .translationY(300f.toPx) 5 .translationX(200f.toPx) 6 .alpha(0.5f) 7 .rotationBy(-40f) 8 .setDuration(1000) 9 .setStartDelay(1000) 10 }
ViewPropertyAnimator
简单易用,但是局限于那么几个既定的方法(属性)
2, ObjectAnimator 既可以实现上面那些简单动画, 也可以用来实现一些自定义动画效果
比如
1 val objectAnimator = ObjectAnimator.ofFloat(climeView, "translationX", 100f.toPx) 2 objectAnimator.startDelay = 1000 3 objectAnimator.duration = 1000 4 objectAnimator.start()
这段代码就等价于 :
1 climeView.animate() 2 .translationX(100f.toPx) 3 .setDuration(1000) 4 .setStartDelay(1000)
区别是ObjectAnimator需要我们显式地调用objectAnimator.start()来启动动画
ObjectAnimator.ofFloat(climeView, "translationX", 100f.toPx)该方法中第一个参数为你要操作的对象, 第二个方法为你要操作的对象的属性,第三个参数是你要让对象移动到什么位置
和ObjectAnimator.ofFloat()类似, ObjectAnimator还有ofInt(), ofArgb(), ofObject()等方法, 具体用哪一种取决于你的属性类型
上面我们说过, ObjectAnimator还可以用来实现一些自定义的动画效果, 具体怎么实现呢?
比如我们要实现一个圆从小到大的动画,
首先, 我们要在view里给我们的圆的半径radius加上set方法, 并在值发生改变时调用invalidate()
1 private var radius = 10f.toPx 2 set(value) { 3 field = value 4 invalidate() 5 }
Android的界面是频繁刷新的, 但是出于性能考虑, 并不会在每次界面刷新的时候进行重绘, 所以我们需要调用invalidate()把界面标记为失效,这样下次重绘的时候才会重新调用onDraw
1 val radiusAnimator = ObjectAnimator.ofFloat(climeView, "radius",200f.toPx) 2 radiusAnimator.start()
但是不同于ViewPropertyAnimator,
ObjectAnimator每次只能操作一个属性, 如果我们要操作多个属性, 就要去写多个ObjectAnimator
或者使用AnimatorSet:
比如
1 val topClimAnimator = ObjectAnimator.ofFloat(climeView, "topClimAngle", 60f) 2 val bottomAnimator = ObjectAnimator.ofFloat(climeView, "bottomClimAngle", -60f) 3 val rotateAnimator = ObjectAnimator.ofFloat(climeView, "rotateAngle", 50f) 4 val animatorSet = AnimatorSet() 5 animatorSet.playTogether(topClimAnimator,rotateAnimator, bottomAnimator) 6 animatorSet.setDuration(2000) 7 animatorSet.startDelay = 1000 8 animatorSet.start()
这里的playTogether()是让三个动画属性同时变化
我们也可以通过调用playSequentially()让我们传入的动画属性按顺序发生变化
如 animatorSet.playSequentially(topClimAnimator,rotateAnimator, bottomAnimator) 动画的执行顺序就是先翻顶部, 再旋转, 再翻底部
或者我们还可以这样
// 使用 AnimatorSet.play(animatorA).with/before/after(animatorB) // 的方式来精确配置各个 Animator 之间的关系 animatorSet.play(animator1).with(animator2); animatorSet.play(animator1).before(animator2); animatorSet.play(animator1).after(animator2); animatorSet.start();
另一种实现多属性动画的方式是 使用PropertyValuesHolder
1 val topClimeValuesHolder = PropertyValuesHolder.ofFloat("topClimAngle", 60f) 2 val bottomValuesHolder = PropertyValuesHolder.ofFloat("bottomClimAngle", -60f) 3 val rotateValuesHolder = PropertyValuesHolder.ofFloat("rotateAngle", 50f) 4 5 val climeAnimator = ObjectAnimator.ofPropertyValuesHolder(climeView, topClimeValuesHolder, bottomValuesHolder, 6 rotateValuesHolder) 7 climeAnimator.setDuration(2000) 8 climeAnimator.startDelay = 1000 9 climeAnimator.start()
PropertyValuesHolder描述的属性,所以它的方法里不需要传入view
AnimatorSet的作用是让多个动画合成,PropertyValuesHolder则是把多个属性合成一个动画
PropertyValuesHolder还有一个很重要的方法是ofKeyFrame(), KeyFrame的意思是关键帧, 就是同一个view,同一个属性的动画中,我们可以精确控制某个时间点动画进行到什么位置,
看个例子
1 //前20%的时间,走了40%的路程,中间60%的时间走了10%的路程,最后20&的时间走了50%的路程 2 val kFrame1 = Keyframe.ofFloat(0.2f, 0.4f*200f.toPx) 3 val kFrame2 = Keyframe.ofFloat(0.8f, 0.5f*200f.toPx) 4 val kFrame3 = Keyframe.ofFloat(1f, 1f*200f.toPx) 5 val keyFrameHolder = PropertyValuesHolder.ofKeyframe("translationX", 6 kFrame1, kFrame2, kFrame3) 7 val objectAnimator = ObjectAnimator.ofPropertyValuesHolder(climeView, keyFrameHolder) 8 objectAnimator.startDelay = 1000 9 objectAnimator.duration = 1000 10 objectAnimator.start()
这样就实现了动画先快再慢再快的效果
当然, 我们也可以通过animator.setInterpolator()方法来设置动画的速度插值器
常见的速度插值器有4种
1) AccelerateDecelerateInterpolator 加速--减速模型, 即从0到最大速度,再从最大速度到0
这种插值器适用于界面内部的控件动画
2)AccelerateInterpolator 持续加速。在整个动画过程中,一直在加速,直到动画结束的一瞬间,直接停止
适用于将控件移出屏幕
3)DecelerateInterpolator 持续减速直到 0。动画开始的时候是最高速度,然后在动画过程中逐渐减速,直到动画结束的时候恰好减速到 0。
适用于从外面将控件移进屏幕
4)LinearInterpolator 匀速。
TypeEvaluator 估值器
在上文中, 我们看到使用ObjectAnimator可以完成很多自定义属性动画, 但上面的例子中我们调用的都是ofFloat()方法, 除了ofFloat之外,api还提供了ofInt(), ofArgb()
在这些方法内部其实都用到了api定义好的TypeEvaluator, 它的作用是,算出在动画运行中的每个时间节点上, 动画的完成度。
如果我们的属性只是简单的移动多少距离,旋转多少度(int/ float类型), 那我们完全都没有必要知道TypeEvaluator
但是, 当这些类型满足不了需求的时候,我们就需要自定义TypeEvaluator来完成计算
看个例子
我们有一个PonitView, 会在屏幕上画出一个点
1 class PointView(context: Context, attributeSet: AttributeSet): View(context, attributeSet) { 2 3 private val paint = Paint(Paint.ANTI_ALIAS_FLAG) 4 private var point = PointF(10f.toPx, 10f.toPx) 5 set(value) { 6 field = value 7 invalidate() 8 } 9 10 override fun onDraw(canvas: Canvas) { 11 super.onDraw(canvas) 12 paint.style = Paint.Style.FILL 13 paint.strokeWidth = 10f.toPx 14 canvas.drawPoint(point.x, point.y, paint) 15 } 16 }
现在我们想对这个点做个动画,
因为PointF不是float或者int, 系统自带的TypeEvalutor无法对点进行加减乘除运算, 所以我们需要自定义TypeEvalutor来完成计算
1 class PointTypeEvulator: TypeEvaluator<PointF>{ 2 //这个方法传入了起始值,终点值和百分比 3 override fun evaluate(fraction: Float, startValue: PointF, endValue: PointF): PointF { 4 val startX = startValue.x 5 val endX = endValue.x 6 val currentX = startX + (endX - startX)*fraction 7 val startY = startValue.y 8 val endY = endValue.y 9 val currentY = startY + (endY - startY)*fraction 10 return PointF(currentX, currentY) 11 } 12 13 }
然后调用
1 val objectAnimator = ObjectAnimator.ofObject(climeView, "point", PointTypeEvulator(), PointF(300f.toPx, 400f.toPx)) 2 objectAnimator.startDelay = 1000 3 objectAnimator.duration = 1000 4 objectAnimator.interpolator = AccelerateInterpolator() 5 objectAnimator.start()
Listener 设置监听
给动画设置监听器,可以在关键时刻得到反馈,从而及时做出合适的操作,例如在动画的属性更新时同步更新其他数据,或者在动画结束后回收资源等。
设置监听器的方法, ViewPropertyAnimator
和 ObjectAnimator
略微不一样: ViewPropertyAnimator
用的是 setListener()
和 setUpdateListener()
方法,可以设置一个监听器,要移除监听器时通过 set[Update]Listener(null)
填 null 值来移除;而 ObjectAnimator
则是用 addListener()
和 addUpdateListener()
来添加一个或多个监听器,移除监听器则是通过 remove[Update]Listener()
来指定移除对象。
另外,由于 ObjectAnimator
支持使用 pause()
方法暂停,所以它还多了一个 addPauseListener()
/ removePauseListener()
的支持;而 ViewPropertyAnimator
则独有 withStartAction()
和 withEndAction()
方法,可以设置一次性的动画开始或结束的监听。
ViewPropertyAnimator.setListener() / ObjectAnimator.addListener()
这两个方法的名称不一样,可以设置的监听器数量也不一样,但它们的参数类型都是 AnimatorListener
,所以本质上其实都是一样的。 AnimatorListener
共有 4 个回调方法:
onAnimationStart(Animator animation)
当动画开始执行时,这个方法被调用。
onAnimationEnd(Animator animation)
当动画结束时,这个方法被调用。
onAnimationCancel(Animator animation)
当动画被通过 cancel()
方法取消时,这个方法被调用。
需要说明一下的是,就算动画被取消,onAnimationEnd()
也会被调用。所以当动画被取消时,如果设置了 AnimatorListener
,那么 onAnimationCancel()
和 onAnimationEnd()
都会被调用。onAnimationCancel()
会先于 onAnimationEnd()
被调用。
onAnimationRepeat(Animator animation)
当动画通过 setRepeatMode()
/ setRepeatCount()
或 repeat()
方法重复执行时,这个方法被调用。
由于 ViewPropertyAnimator
不支持重复,所以这个方法对 ViewPropertyAnimator
相当于无效。
ViewPropertyAnimator.setUpdateListener() / ObjectAnimator.addUpdateListener()
这两个方法虽然名称和可设置的监听器数量不一样,但本质其实都一样的,它们的参数都是 AnimatorUpdateListener
。它只有一个回调方法:onAnimationUpdate(ValueAnimator animation)
。
onAnimationUpdate(ValueAnimator animation)
当动画的属性更新时(不严谨的说,即每过 10 毫秒,动画的完成度更新时),这个方法被调用。
方法的参数是一个 ValueAnimator
,ValueAnimator
是 ObjectAnimator
的父类,也是 ViewPropertyAnimator
的内部实现,所以这个参数其实就是 ViewPropertyAnimator
内部的那个 ValueAnimator
,或者对于 ObjectAnimator
来说就是它自己本身。
ValueAnimator
有很多方法可以用,它可以查看当前的动画完成度、当前的属性值等等。不过 ValueAnimator
是下一期才讲的内容,所以这期就不多说了。
ViewPropertyAnimator.withStartAction/EndAction()
这两个方法是 ViewPropertyAnimator
的独有方法。它们和 set/addListener()
中回调的 onAnimationStart()
/ onAnimationEnd()
相比起来的不同主要有两点:
withStartAction()
/withEndAction()
是一次性的,在动画执行结束后就自动弃掉了,就算之后再重用ViewPropertyAnimator
来做别的动画,用它们设置的回调也不会再被调用。而set/addListener()
所设置的AnimatorListener
是持续有效的,当动画重复执行时,回调总会被调用。withEndAction()
设置的回调只有在动画正常结束时才会被调用,而在动画被取消时不会被执行。这点和AnimatorListener.onAnimationEnd()
的行为是不一致的。
最后在说一下硬件绘制和软件绘制
所谓软件绘制就是用CPU去绘制代码,整个页面的绘制都是基于一个Bitmap, 在系统层面(软件层面)就已经把所有像素准备好,
硬件绘制就是GPU完成绘制, CPU在绘制阶段,比如canvas.drawCircle的时候,只是把这些方法转换成一系列GPU的操作, 而没有进行真正的绘制,在屏幕显示的时候,GPU会把这些操作转成对应的像素,完成绘制
硬件绘制为什么更快呢?
1, GPU更专业, 它就是为绘制而生的, 天生具有绘制的优势
2, 绘制流程得到优化, 重绘的时候会快的多, 软件绘制融合像素形成BITMAP, 但GPU没有去做融合, 当页面改变的时候, 它只会重绘改变了的部分
但是硬件绘制有兼容性的问题, 对于一些特定的api,目前无法使用硬件绘制, 这时候就需要我们关闭硬件绘制
怎么控制硬件绘制呢?
应用级别
<application android:hardwareAccelerated="true" ...>
Activity级别
<application android:hardwareAccelerated="true">
<activity ... />
<activity android:hardwareAccelerated="false" />
</application>
窗口级别
window.setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
)
视图级别:
1 init { 2 //设置离屏缓冲,并使用硬件绘制 3 setLayerType(LAYER_TYPE_HARDWARE, paint) 4 //设置离屏缓冲, 使用软件绘制 5 setLayerType(LAYER_TYPE_SOFTWARE, paint) 6 //关闭离屏缓冲 7 setLayerType(LAYER_TYPE_NONE, paint) 8 }
对于ViewPropertyAnimator中的这些自带属性, 可以通过.withLayer()来开启离屏缓冲, 会在动画结束后自动关闭离屏缓冲
climeView.animate()
.translationY(300f.toPx)
.withLayer()