Android动画

简书真是受不来了,引用的是我7牛上的图片,预览的时候没有问题,一发布就告诉我获取图片失败,不想管了。。。。

动画篇

Property Animation Overview

启舰

Android自定义控件三部曲文章索引

HenCoder Android 自定义 View 1-7:属性动画 Property Animation(进阶篇)

1 概述

在 Android 动画中,总共有两种类型的动画 View Animation(视图动画) 和 Property Animator(属性动画);

其中

  • View Animation 包括 Tween Animation(补间动画)和 Frame Animation(逐帧动画);
  • Property Animator 包括 ValueAnimator 和 ObjectAnimation;

首先,直观上,他们有如下三点不同:

1. 引入时间不同:View Animation 是 API Level 1 就引入的。Property Animation 是 API Level 11 引入的,即 Android 3.0 才开始有 Property Animation 相关的 API。

2. 所在包名不同:View Animation 在包 android.view.animation 中。而 Property Animation API 在包 android.animation中。

3. 动画类的命名不同:View Animation 中动画类取名都叫 XXXXAnimation, 而在 Property Animator 中动画类的取名则叫 XXXXAnimator

为什么还要引入 Property Animator 呢?

  1. Property Animator 能实现补间动画无法实现的功能
  2. View Animation 仅能对指定的控件做动画,而 Property Animator 是通过改变控件某一属性值来做动画的。
  3. 补间动画虽能对控件做动画,但并没有改变控件内部的属性值,所以经常出现视觉效果与从控件中获取的值不统一的问题。而 Property Animator 则是恰恰相反,Property Animator 是通过改变控件内部的属性值来达到动画效果的。

2. ValueAnimator

2.1 ValueAnimator.ofInt()/ofFloat()


public static ValueAnimator ofInt(int... values)
public static ValueAnimator ofFloat(float... values)

//示例    
ValueAnimator animator = ValueAnimator.ofFloat(0f,400f,50f,300f);
animator.setDuration(3000);
 
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        Float curValueFloat = (Float)animation.getAnimatedValue();//获取当前运动点的值
        int curValue = curValueFloat.intValue();
        tv.layout(curValue,curValue,curValue+tv.getWidth(),curValue+tv.getHeight());
    }
});
animator.start();

他们的参数类型都是可变参数长参数,所以我们可以传入任何数量的值;传进去的值列表,就表示动画时的变化范围;比如ofInt(2,90,45)就表示从数值2变化到数字90再变化到数字45;所以我们传进去的数字越多,动画变化就越复杂。

2.2 常用函数

/**
 * 设置动画时长,单位是毫秒
 */
ValueAnimator setDuration(long duration)
/**
 * 获取ValueAnimator在运动时,当前运动点的值
 */
Object getAnimatedValue();
/**
 * 开始动画
 */
void start()
/**
 * 设置循环次数,设置为ValueAnimation.INFINITE表示无限循环
 */
void setRepeatCount(int value)
/**
 * 设置循环模式
 * value取值有RESTART,REVERSE,
 */
void setRepeatMode(int value)
/**
 * 取消动画
 */
void cancel()

2.3 两个监听器

/**
 * 监听器一:监听动画变化时的实时值
 */
public static interface AnimatorUpdateListener {
    void onAnimationUpdate(ValueAnimator animation);
}
//添加方法为:public void addUpdateListener(AnimatorUpdateListener listener)
/**
 * 监听器二:监听动画变化时四个状态
 */
public static interface AnimatorListener {
    void onAnimationStart(Animator animation);
    void onAnimationEnd(Animator animation);
    void onAnimationCancel(Animator animation);
    void onAnimationRepeat(Animator animation);
}
//添加方法为:public void addListener(AnimatorListener listener) 

/**
 * 移除AnimatorUpdateListener
 */
void removeUpdateListener(AnimatorUpdateListener listener);
void removeAllUpdateListeners();
 /**
  * 移除AnimatorListener
  */
void removeListener(AnimatorListener listener);
void removeAllListeners();

2.4 其它函数

/**
 * 延时多久时间开始,单位是毫秒
 */
public void setStartDelay(long startDelay)
/**
 * 完全克隆一个ValueAnimator实例,包括它所有的设置以及所有对监听器代码的处理
 */
public ValueAnimator clone()

2.5 ValueAnimatot.ofObject()

