Android 属性动画 原理 和 使用(Kotlin)

上面一篇文章写了 View动画 和 帧动画。。。。。

那这篇文章,就来一波属性动画。。。。。

我们应该都知道,View动画改变的只是View绘制的位置,并没有改变View的属性,比如四个顶点的坐标。就是你把一个View从他原始位置A,移动到新的位置B并停留在在B,你点击B位置,不是不会有点击事件的响应的。比如要点位置A才有。。。。

所以如果一个动画是有位置移动,但又必须有点击事件的处理的话,View动画就满足不了需求了。。。

但是 属性动画  可以弥补View动画这个缺陷!!!!!!

属性动画是Android3.0 引入的,可以说是 对 View 动画的 一种扩张,是View动画的加强版,,,或者说几乎可以完全替代 View动画,而且属性动画的可以使任何对象,不单止是View。。。。

因为 3.0以后才能用,如果想在3.0前面的版本用,可以使用开源库:http://nineoldandroids.com/

属性动画,其实就是两个类,ValueAnimator 和 ObjectAnimator 

ValueAnimator 

设置开始值和结束值,还有一个总时间。 获取这个时间内,每一个时间节点的过度值,通过值去控制动画目标,其实这种场景用的不多。。。。。。。

ObjectAnimator :

这个用的比较多,也基本上是用这个去控制动画。可以直接以任务对象为动画对象。。。。

============【ValueAnimator 】============

先说 ValueAnimator

使用:ValueAnimator.ofFloat()

    /**
     *      ValueAnimator
     * */
    private fun valueAnimator() {
        // 从 0 过渡到 1
        val valueAnimator = ValueAnimator.ofFloat(0F, 1F)
        // 时间是 100毫秒
        valueAnimator.duration = 100
        // 监听进度
        valueAnimator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {
            override fun onAnimationUpdate(animation: ValueAnimator?) {
                //获取动画进度值,通过这个值,去控制动画目标
                //比如 一个View背景颜色的百分比
                val animatedValue = animation?.animatedValue as Float
                Log.d(TAG, "进度:$animatedValue")
            }
        })
        valueAnimator.start()
    }

看下日志:

从结果看到,多次回调 这个过渡进度的。。。。。

我们再看下  ValueAnimator.ofFloat()的源码

可以传进去多个参数的,所以:

如果  ValueAnimator.ofFloat(0F, 1F)  代表从 0过渡到1,

如果  ValueAnimator.ofFloat(0F, 50F,3F,100F)  代表从 0过渡到50,再从50过度到3,然后再从3过渡到100

上代码:

    /**
     *      ValueAnimator
     * */
    private fun valueAnimator() {
        // 从 0 过渡到 1
        val valueAnimator = ValueAnimator.ofFloat(0F, 50F,3F,100F)
        // 时间是 100毫秒
        valueAnimator.duration = 1000
        // 监听进度
        valueAnimator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {
            override fun onAnimationUpdate(animation: ValueAnimator?) {
                //获取动画进度值,通过这个值,去控制动画目标
                //比如 一个View背景颜色的百分比
                val animatedValue = animation?.animatedValue as Float
                Log.d(TAG, "进度:$animatedValue")
            }
        })
        valueAnimator.start()
    }

看结果:

进度:0.0
进度:0.0
进度:0.09472668
进度:0.40268898
进度:0.9233728
进度:1.655303
进度:2.535273
进度:3.670764
进度:5.009651
进度:6.4521832
进度:8.17451
进度:10.087408
进度:12.056963
进度:14.323733
进度:16.763512
进度:19.211622
进度:21.966991
进度:24.87359
进度:27.73996
进度:30.916098
进度:34.217968
进度:37.43199
进度:40.950714
进度:44.566513
进度:48.049103
进度:48.285706          // 接近50时,开始从 50 往 3F去过度
进度:44.67541
进度:41.22999
进度:37.528618
进度:33.79583
进度:30.263401
进度:26.500002
进度:22.736603
进度:19.204176
进度:15.471386
进度:11.770042
进度:8.324593
进度:4.7142982           // 接近3时,开始从 3 往 100F去过度
进度:6.3578963
进度:13.540961
进度:20.555607
进度:27.381924
进度:33.617188
进度:40.022766
进度:46.184475
进度:51.745235
进度:57.384045
进度:62.423447
进度:67.47879
进度:72.21197
进度:76.36032
进度:80.43042
进度:84.14144
进度:87.29662
进度:90.28128
进度:92.878716
进度:94.96303
进度:96.78872
进度:98.20865
进度:99.17078
进度:99.79256
进度:100.0

除了 ValueAnimator.ofFloat外,用的比较多的还有:ValueAnimator.ofInt()

原理是一样的。就不一一展示了。。。。。。

那么除此之外,我们还可以调用

setStartDelay()方法来设置动画延迟播放的时间,

setRepeatCount() 设置动画循环播放的次数

setRepeatMode()循环播放的模式,循环模式包括RESTART和REVERSE两种,分别表示重新播放和倒序播放的意思。

这些方法都很简单,就不再进行详细讲解了。
 

============【ObjectAnimator 】==========

ObjectAnimator 就不一样了,可以对任何对象进行动画设置,比如:View的背景颜色,还有alpha值。

从源码看,ObjectAnimator  是 继承  ValueAnimator,

所以说到底,都是以 ValueAnimator 设置值的 方式去操作动画。所以ValueAnimator 是整个属性动画的核心。

所以 ValueAnimator  的方法,在 ObjectAnimator  中几乎都能用。。。。所以 ObjectAnimator  几乎可以完全替代ValueAnimator  的。。。。

其实,ObjectAnimator   用法,也差不多,看代码----

我们先看下 透明度(alpha)

private fun objectAnimator() {
        /**
         *      参数1     tv_animator_view 目标View
         *      参数2     需要进行动画的属性 比如:alpha ,这个参数字符乱传会怎样? 后面会说到
         *      参数3...  这里可以传进多个参数,现在代码设置就是:0F, 1F, 0F, 1F
         *                就是从 0(完全透明)过渡到 1 (完全不透明) 再到 0(完全透明) 再到 1 (完全不透明)
         * */
        val objectAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        objectAnimator.duration = 4000
        objectAnimator.start()
    }

OK ,运行下代码,tv_animator_view这个View 确实按照上面说的展示:

从完全透明过渡到完全不透明,再到完全透明,再到完全不透明

举一反三,

我们看下 旋转(rotation)

val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view2, "rotation", 0F, -90F,90F,0F)
        rotationAnimator.duration = 10000
        rotationAnimator.start()

记得我上一篇文章说的,旋转设置的

正数 代表 顺时针方法,

负数 代表 逆时针方法。

所以上面 运行的效果是:先从原始位置,旋转到 逆时针旋转90度,然后从当前位置 旋转到 顺时针90度方向,然后再回到原始处。

运行时的动画效果图我就不贴了。。。。麻烦。。。

我们再看一下 平移(translationX 或 translationY)

        //获取当前 View X方向偏量 如果在原始位置,XY轴的偏移量都是0
        val originX = tv_animator_view3.translationX
        //获取 View 的宽度,返回值单位 px像素
        val width = tv_animator_view3.width
        /**
         *          参数1     tv_animator_view3 目标View
         *          参数2     translationX X轴的平移 正为往右  负数为左,单位为像素
         *          参数3     这里可以传进多个参数,
         *                    运行结果:从原始位置 往左移动 当前View的宽度,然后再回到原始位置
         * */
        val translationAnimator =
            ObjectAnimator.ofFloat(tv_animator_view3, "translationX", originX, width.toFloat(), originX)
        translationAnimator.duration = 10000
        translationAnimator.start()

