文章

属性动画基础

属性动画基础

属性动画及 ValueAnimator 介绍

属性动画介绍

属性动画从 API11 开始提供,动画实现主要依靠 ValueAnimatorObjectAnimator 两个类。其实还有一个 View.animate(),这个内部原理也是属性动画,而且它已经将常用的动画封装好了,使用起来很方便
属性动画框架继承体系:

1
2
3
4
5
6
Animator
    |--ValueAnimator // 不能作用于对象,只能监听后设置
        |--ObjectAnimator // 可以设置到对象
        |--TimeAnimator
    |--AnimatorSet
        |--
  1. ValueAnimator 是属性动画中重要且最基本的类,ObjectAnimator 内部也是借助 ValueAnimator 实现的。ValueAnimator 直接子类有两个 ObjectAnimator 和 TimeAnimator。
  2. ValueAnimator 是数值从初始值逐渐变化到结束值,无法直接作用于对象,只能通过设置动画监听,获取动画过程中的过渡值,然后设置对象的属性就可以实现动画。默认插值器为 AccelerateDecelerateInterpolator,插值器只是动画执行的快慢的控制,控制具体动画过程中获取的值是通过估值器 Evaluator 来实现的。
  3. ValueAnimator 可以利用 XML 文件生成和 java 代码生成 ValueAnimator 类两种方式实现动画。

ValueAnimator

ValueAnimator 工作流程

27en2

ValueAnimator 初始化函数:

1
2
3
4
5
ValueAnimator.ofIntint  values//处理整形参数
ValueAnimator.ofFloatfloat  values//处理浮点型
ValueAnimator.ofArgb(int values) //处理颜色
ValueAnimator.ofObjectTypeEvaluator evaluator, Object values// 处理object对象,需要自定义估值器
ValueAnimator.ofPropertyValuesHolder(PropertyValuesHolder values) // 处理PropertyValuesHolder

ValueAnimator 使用过程:

1
2
3
4
第一步利用上面的函数生成ValueAnimator对象
第二步设置动画的监听 addUpdateListener(ValueAnimator.AnimatorUpdateListener listener)
第三第四步利用添加的监听函数获取当前动画的值ValueAnimator.getAnimatedValue()
第四步设置给View实现动画

属性动画一般用代码生成(因为属性值无法写死在代码中,一般需要动态获取)。

XML 方式生成属性动画

XML 可以生成属性动画三种标签:

1
2
3
animator:对应ValueAnimator
objectAnimator:对应ObjectAnimator
animatorSet:对应AnimatorSet

animator

定义 XML 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
<animator xmlns:android="http://schemas.android.com/apk/res/android"  
    android:valueFrom="0"   // 初始值
    android:valueTo="100"  // 结束值
    android:valueType="intType" // 变化值类型 :floatType & intType
    android:duration="3000" // 动画持续时间(ms),必须设置,动画才有效果
    android:startOffset ="1000" // 动画延迟开始时间(ms)
    android:fillBefore = “true” // 动画播放完后,视图是否会停留在动画开始的状态,默认为true
    android:fillAfter = “false” // 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false
    android:fillEnabled= “true” // 是否应用fillBefore值,对fillAfter值无影响,默认为true
    android:repeatMode= “restart” // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart|
    android:repeatCount = “0” // 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复
    android:interpolator = @[package:]anim/interpolator_resource // 插值器,即影响动画的播放速度
/>

API23 之后还可以利用 PropertyValuesHolderkeyframe 实现

1
2
3
4
5
6
7
8
9
10
<animator xmlns:android="http://schemas.android.com/apk/res/android"
          android:duration="1000"
          android:repeatCount="1"
          android:repeatMode="reverse">
    <propertyValuesHolder>
        <keyframe android:fraction="0" android:value="1"/>
        <keyframe android:fraction=".2" android:value=".4"/>
        <keyframe android:fraction="1" android:value="0"/>
    </propertyValuesHolder>
</animator>

objectAnimator

1
2
3
4
5
6
7
< objectAnimator
    android:duration="5000"
    android:propertyName="trimPathStart"
    android:repeatCount="infinite"
    android:repeatMode="restart"
    android:valueFrom="1"
    android:valueTo="0"/>

利用 AnimatorInflater 加载上面定义的 xml 文件,生成 Animator 实例