ref : 自定义控件三部曲之动画篇(六)——ValueAnimator高级进阶(二)

3. Interpolator

插值器就是用来控制动画区间的值被如何计算出来的。比如LinearInterpolator插值器就是匀速返回区间点的值;而DecelerateInterpolator则表示开始变化快,后期变化慢;其它都类似。

3.1 概述

java类 xml资源id 说明
AccelerateDecelerateInterpolator @android:anim/accelerate_decelerate_interpolator 默认加速器,其变化开始和结束速率较慢,中间加速
AccelerateInterpolator @android:anim/accelerate_interpolator 其变化开始速率较慢,后面加速
DecelerateInterpolator @android:anim/decelerate_interpolator 其变化开始速率较快,后面减速
LinearInterpolator @android:anim/linear_interpolator 其变化速率恒定
AnticipateInterpolator @android:anim/anticipate_interpolator 其变化开始向后甩,然后向前即先回拉一下再进行正常动画轨迹。效果看起来有点像投掷物体或跳跃等动作前的蓄力。
OvershootInterpolator @android:anim/overshoot_interpolator 其变化开始向前甩,过冲到目标值,最后又回到了终值
AnticipateOvershootInterpolator @android:anim/anticipate_overshoot_interpolator 其变化开始向后甩,然后向前甩,过冲到目标值,最后又回到了终值
BounceInterpolator @android:anim/bounce_interpolator 其变化在结束时反弹,即在目标值处弹跳。有点像玻璃球掉在地板上的效果。
CycleInterpolator @android:anim/cycle_interpolator 循环播放,其速率为正弦曲线
TimeInterpolator 一个接口,可以自定义插值器
PathInterpolator 自定义动画完成度 / 时间完成度曲线

3.2 使用

                mCurrentAnim = ValueAnimator.ofFloat(0f, 500f)
                mCurrentAnim?.duration = 3000
                mCurrentAnim?.interpolator = AccelerateDecelerateInterpolator()
                mCurrentAnim?.interpolator = AccelerateInterpolator()
                mCurrentAnim?.interpolator = DecelerateInterpolator()
                mCurrentAnim?.interpolator = LinearInterpolator()
                mCurrentAnim?.interpolator = AnticipateInterpolator()
                mCurrentAnim?.interpolator = OvershootInterpolator()
                mCurrentAnim?.interpolator = AnticipateOvershootInterpolator()
                mCurrentAnim?.interpolator = BounceInterpolator()
                mCurrentAnim?.interpolator = CycleInterpolator(1f)
                val interpolatorPath: Path = Path()
                // 匀速
                //interpolatorPath.lineTo(1f, 1f);
                // 先以「动画完成度 : 时间完成度 = 1 : 1」的速度匀速运行 25%
                interpolatorPath.lineTo(0.25f, 0.25f);
                // 然后瞬间跳跃到 150% 的动画完成度
                interpolatorPath.moveTo(0.25f, 1.5f);
                // 再匀速倒车,返回到目标点
                interpolatorPath.lineTo(1f, 1f);
                mCurrentAnim?.interpolator = PathInterpolator(interpolatorPath)
                mCurrentAnim?.interpolator = FastOutLinearInInterpolator()
                mCurrentAnim?.interpolator = FastOutSlowInInterpolator()


                mCurrentAnim?.addUpdateListener { animationValue ->
                    val curValueFloat = animationValue.animatedValue as Float
                    val curValue = 500 + curValueFloat.toInt()
                    tv_anim.layout(tv_anim.left, curValue, tv_anim.left + tv_anim.getWidth(), curValue + tv_anim.getHeight())
                }
                mCurrentAnim?.start()

3.3 几个比较难理解的

CycleInterpolator

CycleInterpolator

这个也是一个正弦 / 余弦曲线,不过它和 AccelerateDecelerateInterpolator 的区别是,它可以自定义曲线的周期,所以动画可以不到终点就结束,也可以到达终点后回弹,回弹的次数由曲线的周期决定,曲线的周期由 CycleInterpolator() 构造方法的参数决定。

//从起点到终点又回到起点
mCurrentAnim?.interpolator = CycleInterpolator(0.5f)
//从起到到终点,又回到起点,然后逆向移动到负终点,又回到起点
mCurrentAnim?.interpolator = CycleInterpolator(1f)

PathInterpolator

自定义动画完成度 / 时间完成度曲线。