运行结果就是我注释写的:从原始位置 往左移动 当前View的宽度,然后再回到原始位置

注意下方法里面的单位:

translationX    X轴的平移 正为往右  负数为左,单位为像素

translationY    Y轴的平移 正为往下  负数为上,单位为像素

我们再看一下 缩放(scaleX 或 scaleY)

        val translationAnimator =
            ObjectAnimator.ofFloat(tv_animator_view3, "scaleX", 0F, 1F)
        translationAnimator.duration = 10000
        translationAnimator.start()

看代码,聪明的你们都可以看出来了,就是在X轴方向,从中心点 从 0 放大到 1(原始尺寸)

如果换成 scaleY ,那就是 在纵向 进行缩放。。。。。

至此。属性动画最常用的四种动画(alpha、rotation、translationX和scaleY)都展示了。

但是就如最上面所提问的,方法里面的第二个参数,还可以填什么,我给你回答是:任何字符。

what???  are you kidding me?   不是,我并没有开玩笑。

Android 设计的时候,就没有限定 只能是View,可以是Object任何一个子类。

所以,你若在 ObjectAnimator.ofFloat(tv_animator_view3, "abd", 0F, 1F),传 abd,对这个View 是不会有任何动画效果的。

当然,也不至于 会carsh.....

你可以这么理解,传了一个 “scaleX” 进去,动画在进行过程中,就会不断在目标 查找,当前传进去的目标是否有setScaleX()  这个方法。

敲黑板!注意!!    是对应的     方法 

如果有,就会按照进度去调用这个方法。如果没有,则不处理。

而一开始传进去的是TextView ,它有方法 setScaleX(),动画就会对它进行改变。

但是如果我传进去的是“abd”,TextView是没有方法setAbd() ,所以它不会改变任何东西。。。。也不会报错

TextView 的方法 setScaleX(),其实是在其父类View 里面的,源码是这样的:

所以是调用View 的这个setScaleX() 

举一反三,聪明的你们,相比也会想到,View里面肯定会有:

setRotation()、getRotation()、setTranslationX()、getTranslationX()、setScaleY()、getScaleY()  这些方法。。。。。

至于,ObjectAnimator 机制是怎样找到对应set()方法的。。。其实是这样的,ObjectAnimator是依赖当前线程 Looper,如果当前线程 没有Looper,会报错的。从ObjectAnimator .start() 跟踪到最终的 ValueAnimator.star()方法可以看到。

至于怎创建当前的 Looper,就不是本章的内容。读者自己去补习。。。。。

其实一直把源码跟到最终的 animateValue(float  fraction),

里面有一段代码叫:mValues[i].calculateValue(fraction); 这段代码就是用来计算每一帧的属性值的

好的,我们看一下,属性动画是怎么获取 get()  和 set()方法的,这才是我们最关心的。。。。

get()方法,是在我们没有初始化的时候去获取的。 在  PropertyValuesHolder

通过反射获取

那么,set()  呢?其实也是在 PropertyValuesHolder 里面。因为我们创建ObjectAnimator  传进去的第二个参数,

就是保存 在 此类里面的,所以通过 PropertyValuesHolder  获取 get 和 set。

上面源码跟踪只是截取了关键部分,具体跟踪路径读者 自行去查找,,,哈哈

============【动画  中间代理】==================

好吧,我们来个Demo 来验证 属性动画的  方法 -- 属性

我们先建立一个  AnimationTemp 类

class AnimationTemp {

    private val TAG = "AnimationTemp"


    fun setAbcint(abcInt: Int) {
        Log.d(TAG, "调用 setAbcInt() abcInt :  $abcInt ")
    }

    fun getAbcint(): Int {
        Log.d(TAG, "调用 getAbcInt() ")
        return 100
    }

}

