倒霉的菜鸟

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

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 毫秒,动画的完成度更新时),这个方法被调用。

 

方法的参数是一个 ValueAnimatorValueAnimator 是 ObjectAnimator 的父类,也是 ViewPropertyAnimator 的内部实现,所以这个参数其实就是 ViewPropertyAnimator 内部的那个 ValueAnimator,或者对于 ObjectAnimator 来说就是它自己本身。

 

ValueAnimator 有很多方法可以用,它可以查看当前的动画完成度、当前的属性值等等。不过 ValueAnimator 是下一期才讲的内容,所以这期就不多说了。

 

ViewPropertyAnimator.withStartAction/EndAction()

 

这两个方法是 ViewPropertyAnimator 的独有方法。它们和 set/addListener() 中回调的 onAnimationStart() / onAnimationEnd() 相比起来的不同主要有两点:

 

  1. withStartAction() / withEndAction() 是一次性的,在动画执行结束后就自动弃掉了,就算之后再重用 ViewPropertyAnimator 来做别的动画,用它们设置的回调也不会再被调用。而 set/addListener() 所设置的 AnimatorListener 是持续有效的,当动画重复执行时,回调总会被调用。
  2. 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()
posted on 2021-10-16 23:41  倒霉的菜鸟  阅读(225)  评论(0编辑  收藏  举报