用这个 Interpolator 你可以定制出任何你想要的速度模型。定制的方式是使用一个 Path 对象来绘制出你要的动画完成度 / 时间完成度曲线。例如:

val interpolatorPath : Path = Path()
// 匀速运动
interpolatorPath.lineTo(1f, 1f);
mCurrentAnim?.interpolator = PathInterpolator(interpolatorPath)
image.png
val interpolatorPath : Path = Path()
// 先以「动画完成度 : 时间完成度 = 1 : 1」的速度匀速运行 25%
interpolatorPath.lineTo(0.25f, 0.25f);
// 然后瞬间跳跃到 150% 的动画完成度
interpolatorPath.moveTo(0.25f, 1.5f);
// 再匀速倒车,返回到目标点
interpolatorPath.lineTo(1f, 1f);
mCurrentAnim?.interpolator = PathInterpolator(interpolatorPath)
image.png

你根据需求,绘制出自己需要的 Path,就能定制出你要的速度模型。

不过要注意,这条 Path 描述的其实是一个 y = f(x) (0 ≤ x ≤ 1) (y 为动画完成度,x 为时间完成度)的曲线,所以同一段时间完成度上不能有两段不同的动画完成度(这个好理解吧?因为内容不能出现分身术呀),而且每一个时间完成度的点上都必须要有对应的动画完成度(因为内容不能在某段时间段内消失呀)。所以,时间完成度重复或者动画完成度缺失,都会导致程序 FC(force close):

image.png

TimeInterpolator

常用来自定义差值器

class MyInterpolator : TimeInterpolator {
    /**
     * Maps a value representing the elapsed fraction of an animation to a value that represents
     * the interpolated fraction. This interpolated value is then multiplied by the change in
     * value of an animation to derive the animated value at the current elapsed animation time.
     *
     * @param input input参数是一个float类型,它取值范围是0到1,
     * 表示当前动画的进度,取0时表示动画刚开始,取1时表示动画结束,取0.5时表示动画中间的位置,其它类推。
     * @return 表示当前实际想要显示的进度。
     * 取值可以超过1也可以小于0,超过1表示已经超过目标值,小于0表示小于开始位置。
     */
    override fun getInterpolation(input: Float): Float {
        return 1 - input
    }
}

3.4 三个新的 Interpolator 模型

除了上面的这些,Android 5.0 (API 21)引入了三个新的 Interpolator 模型,并把它们加入了 support v4 包中。这三个新的 Interpolator 每个都和之前的某个已有的 Interpolator 规则相似,只有略微的区别。

FastOutLinearInInterpolator

加速运动。

FastOutLinearInInterpolator 的曲线公式是用的贝塞尔曲线(红色),而 AccelerateInterpolator 用的是指数曲线(绿色)。具体来说,它俩最主要的区别是 FastOutLinearInInterpolator 的初始阶段加速度比 AccelerateInterpolator 要快一些,「用起来没区别」。

image.png

FastOutSlowInInterpolator

先加速再减速。

同样也是先加速再减速的还有前面说过的 AccelerateDecelerateInterpolator,不过它们的效果是明显不一样的。FastOutSlowInInterpolator 用的是贝塞尔曲线(红色),AccelerateDecelerateInterpolator用的是正弦 / 余弦曲线(绿色)。具体来讲, FastOutSlowInInterpolator 的前期加速度要快得多

image.png

LinearOutSlowInInterpolator

持续减速。

它和 DecelerateInterpolator 比起来(绿色),同为减速曲线,主要区别在于 LinearOutSlowInInterpolator的初始速度更高(红色)。对于人眼的实际感觉,区别其实也不大,不过还是能看出来一些的。

image.png

4. Evaluator

4.1 概述

我们先不讲什么是Evaluator,我们先来看一张图:

image.png

这幅图讲述了从定义动画的数字区间到通过AnimatorUpdateListener中得到当前动画所对应数值的整个过程。下面我们对这四个步骤具体讲解一下:

  1. ofInt(0,400)表示指定动画的数字区间,是从0运动到400;
  2. 加速器:上面我们讲了,在动画开始后,通过加速器会返回当前动画进度所对应的数字进度,但这个数字进度是百分制的,以小数表示,如0.2
  3. Evaluator:我们知道我们通过监听器拿到的是当前动画所对应的具体数值,而不是百分制的进度。那么就必须有一个地方会根据当前的数字进度,将其转化为对应的数值,这个地方就是Evaluator;Evaluator就是将从加速器返回的数字进度转成对应的数字值。所以上部分中,我们讲到的公式:
当前的值 = 100 + (400 - 100)* 显示进度

Evaluator其实就是一个转换器,他能把小数进度转换成对应的数值位置

4.2 各种Evaluator

首先,加速器返回的小数值,表示的是当前动画的数值进度。进度必然都是在0到1之间的,0表示没开始,1表示数值运动的结束,对于任何动画都是适用的。

但Evaluator则不一样,我们知道Evaluator是根据加速器返回的小数进度转换成当前数值进度所对应的值。这问题就来了,如果我们使用ofInt()来定义动画,动画中的值应该都是Int类型,如果我用ofFloat()来定义动画,那么动画中的值也都是Float类型。

所以如果我用ofInt()来定义动画,所对应的Evaluator在返回值时,必然要返回Int类型的值。同样,我们如果用ofFloat来定义动画,那么Evaluator在返回值时也必然返回的是Float类型的值。

所以每种定义方式所对应的Evaluator必然是它专用的;ofInt()对应的Evaluator类名叫IntEvaluator,而ofFloat()对应的Evaluator类名叫FloatEvaluator; 在设置Evaluator时,是通过animator.setEvaluator()来设置的,比如:

  mCurrentAnim = ValueAnimator.ofInt(0, 500)
  mCurrentAnim?.duration = 3000
  //设置Evaluator
   mCurrentAnim?.setEvaluator(IntEvaluator())
  //在此之前,我们在使用ofInt()时,从来没有给它定义过使用IntEvaluator来转换值啊,那怎么也能正常运行呢?
  //因为ofInt和ofFloat都是系统直接提供的函数,所以在使用时都会有默认的加速器和Evaluator来使用的,不指定则使用默认的;对于Evaluator而言,ofInt()的默认Evaluator当然是IntEvaluator;而FloatEvalutar默认的则是FloatEvalutor

  mCurrentAnim?.addUpdateListener { animationValue ->
  val curValueFloat = animationValue.animatedValue as Float
  val curValue = 500 + curValueFloat.toInt()
  tv_anim.layout(tv_anim.left, curValue, tv_anim.left + tv_anim.getWidth(), curValue +     tv_anim.getHeight())
  mCurrentAnim?.start() }

ArgbEvalutor

Evalutor不能通用,会报强转错误,也就是说,只有在数值类型相同的情况下,Evalutor才能共用。

除了IntEvaluator和FloatEvalutor,在android.animation包下,还有如下的Evaluator:

ArgbEvaluator, FloatArrayEvaluator, FloatEvaluator, IntArrayEvaluator, IntEvaluator, PointFEvaluator, RectEvaluator

举例:ArgbEvalutor是用来做颜色值过渡转换的。

 mCurrentAnim = ValueAnimator.ofInt(0xffffff00.toInt(), 0xff0000ff.toInt())
//将动画的数据范围定义为(0xffffff00,0xff0000ff),即从黄色,变为蓝色。 
mCurrentAnim?.setEvaluator(ArgbEvaluator())
mCurrentAnim?.duration = 3000

mCurrentAnim?.addUpdateListener { animation ->
  val curValue = animation.animatedValue as Int
  tv_anim.setBackgroundColor(curValue)
}
 mCurrentAnim?.start()
}

4.3 自定义TypeEvaluator

private class HsvEvaluator implements TypeEvaluator<Integer> {  
   float[] startHsv = new float[3];
   float[] endHsv = new float[3];
   float[] outHsv = new float[3];

   @Override
   public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
       // 把 ARGB 转换成 HSV
       Color.colorToHSV(startValue, startHsv);
       Color.colorToHSV(endValue, endHsv);

       // 计算当前动画完成度(fraction)所对应的颜色值
       if (endHsv[0] - startHsv[0] > 180) {
           endHsv[0] -= 360;
       } else if (endHsv[0] - startHsv[0] < -180) {
           endHsv[0] += 360;
       }
       outHsv[0] = startHsv[0] + (endHsv[0] - startHsv[0]) * fraction;
       if (outHsv[0] > 360) {
           outHsv[0] -= 360;
       } else if (outHsv[0] < 0) {
           outHsv[0] += 360;
       }
       outHsv[1] = startHsv[1] + (endHsv[1] - startHsv[1]) * fraction;
       outHsv[2] = startHsv[2] + (endHsv[2] - startHsv[2]) * fraction;