1
2
3
4
5
6
7
val animator: ValueAnimator = (AnimatorInflater.loadAnimator(this, R.animator.animator) as ValueAnimator)
animator.addUpdateListener { animation ->
    val value = animation?.animatedValue as Float
    LogUtil.i("hacket","addUpdateListener")
    anim_view.alpha = value
}
animator.start()

ValueAnimator 代码方式详解

属性动画最好用代码实现。ValueAnimator 无法像 ObjectAnimator 一样直接作用于对象,只能通过添加监听,获取动画过程之,然后手动设置给对象改变对象的属性。

ValueAnimator.ofInt(int … values)

values 可以有多个值,ofInt 作用是从初始值(参数中的第一个)以整数形式过渡到结束值,如果参数有多个,那就是从初始值过渡到第二个参数,然后从第二个参数过渡到第三个参数,后面以此类推。

1
2
3
4
5
6
7
8
9
10
11
btn_ValueAnimator_ofInt.setOnClickListener {
    val valueAnimator = ValueAnimator.ofInt(1, 10)
    valueAnimator.cancel()
    valueAnimator.duration = 1000
    valueAnimator.addUpdateListener { animation ->
        val data = animation?.animatedValue as Int
        Log.i("hacket", "getAnimatedValue:$data")
    }
    valueAnimator.addListener(object : AnimatorListenerAdapter(){})
    valueAnimator.start()
}

输出结果:

ofInt 函数,获取到的值都是整形。

1
2
3
4
5
6
7
8
9
10
getAnimatedValue=1
getAnimatedValue=1
getAnimatedValue=1
getAnimatedValue=2
getAnimatedValue=4
getAnimatedValue=6
getAnimatedValue=8
getAnimatedValue=9
getAnimatedValue=9
getAnimatedValue=10

ValueAnimator.ofFloat

ofFloat 和 onInt 用法相同,只是数值精度不同

1
2
3
4
5
6
7
8
9
10
11
12
valueAnimator = ValueAnimator.ofFloat(1,0.5f);
valueAnimator.setDuration(1000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float data = (float) animation.getAnimatedValue();
        Matrix matrix = new Matrix();
        matrix.setScale(data,data);
        //ImageView要支持matrix,需要设置ImageView的ScaleType为matrix
        imageView.setImageMatrix(matrix);
    }
});

ValueAnimator.ofArgb 颜色渐变

ofArgb 是 api21 提供的新方法,可以帮助我们实现颜色的渐变。

1
2
3
4
5
6
public static ValueAnimator ofArgb(int... values) {
    ValueAnimator anim = new ValueAnimator();
    anim.setIntValues(values);
    anim.setEvaluator(ArgbEvaluator.getInstance());
    return anim;
}

ofArgb 内部利用 ArgbEvaluator 估值器计算动画运行期间的过渡颜色,所以颜色过渡的算法一定在 ArgbEvaluator 的 evaluate 方法中。

示例:

1
2
3
4
5
6
7
8
9
10
valueAnimator = ValueAnimator.ofArgb(Color.RED, Color.GREEN);
valueAnimator.setDuration(3000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        int data = (int) animation.getAnimatedValue();
       imageView.setBackgroundColor(data);
       textView.setBackgroundColor(data);
    }
});

9arrq

ValueAnimator.ofObject()

其实 ValueAnimator.ofObject()的本质还是操作 值,只是是采用将 多个值 封装到一个对象里的方式 同时对多个值一起操作而已

方法:

1
ofObject(TypeEvaluator evaluator, Object values)

参数说明:

1
2
evaluator:自定义估值器
values:开始结束对象ofObject处理对象需要传入自定义估值器告诉系统如何计算动画运行过程中的值
1
2
3
4
5
6
public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) {
    ValueAnimator anim = new ValueAnimator();
    anim.setObjectValues(values);
    anim.setEvaluator(evaluator);
    return anim;
}

需要自定义估值器,内部会设置自定义的估值器。

实现的是一个从左上角到右下角的坐标过渡:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
btn_ValueAnimator_ofObjject.setOnClickListener {
    val valueAnimator = ValueAnimator.ofObject(PointTypeEvaluator(), PointF(0F, 0F), PointF(500F, 200F))
    valueAnimator.duration = 5000
    valueAnimator.addUpdateListener { animation ->
        val point = animation?.animatedValue as PointF
        anim_view3.translationX = point.x
        anim_view3.translationY = point.y
        tv_status3.text = "addUpdateListener point=$point"
        Log.i("hacket", "addUpdateListener value=$point")
    }
    valueAnimator.start()
}