然后 按照属性动画的原则,动画对象可以是任何对象,好。我们就用这个 AnimationTemp 作为动画对象

        /**
         *          参数1   以 AnimationTemp 作为 动画对象
         *          参数2   改变 abcint 这个属性,
         *                  对应会在目标对象里找:setAbcint(abcInt: Int) 这个方法,注意,方法名大小写
         *          参数3   起始值
         *          参数4   结束值
         * */
        val ofInt = ObjectAnimator.ofInt(AnimationTemp(), "abcint", 0, 10)
        ofInt.duration = 100
        ofInt.start()

看下运行结果:

好了,很明显了。。。。。不断调用 setAbcint(abcInt: Int)  从0打印到10

再总结下,分析下原理吧:

1>   ObjectAnimator.onInt() 创建了一个以Int 渐变的动画,动画对象是  AnimationTemp实例,需要改变的属性叫:abcint

2>    动画进行时,系统 尝试去变动画对象的属性 abcint ,那么对应

        系统会在动画对象里寻找一个叫:setAbcint(abcInt: Int) 的方法,注意方法的入参类型 和 方法名的命名大小写

3>    所以在整个动画周期内,不断调用 setAbcint(abcInt: Int)  去改边目标动画的属性值

这个时候,你们可能看到,我们的getAbcint() 并没有调用,其实上面的源码已经分析到了,就是我们没有初始化值时,会调用。

好了,我们改一下代码:

 创建  动画时,传入结束值, (如果你只传一个Int只,系统默认为此值是结束值)

        /**
         *          参数1   以 AnimationTemp 作为 动画对象
         *          参数2   改变 abcint 这个属性,
         *                  对应会在目标对象里找:setAbcint(abcInt: Int) 这个方法,注意,方法名大小写
         *          参数3   结束值
         * */
        val ofInt = ObjectAnimator.ofInt(AnimationTemp(), "abcint", 10)
        ofInt.duration = 100
        ofInt.start()

然后 AnimationTemp 不变

class AnimationTemp {

    private val TAG = "AnimationTemp"


    fun setAbcint(abcInt: Int) {
        Log.d(TAG, "调用 setAbcInt() abcInt :  $abcInt ")
    }

    fun getAbcint(): Int {
        Log.d(TAG, "调用 getAbcInt() ")
        return 100
    }
}

看下结果:

好了,很明显。验证了源码的分析。

当没有初始化动画的初始值时,会在动画目标  里找对应属性的 get()方法,作为开始值。

这里我们返回100,作为开始值,然后结束值为10

所以从 100 打印到 10

所以,到这里,大家基本上知道属性动画的原理了吧。都明白了为什么属性动画的目标可以使任何目标!!!!

============【组合动画】==================

说完原理。最后说一下多个动画一起 进行。。组合动画。。。

AnimatorSet   就是用这个类。可以把 ValueAnimator 和 ObjectAnimator 一个或多个放进来播放

主要方法:

play()                            播放单个

playSequentially()       按顺序播放,一个接着一个来

playTogether()             所有动画一起播放

after(Animator animIn)            将现有动画插入到传入(即:animIn)的动画之后执行
after(long delay)                       将现有动画延迟指定毫秒后执行
before(Animator animIn)         将现有动画插入到传入动画(即:animIn)之前执行
with(Animator anim)                将现有动画和传入(即:animIn)的动画同时执行

Demo1----after

好了,我们来跑一趟代码:

        val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        alphaAnimator.duration = 4000

        val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
        rotationAnimator.duration = 4000

        val animatorSet = AnimatorSet()
        animatorSet.play(alphaAnimator).after(rotationAnimator)
        animatorSet.start()

运行效果是:rotationAnimator 先执行, alphaAnimator在执行

 【Demo2----with

        val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        alphaAnimator.duration = 4000

        val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
        rotationAnimator.duration = 4000

        val animatorSet = AnimatorSet()
        animatorSet.play(alphaAnimator).with(rotationAnimator)
        animatorSet.start()