       // 计算当前动画完成度(fraction)所对应的透明度
       int alpha = startValue >> 24 + (int) ((endValue >> 24 - startValue >> 24) * fraction);

       // 把 HSV 转换回 ARGB 返回
       return Color.HSVToColor(alpha, outHsv);
   }
}

ObjectAnimator animator = ObjectAnimator.ofInt(view, "color", 0xff00ff00);  
// 使用自定义的 HslEvaluator
animator.setEvaluator(new HsvEvaluator());  
animator.start();  

5. ObjectAnimator

为了能让动画直接与对应控件相关联,以使我们从监听动画过程中解放出来,谷歌的开发人员在ValueAnimator的基础上,又派生了一个类ObjectAnimator。

image.png

由于ObjectAnimator是派生自ValueAnimator的,所以ValueAnimator中所能使用的方法,在ObjectAnimator中都可以正常使用,但ObjectAnimator也重写了几个方法,比如ofInt(),ofFloat()等。我们先看看利用ObjectAnimator重写的ofFloat方法如何实现一个动画:(改变透明度)

lateinit var mAnimator: ObjectAnimator
//mAnimator = ObjectAnimator.ofFloat(tv_anim, "alpha", 1f, 0f, 1f)
mAnimator = ObjectAnimator.ofFloat(tv_anim, "rotation", 0f, 180f, 0f)
mAnimator.setDuration(2000)
mAnimator.start()
//第一个参数用于指定这个动画要操作的是哪个控件
//第二个参数用于指定这个动画要操作这个控件的哪个属性
//第三个参数是可变长参数,这个就跟ValueAnimator中的可变长参数的意义一样了,就是指这个属性值是从哪变到哪。
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) 

ObjectAnimator做动画,会通过如下规则找到指定属性所对应的set方法来改变属性

  1. 要使用ObjectAnimator来构造对画,要操作的控件中,必须存在对应的属性的set方法
  2. setter 方法的命名必须以骆驼拼写法命名,即set后每个单词首字母大写,其余字母小写,即类似于setPropertyName所对应的属性为propertyName

比如我们上面指定的改变rotation的属性值,ObjectAnimator在做动画时就会到指定控件(TextView)中去找对应的setRotation()方法来改变控件中对应的值。

5.1 常用设置属性

setRotationX(float rotationX):表示围绕X轴旋转,rotationX表示旋转度数 
setRotationY(rotationY):表示围绕Y轴旋转,rotationY表示旋转度数 
setRotation(float rotation):表示围绕Z旋转,rotation表示旋转度数 

image.png

从这张图中,绿色框部分表示手机屏幕,很明显可以看出Z轴就是从屏幕左上角原点向外伸出的一条轴。

setTranslationX(float translationX) :表示在X轴上的平移距离,以当前控件为原点,向右为正方向,参数translationX表示移动的距离。 
setTranslationY(float translationY) :表示在Y轴上的平移距离,以当前控件为原点,向下为正方向,参数translationY表示移动的距离。 
setScaleX(float scaleX):在X轴上缩放,scaleX表示缩放倍数 
setScaleY(float scaleY):在Y轴上缩放,scaleY表示缩放倍数 

5.2 实现原理

image.png

5.3 自定义ObjectAnimator属性

自定义控件三部曲之动画篇(七)——ObjectAnimator基本使用

6. ViewPropertyAnimator

android.view.ViewPropertyAnimator

This class enables automatic and optimized animation of select properties on View objects. If only one or two properties on a View object are being animated, then using an ObjectAnimator is fine; the property setters called by ObjectAnimator are well equipped to do the right thing to set the property and invalidate the view appropriately.

But if several properties are animated simultaneously, or if you just want a more convenient syntax to animate a specific property, then ViewPropertyAnimator might be more well-suited to the task.

This class may provide better performance for several simultaneous animations, because it will optimize invalidate calls to take place only once for several properties instead of each animated property independently causing its own invalidation. Also, the syntax of using this class could be easier to use because the caller need only tell the View object which property to animate, and the value to animate either to or by, and this class handles the details of configuring the underlying Animator class and starting it.

This class is not constructed by the caller, but rather by the View whose properties it will animate. Calls to View.animate() will return a reference to the appropriate ViewPropertyAnimator object for that View.