class PointTypeEvaluator : TypeEvaluator<PointF> {
    override fun evaluate(fraction: Float, startValue: PointF?, endValue: PointF?): PointF {
        val startP = startValue!!
        val endP = endValue!!

        val x = startP.x + fraction * (endP.x - startP.x)
        val y = startP.y + fraction * (endP.y - startP.y)
        return PointF(x, y)
    }
}

ValueAnimator.ofPropertyValuesHolder

PropertyValuesHolder 类:
这个类持有一个属性名和对应的多个属性值,动画运行过程中会返回这种类型。ValueAnimator.ofInt(),ValueAnimator.ofFloat() 等所有的函数内部都是把值存储到 PropertyValuesHolder 中。

用途,同时改变多个属性时用这个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
val floatArrayOf = floatArrayOf(1f, 2f, 3f)
val floatArrayOf2 = floatArrayOf(4f, 5f, 6f)
val propertyValuesHolder1 = PropertyValuesHolder.ofFloat("str1", *floatArrayOf)
val propertyValuesHolder2 = PropertyValuesHolder.ofFloat("str2", *floatArrayOf2)

val valueAnimator = ValueAnimator.ofPropertyValuesHolder(propertyValuesHolder1, propertyValuesHolder2)
valueAnimator.duration = 1000
valueAnimator.interpolator = LinearInterpolator()
valueAnimator.addUpdateListener { animation ->
    val name = animation.getAnimatedValue("str1") as Float
    val age = animation.getAnimatedValue("str2") as Float
    LogUtil.i("$name $age")
}
valueAnimator.start()

Animation.getAnimatedValue("propertyName") 就可以获取到对应的值。

PropertyValuesHolder 的 ofXX 函数比较多:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ofFloat(Property<?, Float> property, float... values)
ofFloat(String propertyName, float... values)
ofInt(String propertyName, int... values)
ofInt(Property<?, Integer> property, int... values)
ofKeyframe(String propertyName, Keyframe... values)
ofKeyframe(Property property, Keyframe... values)
ofMultiFloat(String propertyName, float[][] values)
ofMultiFloat(String propertyName, TypeConverter<V, float[]> converter, TypeEvaluator<V> evaluator, V... values)
ofMultiFloat(String propertyName, Path path)
ofMultiFloat(String propertyName, TypeConverter<T, float[]> converter, TypeEvaluator<T> evaluator, Keyframe... values)
ofMultiInt(String propertyName, TypeConverter<V, int[]> converter, TypeEvaluator<V> evaluator, V... values)
ofObject(String propertyName, TypeConverter<PointF, ?> converter, Path path)
ofObject(String propertyName, TypeEvaluator evaluator, Object... values)
ofObject(Property<?, V> property, TypeConverter<T, V> converter, TypeEvaluator<T> evaluator, T... values)
// 。。。。。。

Interpolator 插值器 动画完成速度模型(时间完成度转换成动画完成度)

插值器继承结构

1
2
3
4
TimeInterpolator
    Interpolator
         BaseInterpolator
            具体的插值器类似LinearInterpolator

补间动画实现 Interpolator 接口;属性动画实现 TimeInterpolator 接口,TimeInterpolator 接口是属性动画中新增的,Interpolator 老接口继承了 TimeInterpolator,这使得所有过去的 Interpolator 实现类都可以直接在**属性动画**使用。所以现在的插值器直接实现 Interpolator 或者 TimeInterpolator 都是一样的。

1
2
3
4
5
6
public interface Interpolator extends TimeInterpolator {
}

public interface TimeInterpolator {
    float getInterpolation(float input);
}

getInterpolation 函数

TimeInterpolator 中的 getInterpolation(float input) 函数。

参数 input: input 的范围在 [0,1.0] 之间,代表当前动画所在的点(执行了多长时间,动画的进度),0 代表动画开始,1.0 代表动画完成。相当于动画执行的时间轴。

getInterpolation() 返回值: 返回值代表动画的完成程度(0 代表动画刚开始的状态,0.5 代表动画完成一半的状态,1 代表动画完成的状态),这个返回值可以超过 1.0 或者小于 0,但是超过 1 或者小于 0 没有意义。所以就代表如果 input=0 表示动画刚开始,但是如果此时返回值为 1,动画也是完成状态,View 的形态会是动画完成后的位置和状态。