运行效果是:rotationAnimator 和  alphaAnimator   同时在执行

 【Demo3----before

        val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        alphaAnimator.duration = 4000

        val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
        rotationAnimator.duration = 4000

        val animatorSet = AnimatorSet()
        animatorSet.play(alphaAnimator).before(rotationAnimator)
        animatorSet.start()

运行效果是:alphaAnimator先执行,rotationAnimator后执行

Demo4 -- after(long)

        val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        alphaAnimator.duration = 4000

        val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
        rotationAnimator.duration = 4000

        val animatorSet = AnimatorSet()
        animatorSet.play(alphaAnimator).before(rotationAnimator).after(3000)
        animatorSet.start()

运行效果是:延时3秒,alphaAnimator先执行,rotationAnimator后执行

Demo5 ---- playTogether

        val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        alphaAnimator.duration = 4000

        val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
        rotationAnimator.duration = 4000

        val animatorSet = AnimatorSet()
        animatorSet.playTogether(alphaAnimator, rotationAnimator)
        animatorSet.start()

运行效果是:rotationAnimator 和  alphaAnimator   同时在执行

Demo5 ---- playSequentially

        val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        alphaAnimator.duration = 4000

        val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
        rotationAnimator.duration = 4000

        val animatorSet = AnimatorSet()
        animatorSet.playSequentially(alphaAnimator, rotationAnimator)
        animatorSet.start()

运行效果是:alphaAnimator先执行,rotationAnimator后执行

==================【动画的监听】==================

其实就是一个方法

        val alphaAnimator = ObjectAnimator.ofFloat(tv_animator_view, "alpha", 0F, 1F, 0F, 1F)
        alphaAnimator.duration = 4000

        val rotationAnimator = ObjectAnimator.ofFloat(tv_animator_view, "rotation", 0F, -90F, 90F, 0F)
        rotationAnimator.duration = 4000

        val animatorSet = AnimatorSet()
        animatorSet.addListener(object : Animator.AnimatorListener{
            override fun onAnimationRepeat(animation: Animator?) {
                //重复
            }

            override fun onAnimationEnd(animation: Animator?) {
                //结束
            }

            override fun onAnimationCancel(animation: Animator?) {
                //取消
            }

            override fun onAnimationStart(animation: Animator?) {
                //开始
            }

        })
        animatorSet.playSequentially(alphaAnimator, rotationAnimator)
        animatorSet.start()

其实,不知道信心的你们,有没有发现,属性动画没有发现有 setFillAfter() 设置动画结束后,是否恢复到初始状态。

这个嘛,我们可以通过监听动画的结束,然后再结束的时候再设置一下target的状态

public class ResetAnimatorListenerAdapter extends AnimatorListenerAdapter {

    View animatorView;

    public ResetAnimatorListenerAdapter(View animatorView) {
        this.animatorView = animatorView;
    }

    @Override
    public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
        //重置 animatorView 的状态
        animatorView.setTranslationY(0);
        animatorView.setAlpha(0.0f);
    }

    @Override
    public void onAnimationCancel(Animator animation) {
        super.onAnimationCancel(animation);
        
    }
    
}

上面说了一大通 代码动态设置动画,其实还可以通过 xml 设置,具体的就不说。因为不太建议用xml,比较死不能更改。

最后说一下,动画的注意事项

1:防止 OOM,避免使用图片比较多的 帧动画。。。

2:防止内存泄漏,View退出不可见时,要停止动画

3:属性动画是在Android3.0以上使用的,低于这个版本,可以使用兼容库

4:View 动画,当移动View了以后,有时会导致View.GONE设置失效,这个时候只要调用View.clearAnimation()清除动画既可

5:动画进行时,可以开启硬件加速,提高流畅性。

以上代码亲测无问题,有问题请留言指正,谢谢

猜你喜欢

转载自blog.csdn.net/Leo_Liang_jie/article/details/90812632