6.1 简单使用

view.animate().translationX(500);  
image.png

其中带有 -By() 后缀的是增量版本的方法,例如,translationX(100) 表示用动画把 ViewtranslationX渐变为 100,而 translationXBy(100) 则表示用动画把 ViewtranslationX渐变地增加 100

6.2 设置监听

setListener(Animator.AnimatorListener listener)
setUpdateListener(ValueAnimator.AnimatorUpdateListener listener)
withStartAction(Runnable runnable)
withEndAction(Runnable runnable)

和ObjectAnimator的监听器对比

设置监听器的方法, ViewPropertyAnimatorObjectAnimator 略微不一样:

ViewPropertyAnimator 用的是 setListener()setUpdateListener() 方法,可以设置一个监听器,要移除监听器时通过 set[Update]Listener(null) 填 null 值来移除;

ObjectAnimator 则是用 addListener()addUpdateListener() 来添加一个或多个监听器,移除监听器则是通过 remove[Update]Listener() 来指定移除对象。

另外,由于 ObjectAnimator 支持使用 pause() 方法暂停,所以它还多了一个 addPauseListener() / removePauseListener() 的支持;

ViewPropertyAnimator 则独有 withStartAction()withEndAction() 方法,可以设置一次性的动画开始或结束的监听。

7. 多个属性动画

如果使用 ViewPropertyAnimator,你可以直接用连写的方式来在一个动画中同时改变多个属性:

view.animate()  
        .scaleX(1)
        .scaleY(1)
        .alpha(1);

对于 ObjectAnimator,是不能这么用的。不过你可以使用 PropertyValuesHolder 来同时在一个动画中改变多个属性。

PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("scaleX", 1);  
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleY", 1);  
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("alpha", 1);

ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, holder1, holder2, holder3)  
animator.start();  

PropertyValuesHolder 的意思从名字可以看出来,它是一个属性值的批量存放地。所以你如果有多个属性需要修改,可以把它们放在不同的 PropertyValuesHolder 中,然后使用 ofPropertyValuesHolder() 统一放进 Animator。这样你就不用为每个属性单独创建一个 Animator 分别执行了。

7.1 PropertyValuesHolder

//android.animation.PropertyValuesHolder
public static PropertyValuesHolder ofFloat(String propertyName, float... values)
public static PropertyValuesHolder ofInt(String propertyName, int... values) 
public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator,Object... values)
public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values)
//propertyName:表示ObjectAnimator需要操作的属性名。即ObjectAnimator需要通过反射查找对应属性的setProperty()函数的那个property.
//values:属性所对应的参数,同样是可变长参数,可以指定多个,还记得我们在ObjectAnimator中讲过,如果只指定了一个,那么ObjectAnimator会通过查找getProperty()方法来获得初始值。

使用

//android.animation.ObjectAnimator
public static ObjectAnimator ofPropertyValuesHolder(Object target,
            PropertyValuesHolder... values) 

7.2 Keyframe

通过前面几篇的讲解,我们知道如果要控制动画速率的变化,我们可以通过自定义插值器,也可以通过自定义Evaluator来实现。但如果真的让我们为了速率变化效果而自定义插值器或者Evaluator的话,恐怕大部分同学会局的很难,因为大部分的同学的数学知识已经还给老师了。

为了解决方便的控制动画速率的问题,谷歌为了提供了一个KeyFrame的类,KeyFrame直译过来就是关键帧。

关键帧这个概念是从动画里学来的,一个关键帧必须包含两个原素,第一时间点,第二位置。即这个关键帧是表示的是某个物体在哪个时间点应该在哪个位置上。

所以谷歌的KeyFrame也不例外,KeyFrame的生成方式为:

Keyframe kf0 = Keyframe.ofFloat(0, 0);
Keyframe kf1 = Keyframe.ofFloat(0.1f, -20f);
Keyframe kf2 = Keyframe.ofFloat(1f, 0);

上面生成了三个KeyFrame对象,其中KeyFrame的ofInt函数的声明为:

public static Keyframe ofFloat(float fraction, float value)
  • fraction:表示当前的显示进度,即从加速器中getInterpolation()函数的返回值;
  • value:表示当前应该在的位置

