只有一种真正的英雄主义


一、摘要

ObjectAnimator是ValueAnimator的子类,它和ValueAnimator一样,同样具有计算属性值的功能,但对比ValueAnimator,它会更加容易使用,因为它不再需要设置监听器来监听值的变化,因为这个工程对于ObjectAnimator来说,是自动的。这篇文章主要通过详细讲解ObejctAniamtior,加深大家对属性动画的认识,让我们对于动画的技巧掌握得更扎实。

如果你想了解更权威的解释,可以查看官方文档:Property Animation。

本文主要对ValueAnimator做介绍,如果大家有兴趣,可以继续阅读本动画系列其他相关文章,作者也在不断更新完善相关内容,希望大家可以指出有误之处。

Android基础夯实--重温动画(一)之Tween Animation

Android基础夯实--重温动画(二)之Frame Animation

Android基础夯实--重温动画(三)之初识Property Animation

Android基础夯实--重温动画(四)之属性动画 ValueAnimator详解

二、 概述

在上节我们知道了在属性动画中,ValueAnimator是通过监听值的变化,然后实现控件的动画播放。在代码过程中,是通过设置初始值、结束值和动画时间,然后通过加速器返回当前的进度的,再经过Evaluator根据进度计算出具体的值,然后我们在监听器里面不断监听拿到这个值,然后修改控件的属性值,从而实现动画。

ObjectAnimator作为ValueAnimator的子类,所以ValueAnimator的很多方法,在ObjectAnimator中也能使用,但是ObjectAnimator覆写了父类的几个方法,如ofInt(),ofFloat(),ofArgb()等。它和ValueAnimator同样也是首先设置初始值、结束值和动画时长,但是同时也绑定了目标控件和属性然后通过加速器返回当前的进度的,再经过Evaluator根据进度计算出具体的值,最后根据属性拼接set函数并反射调用,并将当前值作为参数传入,实现动画。

对比这两个Animator,ObjectAnimator是对ValueAnimator的再封装,它的封装帮助我们避免了使用Listener的麻烦,更加精简了代码,使开发者可以更加专注动画的逻辑代码。

2.1 差异

以下通过Demo来对比ObjectAnimator和ValueAnimator的区别,我们同样使用ObjectAnimator和ValueAnimator实现同样的效果,控件水平方向上的不断左右移动,最后返回原点(如下图)。

通过上一节的学习,我们可以轻松写出ValueAnimator实现的代码:

ValueAnimator animator = ValueAnimator.ofFloat(0, 300, -100, 200, -50, 0);
animator.setDuration(2000);
animator.start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        mBinding.image.setTranslationX((Float) animation.getAnimatedValue());
    }
});

在ObjectAnimator中,我们可以用更简单的代码来实现:

ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.image, "translationX", 0, 300, -100, 200, -50, 0);
animator.setDuration(2000);
animator.start();

通过对比,我们可以看到,ObjectAnimator在实现动画上的代码上会更加简洁,但是实现的效果都是一样的,之所以是这样,是因为ObjectAnimator覆写了ValueAnimator 的ofFloat方法,并对其进行了封装。所以,我们看到ObjectAnimator实现代码上都减少了Listener代码的编写。

大家第一次看到ObjectAnimator可能看到代码比较陌生,上面的一段代码什么意思呢?首先我们看到第一个参数,就是我们动画的目标控件,也就是这个动画要让哪个控件来实现,我这里是DATABinding的一个写法,其实就是我们findViewById得到的一个view,非常的简单;第二个参数是动画要实现的效果,我这里是translationX,即水平x方向上的位移;第三个参数为可变参数,即动画变化过程的系列值,跟ValueAnimator是一样的意思。

2.2 propertyName

在2.1的介绍过后,大家可能还会有疑问,对于上面ofFloat的第二个参数是一个字符串常量,这个字符串常量我们是怎么获取的呢?其实,在ObejctAnimator中,无论ofFloat,ofInt,ofArgb等方法,都有一个叫做propertyName的参数,也就是我们上面对应的第二个参数,这个参数是一个字符串,之所以动画会实现该字符串的效果是因为ObjectAnimator通过反射机制,找到了ImageView中的setTranslationX()这个方法,然后每个十几ms就调用这个方法,并把我们的变化的值传到里面去,从而实现动画效果。

所以到这里大家可以知道,当我们需要用ObjectAnimator实现一个控件的动画效果时,我们首先需要做的就是在这个控件中找到对应的setXXX()驼峰式写法的方法,只有控件拥有相应的setXXX()方法, 我们传入的propertyName参数才起到作用。那么这时候就有同学会问,类似Demo中的“translationX”,它对应的方法是setTranslationX(),那我们应该传入“TranslationX”还是“translationX”,其实都是可以的,因为它内部封装在使用反射机制调用方法时,涵盖了两种写法,所以第一个字母可以大小写,但是后面的字母必须全部对应大小写。