系统提供的 LinearInterpolator 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {

    public LinearInterpolator() {
    }
    public LinearInterpolator(Context context, AttributeSet attrs) {
    }
    
    // 直接返回input,说明默认动画进度和动画完成状态是线性变化的。
    public float getInterpolation(float input) {
        return input;
    }

    /** @hide */
    @Override
    public long createNativeInterpolator() {
        return NativeInterpolatorFactoryHelper.createLinearInterpolator();
    }
}

系统提供的 AccelerateInterpolator 插值器

动画开始变化缓慢,后面越来越快。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {

    private final float mFactor;
    private final double mDoubleFactor;

    public AccelerateInterpolator() {
        mFactor = 1.0f;
        mDoubleFactor = 2.0;
    }

       public AccelerateInterpolator(float factor) {
        mFactor = factor;
        mDoubleFactor = 2 * mFactor;
    }
    // 重点,如果按照默认值,动画的完成状态的变化是动画进度变化(运行时间)的平方。Input=0.1 返回0.01,input=0.9 返回0.81,input=1 返回1
    public float getInterpolation(float input) {
        if (mFactor == 1.0f) {
            return input * input;
        } else {
            return (float)Math.pow(input, mDoubleFactor);
        }
    }
 }

如果 mFactor == 1.0f 时 AccelerateInterpolator 的效果曲线,X 轴为时间轴,Y 轴为动画完成状态 :
g00rl
所以自定义插值器主要是根据 getInterpolation(float input) 函数中的 input,得出对应的动画状态完成进度。

自定义 interpolator

  1. 第一步自定义类可以实现 Interpolator 或 TimeInterpolator 接口,如果要利用 BaseInterpolator,需要 api22,而且还需要实现 implements NativeInterpolatorFactory 接口。
  2. 第二步然后重写 getInterpolation 方法,在 getInterpolation 内部利用参数 input 得到对应的动画完成进度,并返回,就完成了插值器的自定义。

案例

1
2
3
4
5
6
7
8
9
10
public class InterpolationDemo implements Interpolator{
    @Override
    public float getInterpolation(float input) {
        if (input > 0 && input < 0.5){
            return input /10;
        }else{
            return input * input;
        }
    }
}
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXDelta="-300"
    android:toXDelta="500"
    android:fromYDelta="-300"
    android:toYDelta="1000"
    android:duration="3000"
    android:fillBefore="true"/>
1
2
3
InterpolationDemo interpolationDemo = new InterpolationDemo();
mTranslateAnimation.setInterpolator(interpolationDemo);
imageView.startAnimation(mTranslateAnimation);

TypeEvaluator 估值器 (动画完成度对应的具体值)

作用:设置动画 如何从初始值 过渡到 结束值 的逻辑

系统估值器

IntEvaluator

1
2
3
4
5
6
public class IntEvaluator implements TypeEvaluator<Integer> {
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}
  • 参数
    实现 TypeEvaluator,实现了 evaluate 函数,evaluate 三个参数的意义:
1
2
3
fraction:动画运行了多久,[0-1]的规范化数据,如果设置duration为1000ms,达到100ms时,fraction值为0.1,200ms为0.2。
startvalue:开始变化的值,
endValue:变化结束的值。
  • 返回值
    TypeEvaluator 的 evaluate 函数返回值为 (int)(startInt + fraction * (endValue - startInt)),
    很简单就是开始值加上动画运行的时间乘以(结束值减去开始值)。

FloatEvaluator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class FloatEvaluator implements TypeEvaluator {  
// FloatEvaluator实现了TypeEvaluator接口

// 重写evaluate()
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
// 参数说明
// fraction:表示动画完成度(根据它来计算当前动画的值)
// startValue、endValue:动画的初始值和结束值
        float startFloat = ((Number) startValue).floatValue();  
        return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);  
        // 初始值 过渡 到结束值 的算法是:
        // 1. 用结束值减去初始值,算出它们之间的差值
        // 2. 用上述差值乘以fraction系数
        // 3. 再加上初始值,就得到当前动画的值
    }  
}

PointFEvaluator api21

估值器 TypeEvaluator 和插值器 TimeInterpolator 区别?

作用

  1. 插值器(TimeInterpolator)决定 值 的变化模式(匀速、加速等)
  2. 估值器(TypeEvaluator)决定 值 的具体变化数值

在哪用?

  1. 插值器(TimeInterpolator)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ValueAnimator#animateValue Android29