比如Keyframe.ofFloat(0, 0)表示动画进度为0时,动画所在的数值位置为0;Keyframe.ofFloat(0.25f, -20f)表示动画进度为25%时,动画所在的数值位置为-20;Keyframe.ofFloat(1f,0)表示动画结束时,动画所在的数值位置为0;

在理解了KeyFrame.ofFloat()的参数以后,我们来看看PropertyValuesHolder是如何使用KeyFrame对象的:

public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values)
  • propertyName:动画所要操作的属性名
  • values:Keyframe的列表,PropertyValuesHolder会根据每个Keyframe的设定,定时将指定的值输出给动画。

所以完整的KeyFrame的使用代码应该是这样的:

Keyframe frame0 = Keyframe.ofFloat(0f, 0);
Keyframe frame1 = Keyframe.ofFloat(0.1f, -20f);
Keyframe frame2 = Keyframe.ofFloat(1, 0);

PropertyValuesHolder frameHolder = PropertyValuesHolder.ofKeyframe("rotation",frame0,frame1,frame2);

Animator animator = ObjectAnimator.ofPropertyValuesHolder(mImage,frameHolder);
animator.setDuration(1000);

animator.start();

第一步:生成Keyframe对象;

第二步:利用PropertyValuesHolder.ofKeyframe()生成PropertyValuesHolder对象

第三步:ObjectAnimator.ofPropertyValuesHolder()生成对应的Animator

8. AnimatorSet联合动画

简单使用

private fun doPlaySequentiallyAnimator() {
        val tv1BgAnimator = ObjectAnimator.ofInt(tv_anim1, "BackgroundColor", -0xff01, -0x100, -0xff01)//0xffff00ff, 0xffffff00, 0xffff00ff
        val tv1TranslateY = ObjectAnimator.ofFloat(tv_anim1, "translationY", 0f, 300f, 0f)
        val tv2TranslateY = ObjectAnimator.ofFloat(tv_anim2, "translationY", 0f, 400f, 0f)

        val animatorSet = AnimatorSet()
        animatorSet.playSequentially(tv1BgAnimator, tv1TranslateY, tv2TranslateY)
        animatorSet.duration = 1000
        animatorSet.start()
    }

8.1 常用函数

public void playSequentially(Animator... items);
public void playSequentially(List<Animator> items);

public void playTogether(Animator... items);
public void playTogether(Collection<Animator> items);

8.2 AnimatorSet.Builder

// 使用 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(); 

//创建
AnimatorSet.Builder builder = animatorSet.play(animator1);
//和前面动画一起执行
public Builder with(Animator anim)
//执行前面的动画后才执行该动画
public Builder before(Animator anim)
//执行先执行这个动画再执行前面动画
public Builder after(Animator anim)
//延迟n毫秒之后执行动画
public Builder after(long delay)

8.3 AnimatorSet监听器

public void addListener(AnimatorListener listener);

public static interface AnimatorListener {
    /**
     * 当AnimatorSet开始时调用
     */
    void onAnimationStart(Animator animation);

    /**
     * 当AnimatorSet结束时调用
     */
    void onAnimationEnd(Animator animation);

    /**
     * 当AnimatorSet被取消时调用
     */
    void onAnimationCancel(Animator animation);

    /**
     * 当AnimatorSet重复时调用,由于AnimatorSet没有设置repeat的函数,所以这个方法永远不会被调用
     */
    void onAnimationRepeat(Animator animation);
}

所以我们来总结一下AnimatorSet的监听:

  1. AnimatorSet的监听函数也只是用来监听AnimatorSet的状态的,与其中的动画无关;

  2. AnimatorSet中没有设置循环的函数,所以AnimatorSet监听器中永远无法运行到onAnimationRepeat()中!

如何实现无限循环?为每个动画单独设置无限循环,setRepeatCount(ValueAnimator.INFINITE),所以在playTogether指定开始动画之后,每个动画都是无限循环的。

8.4 通用函数逐个设置与AnimatorSet设置的区别

在AnimatorSet中设置以后,会覆盖单个ObjectAnimator中的设置;

即如果AnimatorSet中没有设置,那么就以ObjectAnimator中的设置为准,如果AnimatorSet中设置了,ObjectAnimator中的设置就会无效。特殊情况除外。

//设置单次动画时长
public AnimatorSet setDuration(long duration);
//设置加速器
public void setInterpolator(TimeInterpolator interpolator)
    