其次还要同学会疑问,我们怎么知道在实例化ObjectAnimator时应该通过ofFloat还是ofInt还是其他呢?其实跟我们的ValueAnimator一样,我们调用哪个方法来实例化都是要考虑我们要改变的控件哪个属性的。例如,我们上面的例子是改变水平方向上的位移,那么ObjectAnimator最终是调用控件的setTranslationX(float translationX),我们可以看到传入参数是float型,那么毫无疑问,我们这里需要使用ofFloat了,其它以此类推。下面给大家举例一些常用的propertyName。

2.2.1 Scale

View中关于伸缩变化(Scale)有以下两个方法:

  • public void setScaleX(float scaleX):X方向上伸缩。
  • public void setScaleY(float scaleY):Y方向上伸缩。

所以对应代码为:

ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.image, "scaleX", 0f, 1.5f, 2f, 1.5f, 0f, 0.5f, 0.2f, 1f);
mBinding.image.setScaleType(ImageView.ScaleType.CENTER_CROP);
animator.setDuration(2000);
animator.start();
View view = new View(MyObjectAnimator.this);
ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.image, "scaleY", 0f, 1.5f, 2f, 1.5f, 0f, 0.5f, 0.2f, 1f);
animator.setDuration(2000);
mBinding.image.setScaleType(ImageView.ScaleType.CENTER_CROP);
animator.start();

2.2.2 Translation

View中关于位置变化(Translation)有以下两个方法:

  • public void setTranslationX(float translationX):X轴上位移。
  • public void setTranslationY(float translationY):Y轴上位移。
  • public void setTranslationZ(float translationZ):设置阴影。

所以对应代码为:

ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.image, "translationX", 0, 100, -100, 0);
mBinding.image.setScaleType(ImageView.ScaleType.CENTER_CROP);
animator.setDuration(2000);
animator.start();
ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.image, "translationY", 0, 100, -100, 0);
animator.setDuration(2000);
mBinding.image.setScaleType(ImageView.ScaleType.CENTER_CROP);
animator.start();

2.2.3 Alpha

View中关于透明度变化(Alpha)有以下方法:

  • public void setAlpha(float alpha);

所以对应代码为:

ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.image, "alpha", 0, 0.5f, 1.0f);
mBinding.image.setScaleType(ImageView.ScaleType.CENTER_CROP);
animator.setDuration(2000);
animator.start();

2.2.4 Rotation

View中关于角度变化(Rotation)有以下方法:

  • public void setRotation(float rotation):关于Z轴旋转。
  • public void setRotationX(float rotationX):关于X轴旋转。
  • public void setRotationY(float rotationY):关于Y轴旋转。

所以对应代码为:

ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.image, "rotation", 0, 180, 0, -180, 0);
mBinding.image.setScaleType(ImageView.ScaleType.CENTER_CROP);
animator.setDuration(3000);
animator.start();
ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.image, "rotationX", 0, 180, 0, -180, 0);
animator.setDuration(3000);
mBinding.image.setScaleType(ImageView.ScaleType.CENTER_CROP);
animator.start();
ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.image, "rotationY", 0, 180, 0, -180, 0);
animator.setDuration(3000);
mBinding.image.setScaleType(ImageView.ScaleType.CENTER_CROP);
animator.start();

2.2.5 图片变化

View中关于图片的其中一个方法是setBackgroundResource,同样我们也可以通过ObjectAnimator来实现一个图片变化的效果:

  • public void setBackgroundResource(@DrawableRes int resid)

对应代码为:

ObjectAnimator animator = ObjectAnimator.ofInt(mBinding.image, "backgroundResource", R.drawable.a1, R.drawable.a2, R.drawable.a3);
animator.setDuration(2000);
animator.start();

其实还有很多set方法可以可以实现动画效果,这里就不再一一列举,大家有兴趣可以自己去深入研究,你会发现ObjectAnimator可以非常简单快捷地实现动画效果。

2.3 实例化ObjectAnimator方法

在ValueAnimator中,我们知道实例化并不是通过new一个对象出来,而是通过ofInt,ofFloat,ofObject等方法。在ObjectAnimator中同样如此,因为ofInt,ofFloat,ofObject等方法的内部帮我们封装了实例化过程,所以我们可以直接调用来拿到一个实例化的对象。在ObjectAnimator中,大概有以下几种实例化方法:

2.3.1 ofInt()

通过ofInt()来实例化对象,那么属性值必须为int型,通常我们通过ofInt可以实现很多动画,例如实现颜色渐变等;ofInt()也有几个重载函数,这里介绍其中一个:

  • ofInt(Object target, String propertyName, int... values):对目标对象T的property属性值进行改变。

例如颜色值的变化。

ObjectAnimator animator = ObjectAnimator.ofInt(mBinding.image, "backgroundColor",  0xffff00ff, 0xffffff00, 0xffff00ff);
animator.setEvaluator(new ArgbEvaluator());
animator.setDuration(4000);
animator.start();

2.3.2 ofFloat()

ofFloat()来实例化对象,那么属性值必须为float型,通常我们通过ofFloat可以实现很多动画,例如实现位置变化等;ofFloat()也有几个重载函数,这里介绍其中一个:

  • ObjectAnimator ofFloat (Object target, String xPropertyName, String yPropertyName, Path path)::对目标对象T的property属性值进行改变。