void animateValue(float fraction) {
    fraction = mInterpolator.getInterpolation(fraction);
    mCurrentFraction = fraction;
    int numValues = mValues.length;
    for (int i = 0; i < numValues; ++i) {
        mValues[i].calculateValue(fraction);
    }
    if (mUpdateListeners != null) {
        int numListeners = mUpdateListeners.size();
        for (int i = 0; i < numListeners; ++i) {
            mUpdateListeners.get(i).onAnimationUpdate(this);
        }
    }
}

插值器在需要更新值时,讲当前线性消逝的 fraction 转换成你想要的的 fraction,用来定义动画的变化速率

  1. 估值器(TypeEvaluator)
    计算当前动画进度的值用到,PropertyValuesHolder#calculateValue 时调用 KeyframeSet#getValue(fraction),里面会用到 TypeEvaluator 动画更新的实际值

自定义估值器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 btn_ValueAnimator_ofObject.setOnClickListener {
    val heightAndColor1 = HeightAndColorEvaluator.HeightAndColor()
    heightAndColor1.height = 200
    heightAndColor1.color = Color.RED

    val heightAndColor2 = HeightAndColorEvaluator.HeightAndColor()
    heightAndColor2.height = 400
    heightAndColor2.color = Color.GREEN

    val valueAnimator = ValueAnimator.ofObject(HeightAndColorEvaluator(), heightAndColor1, heightAndColor2)
    valueAnimator.duration = 3000
    valueAnimator.addUpdateListener { animation ->
        val data = animation.animatedValue as HeightAndColorEvaluator.HeightAndColor
        anim_view.setBackgroundColor(data.color)

        val lp = anim_view.layoutParams
        lp.height = data.height
        anim_view.layoutParams = lp
    }
    valueAnimator.start()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public class HeightAndColorEvaluator implements TypeEvaluator<HeightAndColorEvaluator.HeightAndColor> {

    @Override
    public HeightAndColor evaluate(float fraction, HeightAndColor startValue, HeightAndColor endValue) {
        int startHeight = startValue.getHeight();
        int currHeight = (int) (startHeight + fraction * (endValue.getHeight() - startHeight));
        int currColor = getCurrRGBA(fraction, startValue.getColor(), endValue.getColor());
        HeightAndColor heightAndColor = new HeightAndColor();
        heightAndColor.setColor(currColor);
        heightAndColor.setHeight(currHeight);
        return heightAndColor;
    }

    public int getCurrRGBA(float fraction, int startValue, int endValue) {
        int startInt = startValue;
        float startA = ((startInt >> 24) & 0xff) / 255.0f;
        float startR = ((startInt >> 16) & 0xff) / 255.0f;
        float startG = ((startInt >> 8) & 0xff) / 255.0f;
        float startB = (startInt & 0xff) / 255.0f;

        int endInt = endValue;
        float endA = ((endInt >> 24) & 0xff) / 255.0f;
        float endR = ((endInt >> 16) & 0xff) / 255.0f;
        float endG = ((endInt >> 8) & 0xff) / 255.0f;
        float endB = (endInt & 0xff) / 255.0f;

        // convert from sRGB to linear
        startR = (float) Math.pow(startR, 2.2);
        startG = (float) Math.pow(startG, 2.2);
        startB = (float) Math.pow(startB, 2.2);

        endR = (float) Math.pow(endR, 2.2);
        endG = (float) Math.pow(endG, 2.2);
        endB = (float) Math.pow(endB, 2.2);

        // compute the interpolated color in linear space
        float a = startA + fraction * (endA - startA);
        float r = startR + fraction * (endR - startR);
        float g = startG + fraction * (endG - startG);
        float b = startB + fraction * (endB - startB);

        // convert back to sRGB in the [0..255] range
        a = a * 255.0f;
        r = (float) Math.pow(r, 1.0 / 2.2) * 255.0f;
        g = (float) Math.pow(g, 1.0 / 2.2) * 255.0f;
        b = (float) Math.pow(b, 1.0 / 2.2) * 255.0f;

        return Math.round(a) << 24 | Math.round(r) << 16 | Math.round(g) << 8 | Math.round(b);
    }

    public static class HeightAndColor {
        private int color;
        private int height;

        public int getColor() {
            return color;
        }

        public void setColor(int color) {
            this.color = color;
        }

        public int getHeight() {
            return height;
        }

        public void setHeight(int height) {
            this.height = height;
        }
    }
}

hhe7t

#

#

本文由作者按照 CC BY 4.0 进行授权