//设置ObjectAnimator动画目标控件,AnimatorSet.setTarget()的作用是将动画的目标统一设置为当前控件,AnimatorSet中的所有动画都将作用在所设置的target控件上,相当于多个属性的效果
public void setTarget(Object target)
//设置延时开始动画时长,AnimatorSet的延时是仅针对性的延长AnimatorSet激活时间的,对单个动画的延时设置没有影响,即AnimatorSet真正激活延时 = AnimatorSet.startDelay + 第一个动画.startDelay
public void setStartDelay(long startDelay)

9. 联合动画的XML实现

https://developer.android.google.cn/guide/topics/resources/animation-resource#Property

在xml中对应animator总共有三个标签,分别是

<set
  android:ordering=["together" | "sequentially"]>

    <objectAnimator
        android:propertyName="string"
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType" | "colorType" | "pathType"]/>

    <animator
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType" | "colorType" | "pathType"]/>

    <set>
        ...
    </set>
</set>

9.1 animator

Performs an animation over a specified amount of time. Represents a ValueAnimator.

  • android:duration:每次动画播放的时长
  • android:valueFrom:初始动化值;取值范围为float,int和color,如果取值为float对应的值样式应该为89.0,取值为Int时,对应的值样式为:89;当取值为clolor时,对应的值样式为 #333333;
  • android:valueTo:动画结束值;取值范围同样是float,int和color这三种类型的值;
  • android:startOffset:动画激活延时;对应代码中的startDelay(long delay)函数;
  • android:repeatCount:动画重复次数
  • android:repeatMode:动画重复模式,取值为repeat和reverse;repeat表示正序重播,reverse表示倒序重播
  • android:valueType:表示参数值类型,取值为intType和floatType;与android:valueFrom、android:valueTo相对应。如果这里的取值为intType,那么android:valueFrom、android:valueTo的值也就要对应的是int类型的数值。如果这里的数值是floatType,那么android:valueFrom、android:valueTo的值也要对应的设置为float类型的值。非常注意的是,如果android:valueFrom、android:valueTo的值设置为color类型的值,那么不需要设置这个参数;
  • android:interpolator:设置加速器;有关系统加速器所对应的xml值对照表如下:
Interpolator class Resource ID
AccelerateDecelerateInterpolator @android:anim/accelerate_decelerate_interpolator
AccelerateInterpolator @android:anim/accelerate_interpolator
AnticipateInterpolator @android:anim/anticipate_interpolator
AnticipateOvershootInterpolator @android:anim/anticipate_overshoot_interpolator
BounceInterpolator @android:anim/bounce_interpolator
CycleInterpolator @android:anim/cycle_interpolator
DecelerateInterpolator @android:anim/decelerate_interpolator
LinearInterpolator @android:anim/linear_interpolator
OvershootInterpolator @android:anim/overshoot_interpolator

使用

val valueAnimator = AnimatorInflater.loadAnimator(this@ValueAnimatorTestActivity,
                        R.animator.animator_test) as ValueAnimator
                valueAnimator.addUpdateListener { animation ->
                    val offset = animation.animatedValue as Int
                    tv_anim.layout(offset, offset, tv_anim.getWidth() + offset, tv_anim.getHeight() + offset)
                }
                valueAnimator.start()

9.2 objectAnimator

  • android:propertyName:对应属性名,即ObjectAnimator所需要操作的属性名。 其它字段的意义与animator的意义与取值是一样的,下面再重新列举一下。
  • android:startOffset:动画激活延时;对应代码中的startDelay(long delay)函数;
val objectAnimator = AnimatorInflater.loadAnimator(this, R.animator.object_animator_test) as ObjectAnimator
objectAnimator.target = tv_anim
objectAnimator.start()

9.3 set

这个是AnimatorSet所对应的标签。

android:ordering:表示动画开始顺序。together表示同时开始动画,sequentially表示逐个开始动画

val set = AnimatorInflater.loadAnimator(this, R.animator.set_animator_test) as AnimatorSet
val childAnimations: ArrayList<Animator> = set.childAnimations
for (i in 0 until childAnimations.size) {
    val childAnimator: Animator? = childAnimations.get(i)
    if (i == 0) {
        childAnimator?.setTarget(tv_anim1)
    } else if (i == 1) {
        childAnimator?.setTarget(tv_anim2)
    }

}
//set.setTarget(tv_anim)
set.start()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容