例如实现一个贝塞尔曲线:

Path path = new Path();
path.quadTo(800, 200, 800, 800);
ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.image, "x", "y", path);
animator.setDuration(4000);
animator.start();

2.3.3 ofArgb()

我们在ValueAnimator中已经提到了ofArgb()可以帮助我们实现颜色的渐变效果,这里同样是可以通过ofArgb()来实现动画效果。上面我们已经在ofInt里面实现了颜色渐变,但是代码稍多,所以Google在API LEVEL 21之后增加了这个方法ofArgb()。通过这个方法我们更容易地实现颜色演变,因为它里面封装了对ArgbEvaluator的使用,实现2.3.1的效果,大家可以对比一下代码:

ObjectAnimator animator = ObjectAnimator.ofArgb(mBinding.image, "BackgroundColor", 0xffff00ff, 0xffffff00, 0xffff00ff);
animator.setDuration(4000);
animator.start();

2.3.4 ofPropertyValuesHolder()

认真的同学都会发现,在ValueAnimator和ObjectAnimator中,都有一个实例化方法,就是ofPropertyValuesHolder()方法,由于在ObjectAnimator中使用更为广泛,所以这里以ObjectAnimator的ofPropertyValuesHolder为例子,当大家懂了之后,那么大家对ValueAnimator的ofPropertyValuesHolder也应该理解了。

在ObjectAnimator中,我们可以通过

  • ObjectAnimator ofPropertyValuesHolder (Object target, PropertyValuesHolder... values)

来实例化一个ObjectAnimator。

我们可以看到,它和我们其他的实例化方法差不多,都需要设置一个target(目标控件),还有一组PropertyValuesHolder类型的值,但是不需要设置属性,target我们知道了,是要实现动画的控件,那么PropertyValuesHolder是什么呢?我们来看一下官方文档:

This class holds information about a property and the values that that property should take on during an animation. PropertyValuesHolder objects can be used to create animations with ValueAnimator or ObjectAnimator that operate on several different properties in parallel.

什么意思呢?

这是一个包含一个属性信息的类,并且它的值应该用到一个动画里面。PropertyValuesHolder对象可以配合ValueAnimator和ObjectAnimator来实现不同属性的并行的动画。

听起来有点别扭,也就是说,当我们需要实现一个包含多种属性的同时播放的动画时,我们就可以使用ofPropertyValuesHolder来实例化一个Animator,当然,拥有一个属性时也是可以的,为什么这么说?ofFloat()的内部实现其实就是将传进来的参数封装成PropertyValuesHolder实例来保存动画状态。所以可见PropertyValuesHolder是多么有用了吧。

在PropertyValuesHolder这个类里面,同样也有ofInt(),ofFloat(),ofKeyframe()等方法来实例化,举个例子:

代码也非常简单:

PropertyValuesHolder rotationHolder = PropertyValuesHolder.ofFloat("Rotation", 90, -90, 45, -45, 60, -60);
PropertyValuesHolder colorHolder = PropertyValuesHolder.ofInt("BackgroundColor", 0xff55aa11, 0xff115633, 0xff123344, 0xffaabbcc);
PropertyValuesHolder scaleXHolder = PropertyValuesHolder.ofFloat("ScaleX", 1f, 1.1f, 1.2f, 1.5f, 1.8f, 1.5f, 1.2f, 1.1f, 1);
PropertyValuesHolder scaleYHolder = PropertyValuesHolder.ofFloat("ScaleY", 1f, 1.1f, 1.2f, 1.5f, 1.8f, 1.5f, 1.2f, 1.1f, 1);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mBinding.image, rotationHolder, colorHolder, scaleXHolder, scaleYHolder);
animator.setDuration(3500);
animator.setInterpolator(new AccelerateInterpolator());
animator.start();

通过代码我们可以知道,通过ofPropertyValuesHolder来实例化,其实就是将不同动画效果分配到一个个PropertyValuesHolder中去,然后把多个不同的PropertyValuesHolder对象在初始化时传入,最终实现多个效果并行播放。

2.3.4 其他实例化方法

在ObjectAnimator中提供了非常丰富的实例化方法,除了以上三个之外,在API LEVEL 21之后,Google推出了更多的实例化方法,例如:

  • ofMultiFloat()
  • ofMultiInt()
  • ofObject()
  • ofPropertyValuesHolder()

在上面三个(2.3.1-2.3.3)不足以解决我们需求的时候,我们可以到官方文档参考这三个比较新的实例化方法,它们也是为了简化操作而进行了更高度的封装,所以这也有助于帮助我们用更少的代码来实现动画。

总结

ObjectAnimator作为ValueAnimator的子类,在代码上对ValueAnimator进行了进一步的封装,使我们在日常使用中更加简单,但是正是因为封装,使得我们在一些特殊情况下使用ObjectAnimator使用上还是有一定的局限性,所以在大家掌握了ValueAnimator和ObjectAnimator的基本使用后,还需要自己通过写小Demo来加深和进阶使用,这样在我们用到时方能得心应手。