Android属性动画原理解析

前言

在前一篇Android视图动画原理解析中我提到了视图动画只能实现缩放,平移,旋转,透明度转换等一些简单的动画效果,对于复杂的动画则显得无能为力。正因为如此Google在Android 3.0之后引入了一个更强大的动画库,即属性动画(Property Animator)。今天我就来讲讲属性动画的一些知识点和它的实现原理。

概述

属性动画基本都是以xxxAnimator命名,它一共就包含了几个类,其中最主要的是ValueAnimator类。下面是它的类结构图:

在这里插入图片描述
Animator:属性动画的基类
ValueAnimator:属性动画最主要的实现类,明白了它的实现原理基本就弄明白了属性动画的原理。
AnimatorSet:属性动画集合类,用于管理多个属性动画的执行,类似于视图动画的AnimationSet。
ObjectAnimator,TimeAnimator:都继承于ValueAnimator,对ValueAnimator做进一步封装,方便开发人员使用。

基本用法

我们以ValueAnimator为例,讲讲它的基本用法。
ValueAnimator提供了多种构造属性动画的方式,我们每一种都实现一遍。

1.ValueAnimator.ofFloat
实现控件从0到1的缩放动画,代码如下:

ValueAnimator valueAnimator = ValueAnimator.ofFloat(0,1.0f);
        valueAnimator.setDuration(500);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //获取动画属性值
                float value = (float) animation.getAnimatedValue();
                mTvDis.setScaleX(value);
                mTvDis.setScaleY(value);
            }
        });
        valueAnimator.start();

效果图:
在这里插入图片描述
2.ValueAnimator.ofInt
实现控件向右下,向下平移100px。代码如下:

ValueAnimator valueAnimator = ValueAnimator.ofInt(0,100);
        valueAnimator.setDuration(500);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //获取动画属性值
                int value = (int) animation.getAnimatedValue();
                mTvDis.setTranslationX(value);
                mTvDis.setTranslationY(value);
            }
        });
        valueAnimator.start();

效果图:
在这里插入图片描述
3.ValueAnimator.ofObject
实现控件从坐标点(100,100)平移到坐标点(500,500)。代码如下:

Point startPoint = new Point(100,100);
        Point endPoint = new Point(500,500);
        PointEvaluator pointEvaluator = new PointEvaluator();
        ValueAnimator valueAnimator = ValueAnimator.ofObject(pointEvaluator,startPoint,endPoint);
        valueAnimator.setDuration(500);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //获取动画属性值
                Point value = (Point) animation.getAnimatedValue();
                mTvDis.setX(value.x);
                mTvDis.setY(value.y);
            }
        });
        valueAnimator.start();

估值器代码:

public class PointEvaluator implements TypeEvaluator<Point> {

    @Override
    public Point evaluate(float fraction, Point startValue, Point endValue) {
        Point point = new Point();
        point.x = (int) ((endValue.x - startValue.x) * fraction) + startValue.x;
        point.y = (int) ((endValue.x - startValue.x) * fraction) + startValue.y;
        return point;
    }
}

效果图:
在这里插入图片描述
4.ValueAnimator.ofPropertyValuesHolder
PropertyValuesHolder的构造又有三种方式:
4.1 PropertyValuesHolder.ofFloat
实现控件从0到1的缩放动画,代码如下:

PropertyValuesHolder propertyValuesHolder = PropertyValuesHolder.ofFloat("scale",0,1.0f);
        ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(propertyValuesHolder);
        valueAnimator.setDuration(500);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float scale = (float) animation.getAnimatedValue();
                mTvDis.setScaleX(scale);
                mTvDis.setScaleY(scale);
            }
        });
        valueAnimator.start();

4.2 PropertyValuesHolder.ofInt
实现控件x坐标向右平移100px,y坐标向下平移200px。代码如下:

PropertyValuesHolder txPvh = PropertyValuesHolder.ofInt("tx",0,100);
        PropertyValuesHolder tyPvh = PropertyValuesHolder.ofInt("ty",0,200);
        ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(txPvh,tyPvh);
        valueAnimator.setDuration(500);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int tx = (int) animation.getAnimatedValue("tx");
                int ty = (int) animation.getAnimatedValue("ty");
                mTvDis.setTranslationX(tx);
                mTvDis.setTranslationY(ty);
            }
        });
        valueAnimator.start();

4.3 PropertyValuesHolder.ofObject
实现控件从坐标点(100,100)平移到坐标点(500,500)。代码如下:

 Point startPoint = new Point(100,100);
        Point endPoint = new Point(500,500);
        PointEvaluator pointEvaluator = new PointEvaluator();
        PropertyValuesHolder propertyValuesHolder = PropertyValuesHolder.ofObject("point",pointEvaluator,startPoint,endPoint);

        ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(propertyValuesHolder);
        valueAnimator.setDuration(500);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //获取动画属性值
                Point value = (Point) animation.getAnimatedValue();
                mTvDis.setTranslationX(value.x);
                mTvDis.setTranslationY(value.y);
            }
        });
        valueAnimator.start();

4.4 PropertyValuesHolder.ofKeyframe
Keyframe的构造又有三种方式:
4.4.1 Keyframe.ofFloat
实现控件从0到1的缩放动画,代码如下:

 Keyframe scale1 = Keyframe.ofFloat(0,0);
        Keyframe scale2 = Keyframe.ofFloat(0.5f,0.5f);
        Keyframe scale3 = Keyframe.ofFloat(1.0f,1.0f);
        PropertyValuesHolder propertyValuesHolder = PropertyValuesHolder.ofKeyframe("scale",scale1,scale2,scale3);
        ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(propertyValuesHolder);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float scale = (float) animation.getAnimatedValue("scale");
                mTvDis.setScaleX(scale);
                mTvDis.setScaleY(scale);
            }
        });
        valueAnimator.start();

4.4.2 Keyframe.ofInt
实现控件x坐标向右平移100px,y坐标向下平移200px。代码如下:

Keyframe tx1 = Keyframe.ofInt(0,0);
        Keyframe tx2 = Keyframe.ofInt(0.5f,50);
        Keyframe tx3 = Keyframe.ofInt(1,100);
        PropertyValuesHolder txPvh = PropertyValuesHolder.ofKeyframe("tx",tx1,tx2,tx3);

        Keyframe ty1 = Keyframe.ofInt(0,0);
        Keyframe ty2 = Keyframe.ofInt(0.5f,100);
        Keyframe ty3 = Keyframe.ofInt(1,200);
        PropertyValuesHolder tyPvh = PropertyValuesHolder.ofKeyframe("ty",ty1,ty2,ty3);

        ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(txPvh,tyPvh);
        valueAnimator.setDuration(500);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int tx = (int) animation.getAnimatedValue("tx");
                int ty = (int) animation.getAnimatedValue("ty");
                mTvDis.setTranslationX(tx);
                mTvDis.setTranslationY(ty);
            }
        });
        valueAnimator.start();

4.4.3 Keyframe.ofObject
实现控件从坐标点(100,100)平移到坐标点(500,500)。代码如下:

 Point startPoint = new Point(100,100);
        Point endPoint = new Point(500,500);
        //估值器
        PointEvaluator pointEvaluator = new PointEvaluator();

        Keyframe p1 = Keyframe.ofObject(0,startPoint);
        Keyframe p2 = Keyframe.ofObject(1,endPoint);
        PropertyValuesHolder propertyValuesHolder = PropertyValuesHolder.ofKeyframe("point",p1,p2);
        //属性值对象是object类型需要手动设置估值器,不然会出现npe
        propertyValuesHolder.setEvaluator(pointEvaluator);

        ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(propertyValuesHolder);
        valueAnimator.setDuration(500);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //获取动画属性值
                Point value = (Point) animation.getAnimatedValue();
                mTvDis.setTranslationX(value.x);
                mTvDis.setTranslationY(value.y);
            }
        });
        valueAnimator.start();

原来实现一个属性动画可以通过这么多种方式,我猜应该很多人都只用过第一种方式。其实不管用哪种方式实现,在属性动画内部最终都会转成通过Keyframe实现,只不过为了让开发者不需要理解太多复杂原理,在上层多封装了一些方法,方便开发者调用,满足日常基本需求。
如果想实现一些更复杂的效果,或者提高动画性能,那么理解属性动画原理还是很有必要的。比如要实现一个控件的宽从0放大到1倍,同时高从0放大到2倍的动画,如果你只会第1种方式,那么你可能会这么写:

ValueAnimator scaleX = ValueAnimator.ofFloat(0,1.0f);
        scaleX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //获取动画属性值
                float value = (float) animation.getAnimatedValue();
                mTvDis.setScaleX(value);
            }
        });

        ValueAnimator scaleY = ValueAnimator.ofFloat(0,2.0f);
        scaleY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //获取动画属性值
                float value = (float) animation.getAnimatedValue();
                mTvDis.setScaleY(value);
            }
        });

        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(scaleX,scaleY);
        animatorSet.start();

通过构建两个ValueAnimator ,一个改变控件宽度,一个改变控件高度,然后通过AnimatorSet让这两个动画一起执行,来达到效果。
而如果你对属性动画了解更多的话,你应该知道一个属性动画是可以同时改变多个属性值的,这样我们其实只需要一个ValueAnimator 就能达到目的。代码如下:

 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX",0,1.0f);
        PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY",0,2.0f);
        ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(scaleX,scaleY);
        valueAnimator.setDuration(500);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float scaleX = (float) animation.getAnimatedValue("scaleX");
                float scaleY = (float) animation.getAnimatedValue("scaleY");
                mTvDis.setScaleX(scaleX);
                mTvDis.setScaleY(scaleY);
            }
        });
        valueAnimator.start();

这里总结一点:
PropertyValuesHolder是属性动画属性值的一个处理类,一个PropertyValuesHolder负责处理属性动画的一个属性值,而属性动画是可以有多个属性值的,只要在构造ValueAnimator时传入多个PropertyValuesHolder就可以。
通过ValueAnimator.ofFloat,ValueAnimator.ofInt,ValueAnimator.ofObject构造的属性动画,内部都会为其创建一个对应的PropertyValuesHolder,因为只有一个PropertyValuesHolder所以构造的属性动画只能改变一个属性值,如果你想在一个属性动画里同时改变多个属性值,那么就需要用ValueAnimator.ofPropertyValuesHolder来构造属性动画,通过参数传入多个PropertyValuesHolder即可。

实现原理

前面已经介绍了属性动画的一些基本使用方式,那么现在就开始讲讲属性动画的实现原理,我会结合源码来做分析,方便大家理解。

如果要用一句话说明属性动画的原理,那么可以概括为属性动画是通过修改控件的属性值实现的动画

在讲解属性动画原理之前,我们还需要先弄明白几个概念。

1.fraction

fraction在视图动画里也有出现过,它表示的是动画执行的时间占比。计算公式如下:

fraction = (currentTime - startTime) / duration

看公式我们很容易就明白,它是以当前时间减去动画开始时间再除以动画需要执行的总时间。它的取值范围是0到1之间,所以fraction可以表示动画执行过程中的某个时间节点。 明白这个概念对后面理解Keyframe会有帮助。

fraction有什么作用呢?
我们知道动画的原理其实就是在某个时间段内不断地改变作用对象的状态实现的。那么动画在不同的时间节点都会对应一个状态值。因为fraction可以表示时间节点,所以我们可以通过fraction来控制动画的变化过程。比如我要实现一个控件在某段时间内从1倍缩放到3倍大小,那么可以这样写

float scale = 1 + (3 - 1)  * fraction;
viwe.setScaleX(scale);
view.setScaleY(scale);

对应的属性值计算公式是这样的

value = startValue + (endValue - startValue) * fraction;

2.差值器

前面已经讲过可以通过fraction来控制动画的变化过程,但是因为fraction表示的是时间节点,所以它是线性变化的,随着时间推移均匀递增(因为时间不可能一会儿走的快,一会儿走的慢)。如果直接通过fraction来改变动画,那么动画的变化过程都是匀速的。在实际开发中,动画的变化过程通常都不是匀速的,这都是依赖于差值器的实现。

差值器的作用,简单讲它可以通过改变fraction的变化规律,控制动画的变化速率,但是它的作用还不仅仅如此,比如还可以用它实现弹性动画效果。

通过差值器可以将fraction从线性变化转成非线性变化。你可以这样理解,差值器就像是一台时间机器,它可以扭曲时间点,使得时间不再是均匀变化,而是跳跃变化,甚至可以前后变化(正常时间是不可逆的,但是差值器可以让时间可逆,弹性动画就是让fraction围绕某个点上下震荡变化实现的)。

差值器的接口是TimeInterpolator,它有一个子接口Interpolator,所有差值器都直接或间接实现了该接口。
TimeInterpolator只定义了一个方法,因为我们是用百分比来表示时间节点的,所以它的参数是float类型的值。代码如下:

public interface TimeInterpolator {
	//通过此方法对fraction做转换
    float getInterpolation(float input);
}

其中参数input通常都是fraction,子类通过实现该方法来改变fraction的值。正常差值器会把改变后的值继续赋值给fraction,因为对于上层来说它所理解的fraction还是不变的,仍然认为它是一个时间节点,只不过此时的时间节点可能是跳跃的,而不是顺序的。开发者仍然通过下面的代码实现一个控件在某段时间内从1倍缩放到3倍大的功能,而不需要理解fraction到底经历了什么改变。

float scale = 1 + (3 - 1)  * fraction;
viwe.setScaleX(scale);
view.setScaleY(scale);

我们来看看AccelerateDecelerateInterpolator对该方法的实现:

    public float getInterpolation(float input) {
        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
    }

AccelerateDecelerateInterpolator通过一定的数学运算改变了fraction的值,从而实现了动画开始和结束时慢,中间快的效果。

3.估值器

在属性动画中引入了估值器(TypeEvaluator)的概念。很多人可能都不大理解估值器到底是什么,其实估值器和差值器的作用差不多,只不过差值器控制的是时间节点(fraction)的变化规律,而估值器控制的是属性值的变化规律,说完整点就是它控制着属性值怎么随着fraction的改变而改变。因为动画的变化过程最终是由属性值的变化规律体现,所以估值器其实也是可以改变动画的变化速率,甚至实现弹性动画效果,只要你通过合适的算法去改变属性值即可。

为什么需要有估值器?
因为在属性动画中我们可以改变任意类型的值,那么怎么改变就需要专门有一个类来负责。比如,你要定义一个控件从某个坐标点移动到另一个坐标点,这时候我们可以定义一个属性值类型为PointF的属性动画,通过不断改变PointF的值,使控件从一点移动到另一点。但是在这个过程中它的移动轨迹可以有很多,比如直线运动,曲线运动,或者圆形运动等等,不同的运行轨迹对应的坐标点算法是不一样的,这个只能通过估值器实现。
所以估值器有一个很重要的作用,它可以控制动画的运行轨迹。

所有的估值器都需要实现TypeEvaluator接口。它的代码如下:

public interface TypeEvaluator<T> {
	//通过此方法定义动画运行轨迹
    public T evaluate(float fraction, T startValue, T endValue);
}

它同样也是只有一个方法,不过相比于差值器,它有几点不同,
1.参数类型是泛型,这样就可以改变任意类型的属性值。
2.它的参数多了startValue和endValue,因为你要描述动画从起始点到终点的运行轨迹,那么就必须知道它的起始和终点值。这里需要注意的是,引入了Keyframe后,startValue和endValue表示的是两个Keyframe之间的值,前一个Keyframe的value作为起始值,后一个Keyframe的value作为终点值,而不是整个动画的起始终点值。

4.Keyframe

如果你理解了fraction,差值器和估值器的概念,那么Keyframe就不会那么难理解了。Keyframe的中文翻译就是关键帧,既然是帧那么它表示的是动画在某个时间节点的状态,所以Keyframe至少需要有两个成员变量,一个是fraction表示时间节点,一个是value表示状态值。大家应该还记得Keyframe是怎么构造的吧,比如下面代码:

		Keyframe scale1 = Keyframe.ofFloat(0,0);
        Keyframe scale2 = Keyframe.ofFloat(0.5f,0.5f);
        Keyframe scale3 = Keyframe.ofFloat(1.0f,1.0f);

其中ofFloat的第一个参数表示的是fraction,第二个参数表示的就是fraction对应的value。这段代码的意思是,我要构造一个值类型是Float的属性动画,并且要求它在动画执行开始value为0,动画执行一半时value变成0.5,动画执行完毕value变成1。其实它定义了动画执行过程的三种状态,这三种状态我们就可以通过三个Keyframe来描述。
Keyframe还可以定义自己的Interpolator,用来定义从上一个Keyframe到下一个Keyframe之间的动画变化规则,所以有了关键帧后,我们可以把一个大动画拆分成多个小动画,每个小动画可以定义自己的变化规则。

前面说了PropertyValuesHolder负责属性值在整个动画变化过程的处理,所以直接跟Keyframe产生关系的是PropertyValuesHolder,而且一个PropertyValuesHolder会对应一个Keyframe集合,因为一个动画至少存在两个状态,开始状态和结束状态,所以至少会有两个Keyframe,当然我们也可以通过增加Keyframe来描述中间的状态。

为了描述PropertyValuesHolder和Keyframe一对多的关系,Android提供了KeyframeSet类来管理多个Keyframe,每个KeyframeSet都会实现Keyframes的接口,PropertyValuesHolder直接依赖于Keyframes,这样做符合依赖倒置原则。下面是整个ValueAnimator的依赖关系类图:

在这里插入图片描述
之所以画类图,是因为类图可以帮助我们理清依赖关系,可以让我们更加明白代码的执行流程,有利于对源码的理解。

5.源码分析

到了这里我们要开始阅读源码了,如果对源码不感兴趣的可以直接跳过。
关于属性动画,我们主要研究它的两个机制,一个是定时刷新机制,一个是属性值更新机制

5.1 定时刷新机制

动画其实跟视频一样需要在一段时间内不断更新帧来实现动画效果。在前一篇关于视图动画原理的分析中我们知道视图动画是通过不断通知视图重绘来实现定时刷新的,那么属性动画又是怎么来实现这一机制的呢?

我们先给结论再分析源码:
属性动画是通过在动画结束之前不断向Choreographer发送事件,通知它更新帧,Choreographer默认每隔10ms更新一帧,那么动画就会每隔10ms更新一次属性值,根据变化的属性值来更新作用对象这样就形成了动画效果。

如果想改变属性动画的刷新频率,需要改变Choreographer的帧更新频率,通过以下方法实现,但是该方法并不能保证在所有手机都能奏效。

long frameDelay = 50;
ValueAnimator.setFrameDelay(frameDelay );

以ValueAnimator为例分析源码,讲解看代码注释:
1.调用ValueAnimator的start方法。

private void start(boolean playBackwards) {
    .....省略部分代码.....
    
    //通过该方法添加动画刷新回调
    addAnimationCallback(0);

    .....省略部分代码.....
}

2.点进入看看addAnimationCallback的实现

private void addAnimationCallback(long delay) {
    if (!mSelfPulse) {
        return;
    }
    getAnimationHandler().addAnimationFrameCallback(this, delay);
}
public AnimationHandler getAnimationHandler() {
    return AnimationHandler.getInstance();
}

这里会实例化一个AnimationHandler对象,并传递一个AnimationFrameCallback给它,因为ValueAnimator实现了AnimationFrameCallback接口,所以就是把自己的引用传给它。
我们点进去看AnimationHandler的源码,发现它是一个从ThreadLocal获取的对象,所以它是一个线程隔绝的单例对象,在同一个线程中只能存在一个对象,也就是同一个线程的所有属性动画的刷新都由它管理,刷新频率是一样的。

public final static ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>();
private boolean mListDirty = false;

public static AnimationHandler getInstance() {
    if (sAnimatorHandler.get() == null) {
        sAnimatorHandler.set(new AnimationHandler());
    }
    return sAnimatorHandler.get();
}

3.我们继续看AnimationHandler的addAnimationFrameCallback方法。

public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
    if (mAnimationCallbacks.size() == 0) {
        //调用该方法最终会向Choreographer发送一个事件,通知它更新帧,
        //Choreographer每次更新帧就会通知mFrameCallback
        getProvider().postFrameCallback(mFrameCallback);
    }
    
    //将动画刷新回调添加到mAnimationCallbacks,根据mAnimationCallbacks是否包含回调判断动画是否已经执行结束
    if (!mAnimationCallbacks.contains(callback)) {
        mAnimationCallbacks.add(callback);
    }

    if (delay > 0) {
        mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
    }
}
private AnimationFrameCallbackProvider getProvider() {
    if (mProvider == null) {
        mProvider = new MyFrameCallbackProvider();
    }
    return mProvider;
}
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {

    final Choreographer mChoreographer = Choreographer.getInstance();

    @Override
    public void postFrameCallback(Choreographer.FrameCallback callback) {
        //通知更新下一帧
        mChoreographer.postFrameCallback(callback);
    }

    @Override
    public void postCommitCallback(Runnable runnable) {
        mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, runnable, null);
    }

    @Override
    public long getFrameTime() {
        return mChoreographer.getFrameTime();
    }

    @Override
    public long getFrameDelay() {
        return Choreographer.getFrameDelay();
    }

    @Override
    public void setFrameDelay(long delay) {
        Choreographer.setFrameDelay(delay);
    }
}

在mFrameCallback接收Choreographer的更新回调。

private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
        //接收Choreographer的回调,Choreographer每更新一帧就回调到该方法
        doAnimationFrame(getProvider().getFrameTime());
        if (mAnimationCallbacks.size() > 0) {
            //如果动画没结束,那么就继续向Choreographer发送事件,通知更新下一帧
            //因为Choreographer默认每10毫秒更新一帧,所以动画默认每10毫秒刷新一次
            getProvider().postFrameCallback(this);
        }
    }
};
private void doAnimationFrame(long frameTime) {
    long currentTime = SystemClock.uptimeMillis();
    final int size = mAnimationCallbacks.size();
    for (int i = 0; i < size; i++) {
        final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
        if (callback == null) {
            continue;
        }
        if (isCallbackDue(callback, currentTime)) {
            //通知动画刷新
            callback.doAnimationFrame(frameTime);
            if (mCommitCallbacks.contains(callback)) {
                getProvider().postCommitCallback(new Runnable() {
                    @Override
                    public void run() {
                        commitAnimationFrame(callback, getProvider().getFrameTime());
                    }
                });
            }
        }
    }
    cleanUpList();
}

通过以上源码我们知道,属性动画的刷新机制依赖于Choreographer,Choreographer每更新一帧就通知到AnimationHandler的mFrameCallback回调,然后执行doAnimationFrame方法通知ValueAnimator刷新属性值,然后AnimationHandler会继续判断是否动画已经执行结束,如果还没执行结束就继续向Choreographer发送一个事件通知它更新下一帧,这样就重复了前面的过程直到动画执行结束。

我们已经知道Choreographer每更新一帧时都会回调到ValueAnimator的doAnimationFrame方法,并且把此刻时间作为参数传过来,最终会执行到animateBasedOnTime方法把时间转成fraction。

boolean animateBasedOnTime(long currentTime) {
    boolean done = false;
    if (mRunning) {
        final long scaledDuration = getScaledDuration();
        //将时间转成fraction
        final float fraction = scaledDuration > 0 ?
                (float)(currentTime - mStartTime) / scaledDuration : 1f;
        final float lastFraction = mOverallFraction;
        final boolean newIteration = (int) fraction > (int) lastFraction;
        final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
                (mRepeatCount != INFINITE);
        if (scaledDuration == 0) {
            // 0 duration animator, ignore the repeat count and skip to the end
            done = true;
        } else if (newIteration && !lastIterationFinished) {
            // Time to repeat
            if (mListeners != null) {
                int numListeners = mListeners.size();
                for (int i = 0; i < numListeners; ++i) {
                    mListeners.get(i).onAnimationRepeat(this);
                }
            }
        } else if (lastIterationFinished) {
            done = true;
        }
        mOverallFraction = clampFraction(fraction);
        float currentIterationFraction = getCurrentIterationFraction(
                mOverallFraction, mReversing);
        //将fraction传给animateValue方法,在这里真正去改变动画属性值        
        animateValue(currentIterationFraction);
    }
    return done;
}
5.2 属性值更新机制

前面已经说了属性动画的帧更新机制,每次帧更新时最终都会调用到ValueAnimator的animateValue方法,在这里开始真正去改变动画属性值。

void animateValue(float fraction) {
    //根据差值器将时间fraction转成特定fraction
    //时间fraction是线性的,而根据差值器转换后的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);
        }
    }
}

其中第9行代码,mValues[i].calculateValue(fraction)是改变属性值的关键代码,其中mValues[i]是动画的属性值,因为一个属性动画可以同时改变多个属性,所以它是一个数组,它的类型是PropertyValuesHolder,也就是属性动画的属性值封装在一个叫做PropertyValuesHolder的类里。

下面是简化后的PropertyValuesHolder代码。

public class PropertyValuesHolder implements Cloneable {
    //属性名,可以作为key值区分该属性
    String mPropertyName;

    //属性值的类型,比如float,int,甚至是一个Object
    Class mValueType;

     //关键帧集合,关键帧用于表示动画变化过程中在一些关键时间点的帧信息
     //一个动画在变化过程中存在多个关键帧,所以这里是个集合
    //关键帧的个数可以由用户自己定义,不过至少要有两个关键帧
    Keyframes mKeyframes = null;

    //int属性值估值器
    private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
    //float属性值估值器
    private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();
    //自定义属性值的估值器
    private TypeEvaluator mEvaluator;

    private PropertyValuesHolder(String propertyName) {
        mPropertyName = propertyName;
    }

    //根据int值构造PropertyValuesHolder 
    public static PropertyValuesHolder ofInt(String propertyName, int... values) {
        return new IntPropertyValuesHolder(propertyName, values);
    }

    //根据float值构造PropertyValuesHolder
    public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
        return new FloatPropertyValuesHolder(propertyName, values);
    }

    //构造Object属性值类型的PropertyValuesHolder 
    public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator,
            Object... values) {
        PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
        pvh.setObjectValues(values);
        pvh.setEvaluator(evaluator);
        return pvh;
    }

    //根据关键帧构造PropertyValuesHolder
    public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values) {
        KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
        return ofKeyframes(propertyName, keyframeSet);
    }

   //设置int类型的属性值
    public void setIntValues(int... values) {
        mValueType = int.class;
        mKeyframes = KeyframeSet.ofInt(values);
    }

    //设置float类型的属性值
    public void setFloatValues(float... values) {
        mValueType = float.class;
        mKeyframes = KeyframeSet.ofFloat(values);
    }

    //设置关键帧
    public void setKeyframes(Keyframe... values) {
        int numKeyframes = values.length;
        Keyframe keyframes[] = new Keyframe[Math.max(numKeyframes,2)];
        mValueType = ((Keyframe)values[0]).getType();
        for (int i = 0; i < numKeyframes; ++i) {
            keyframes[i] = (Keyframe)values[i];
        }
        mKeyframes = new KeyframeSet(keyframes);
    }

    //设置Object类型的属性值
    public void setObjectValues(Object... values) {
        mValueType = values[0].getClass();
        mKeyframes = KeyframeSet.ofObject(values);
        if (mEvaluator != null) {
            //将估值器传给mKeyframes
            mKeyframes.setEvaluator(mEvaluator);
        }
    }

    //设置估值器
    public void setEvaluator(TypeEvaluator evaluator) {
        mEvaluator = evaluator;
        mKeyframes.setEvaluator(evaluator);
    }

    //根据fraction计算属性值
    void calculateValue(float fraction) {
        //调用Keyframes的getValue方法去计算属性值
        Object value = mKeyframes.getValue(fraction);
        //将计算后的值赋值给mAnimatedValue,在赋值前可以通过mConverter做值转换
        mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
    }
     
     //获取动画属性值
    Object getAnimatedValue() {
        return mAnimatedValue;
    }

    public void setPropertyName(String propertyName) {
        mPropertyName = propertyName;
    }

    public String getPropertyName() {
        return mPropertyName;
    }
    
    /**
     * @hide
     */
    public Class getValueType() {
        return mValueType;
    }
    }

我们重点看下calculateValue方法:

	//根据fraction计算属性值
    void calculateValue(float fraction) {
        //调用Keyframes的getValue方法去计算属性值
        Object value = mKeyframes.getValue(fraction);
        //将计算后的值赋值给mAnimatedValue,在赋值前可以通过mConverter做值转换
        mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
    }

它会通过Keyframes去计算属性值,并且赋值给mAnimatedValue ,所以要拿到属性值需要通过PropertyValuesHolder的getAnimatedValue方法获取。

我们前面已经说了Keyframs只是一个接口,它的实现是KeyframeSet,所以我们直接看看KeyframeSet的实现,同样省略了部分源码。

/**
 * This class holds a collection of Keyframe objects and is called by ValueAnimator to calculate
 * values between those keyframes for a given animation. The class internal to the animation
 * package because it is an implementation detail of how Keyframes are stored and used.
 * @hide
 */
public class KeyframeSet implements Keyframes {
     //关键帧个数
     int mNumKeyframes;
    //第一帧
    Keyframe mFirstKeyframe;
    //最后一帧
    Keyframe mLastKeyframe;
    TimeInterpolator mInterpolator; // only used in the 2-keyframe case
    //所有关键帧集合
    List<Keyframe> mKeyframes; // only used when there are not 2 keyframes
    //估值器
    TypeEvaluator mEvaluator;

    public KeyframeSet(Keyframe... keyframes) {
        mNumKeyframes = keyframes.length;
        // immutable list
        mKeyframes = Arrays.asList(keyframes);
        mFirstKeyframe = keyframes[0];
        mLastKeyframe = keyframes[mNumKeyframes - 1];
        mInterpolator = mLastKeyframe.getInterpolator();
    }

    //获取关键帧集合
    public List<Keyframe> getKeyframes() {
        return mKeyframes;
    }

    //构造属性值是int类型的KeyframeSet
    public static KeyframeSet ofInt(int... values) {
        int numKeyframes = values.length;
        IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
        if (numKeyframes == 1) {
            //如果用户只传了一个值过来,那么会默认构建两个关键帧
            //第一个关键帧fraction为0,value也为0,第2个关键帧fraction为1,value为用户传过来的值
            keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
            keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
        } else {
            //如果用户传过来的值大于等于2,那么第一个关键帧就是fraction为0,value为values[0]
            keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
            for (int i = 1; i < numKeyframes; ++i) {
                //从第2值开始,每个关键帧的fraction等于(i / (numKeyframes - 1))
                keyframes[i] =
                        (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
            }
        }
        return new IntKeyframeSet(keyframes);
    }

    //构造属性值是float类型的KeyframeSet
    public static KeyframeSet ofFloat(float... values) {
        boolean badValue = false;
        int numKeyframes = values.length;
        FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
        if (numKeyframes == 1) {
            //如果用户只传了一个值过来,那么会默认构建两个关键帧
            //第一个关键帧fraction为0,value也为0,第2个关键帧fraction为1,value为用户传过来的值
            keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
            keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
            if (Float.isNaN(values[0])) {
                badValue = true;
            }
        } else {
            keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
            for (int i = 1; i < numKeyframes; ++i) {
                //从第2值开始,每个关键帧的fraction等于(i / (numKeyframes - 1))
                keyframes[i] =
                        (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
                if (Float.isNaN(values[i])) {
                    badValue = true;
                }
            }
        }
        if (badValue) {
            Log.w("Animator", "Bad value (NaN) in float animator");
        }
        return new FloatKeyframeSet(keyframes);
    }

    //根据Keyframe数组构造KeyframeSet
    public static KeyframeSet ofKeyframe(Keyframe... keyframes) {
        // if all keyframes of same primitive type, create the appropriate KeyframeSet
        int numKeyframes = keyframes.length;
        boolean hasFloat = false;
        boolean hasInt = false;
        boolean hasOther = false;
        for (int i = 0; i < numKeyframes; ++i) {
            if (keyframes[i] instanceof FloatKeyframe) {
                hasFloat = true;
            } else if (keyframes[i] instanceof IntKeyframe) {
                hasInt = true;
            } else {
                hasOther = true;
            }
        }
        if (hasFloat && !hasInt && !hasOther) {
            FloatKeyframe floatKeyframes[] = new FloatKeyframe[numKeyframes];
            for (int i = 0; i < numKeyframes; ++i) {
                floatKeyframes[i] = (FloatKeyframe) keyframes[i];
            }
            return new FloatKeyframeSet(floatKeyframes);
        } else if (hasInt && !hasFloat && !hasOther) {
            IntKeyframe intKeyframes[] = new IntKeyframe[numKeyframes];
            for (int i = 0; i < numKeyframes; ++i) {
                intKeyframes[i] = (IntKeyframe) keyframes[i];
            }
            return new IntKeyframeSet(intKeyframes);
        } else {
            return new KeyframeSet(keyframes);
        }
    }

    //构造属性值是Object类型的KeyframeSet,因为是Object所以可以传入任意类型的属性值
    public static KeyframeSet ofObject(Object... values) {
        int numKeyframes = values.length;
        ObjectKeyframe keyframes[] = new ObjectKeyframe[Math.max(numKeyframes,2)];
        if (numKeyframes == 1) {
            //如果用户只传了一个值过来,那么会默认构建两个关键帧
            //第一个关键帧fraction为0,value也为0,第2个关键帧fraction为1,value为用户传过来的值
            keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f);
            keyframes[1] = (ObjectKeyframe) Keyframe.ofObject(1f, values[0]);
        } else {
            keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f, values[0]);
            for (int i = 1; i < numKeyframes; ++i) {
                //从第2值开始,每个关键帧的fraction等于(i / (numKeyframes - 1))
                keyframes[i] = (ObjectKeyframe) Keyframe.ofObject((float) i / (numKeyframes - 1), values[i]);
            }
        }
        return new KeyframeSet(keyframes);
    }

    //设置估值器
    public void setEvaluator(TypeEvaluator evaluator) {
        mEvaluator = evaluator;
    }

    //获取关键帧属性值类型
    @Override
    public Class getType() {
        return mFirstKeyframe.getType();
    }

    //根据fraction获取属性值
    public Object getValue(float fraction) {
        // Special-case optimization for the common case of only two keyframes
        if (mNumKeyframes == 2) {
            if (mInterpolator != null) {
                fraction = mInterpolator.getInterpolation(fraction);
            }
            //注意这里直接调用mEvaluator方法,所以如果属性值类型是Object必须传TypeEvaluator,不然会出现npe
            return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(),
                    mLastKeyframe.getValue());
        }
        if (fraction <= 0f) {
            final Keyframe nextKeyframe = mKeyframes.get(1);
            final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
            if (interpolator != null) {
                fraction = interpolator.getInterpolation(fraction);
            }
            final float prevFraction = mFirstKeyframe.getFraction();
            float intervalFraction = (fraction - prevFraction) /
                (nextKeyframe.getFraction() - prevFraction);
            return mEvaluator.evaluate(intervalFraction, mFirstKeyframe.getValue(),
                    nextKeyframe.getValue());
        } else if (fraction >= 1f) {
            final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2);
            final TimeInterpolator interpolator = mLastKeyframe.getInterpolator();
            if (interpolator != null) {
                fraction = interpolator.getInterpolation(fraction);
            }
            final float prevFraction = prevKeyframe.getFraction();
            float intervalFraction = (fraction - prevFraction) /
                (mLastKeyframe.getFraction() - prevFraction);
            return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
                    mLastKeyframe.getValue());
        }
        //第一个关键帧
        Keyframe prevKeyframe = mFirstKeyframe;
        for (int i = 1; i < mNumKeyframes; ++i) {
            //下一个关键帧
            Keyframe nextKeyframe = mKeyframes.get(i);
            //判断fraction是否在下一个关键帧的fraction之内
            if (fraction < nextKeyframe.getFraction()) {
                final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
                final float prevFraction = prevKeyframe.getFraction();
                //计算前一个关键帧到下一个关键帧的fraction,命名为intervalFraction
                float intervalFraction = (fraction - prevFraction) /
                    (nextKeyframe.getFraction() - prevFraction);
                // Apply interpolator on the proportional duration.
                if (interpolator != null) {
                    //通过差值器改变intervalFraction
                    intervalFraction = interpolator.getInterpolation(intervalFraction);
                }
                //将intervalFraction,前一个关键帧的value,下一个关键帧的value作为参数传给估值器
                return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
                        nextKeyframe.getValue());
            }
            prevKeyframe = nextKeyframe;
        }
        // shouldn't reach here
        return mLastKeyframe.getValue();
    }
}

我们重点关注getValue方法,在这里计算属性值。通过源码我们知道它会先根据fraction获取到对于的Keyframe,然后重新计算一个intervalFraction ,intervalFraction表示的是从上一个Keyframe到当前Keyframe之间的时间节点,然后取出该Keyframe对应的差值器改变intervalFraction 的值,最后再把intervalFraction,上一个Keyframe的值和当前Keyframe的值作为参数传给估值器处理。

也把keyframes和Keyframe的源码贴下,讲解看注释:

Keyframes:

public interface Keyframes extends Cloneable {
    //设置估值器
    void setEvaluator(TypeEvaluator evaluator);
    
    //关键帧的属性值类型
    Class getType();

    //根据fraction获取属性值
    Object getValue(float fraction);

    //获取所有关键帧
    List<Keyframe> getKeyframes();

    //int类型的关键帧集合
    public interface IntKeyframes extends Keyframes {
        
        //根据fraction获取int类型的属性值
        int getIntValue(float fraction);
    }

    //float类型的关键帧集合
    public interface FloatKeyframes extends Keyframes {

        //根据fraction获取float类型的属性值
        float getFloatValue(float fraction);
    }
}

Keyframe:

public abstract class Keyframe implements Cloneable {

     //可以理解成一个时间点,表示此刻动画已经执行到了哪里
     //mFraction的取值在0到1之间,0表示动画刚开始执行,1表示动画执行完毕,0.5则表示动画执行到一半
     float mFraction;
    
     //属性值类型
     Class mValueType;

     //差值器,每个关键帧都可以有自己的差值器
    private TimeInterpolator mInterpolator = null;

    //构造int类型的Keyframe,fraction表示动画执行过程中某一刻的时间点,
    //而value则表示在fraction时间点,对应的属性值
    public static Keyframe ofInt(float fraction, int value) {
        return new IntKeyframe(fraction, value);
    }

    //构造int类型的Keyframe
    public static Keyframe ofInt(float fraction) {
        return new IntKeyframe(fraction);
    }

    //构造float类型的Keyframe,value表示fraction对应的属性值
    public static Keyframe ofFloat(float fraction, float value) {
        return new FloatKeyframe(fraction, value);
    }

    //构造float类型的Keyframe
    public static Keyframe ofFloat(float fraction) {
        return new FloatKeyframe(fraction);
    }

    //构造自定义对象类型的Keyframe
    public static Keyframe ofObject(float fraction, Object value) {
        return new ObjectKeyframe(fraction, value);
    }

    //构造自定义对象类型的Keyframe
    public static Keyframe ofObject(float fraction) {
        return new ObjectKeyframe(fraction, null);
    }

    //获取属性值,抽象方法由子类实现
    public abstract Object getValue();

    //设置属性值,抽象方法由子类实现
    public abstract void setValue(Object value);

    //得到时间点
    public float getFraction() {
        return mFraction;
    }

    //设置时间点
    public void setFraction(float fraction) {
        mFraction = fraction;
    }

    public TimeInterpolator getInterpolator() {
        return mInterpolator;
    }

    public void setInterpolator(TimeInterpolator interpolator) {
        mInterpolator = interpolator;
    }

    public Class getType() {
        return mValueType;
    }
    
    //属性值类型是Object的Keyframe
    static class ObjectKeyframe extends Keyframe {
        
        //在mFraction时间点对应的属性值
        Object mValue;

        ObjectKeyframe(float fraction, Object value) {
            mFraction = fraction;
            mValue = value;
            mHasValue = (value != null);
            mValueType = mHasValue ? value.getClass() : Object.class;
        }

        public Object getValue() {
            return mValue;
        }

        public void setValue(Object value) {
            mValue = value;
            mHasValue = (value != null);
        }
    }

    //属性值类型是int的Keyframe
    static class IntKeyframe extends Keyframe {

        //在mFraction时间点对应的属性值
        int mValue;

        IntKeyframe(float fraction, int value) {
            mFraction = fraction;
            mValue = value;
            mValueType = int.class;
            mHasValue = true;
        }

        IntKeyframe(float fraction) {
            mFraction = fraction;
            mValueType = int.class;
        }

        public int getIntValue() {
            return mValue;
        }

        public Object getValue() {
            return mValue;
        }

        public void setValue(Object value) {
            if (value != null && value.getClass() == Integer.class) {
                mValue = ((Integer)value).intValue();
                mHasValue = true;
            }
        }
    }

    //属性值类型是float的Keyframe
    static class FloatKeyframe extends Keyframe {
        //在mFraction时间点对应的属性值
        float mValue;

        FloatKeyframe(float fraction, float value) {
            mFraction = fraction;
            mValue = value;
            mValueType = float.class;
            mHasValue = true;
        }

        FloatKeyframe(float fraction) {
            mFraction = fraction;
            mValueType = float.class;
        }

        public float getFloatValue() {
            return mValue;
        }

        public Object getValue() {
            return mValue;
        }

        public void setValue(Object value) {
            if (value != null && value.getClass() == Float.class) {
                mValue = ((Float)value).floatValue();
                mHasValue = true;
            }
        }
    }
}

我总结下整个流程:
首先ValueAnimator每次收到帧更新消息时会先计算出此时的fraction,然后将fraction作为参数传给PropertyValuesHolder的calculateValue方法去计算此时对应的属性值,PropertyValuesHolder再把fraction作为参数传给Keyframs的getValue方法去计算属性值,Keyframs只是个接口,真正的实现是在KeyframeSet,KeyframeSet会取到对应的Keyframe重新计算出新的fraction和startValue,endValue,然后将它们作为参数传给估值器的evaluate方法,所以属性值最终是由估值器计算所得的。

理解了属性动画的值更新机制,那么要获取每次更新后的属性值就很简单了。
我们只需要监听动画更新接口,在里面通过getAnimatedValue方法就能拿到每次更新后的属性值。如果属性动画同时更新多个属性,那么需要给每个PropertyValuesHolder定义一个propertyName,通过propertyName获取到对应属性的值。参考代码如下:

 valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //获取动画属性值,默认获取的是第一个PropertyValuesHolder的属性值
                float value = (float) animation.getAnimatedValue();
                //根据propertyName获取对应属性的值
                value = (float) animation.getAnimatedValue(propertyName);
            }
        });

总结

注意事项:我们通过源码发现KeyframeSet通过getValue处理fraction时最后直接调用了mEvaluator的evaluate方法,没有对mEvaluator进行判空,所以在构建自定义值类型的属性动画时必须传mEvaluator进来,不然会出现npe异常。

最后再回答一个问题:
有了视图动画后为什么还要引入属性动画?
1.视图动画只能支持一些简单的动画效果,而属性动画能实现视图动画无法实现的功能 。
2.视图动画只是改变了视图的绘制,并没有改变视图的属性,所以会出现一些莫名其妙的问题,比如一个控件通过视图动画移动到另一个位置,但是点击此时的控件不会触发事件,而点击原来的位置却能触发事件。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android属性动画是在Android 3.0之后出现的一种强大的动画框架,它可以为几乎任何对象添加动画效果。属性动画通过在指定的时间内更改对象中的属性值来实现动画效果。与帧动画和补间动画相比,属性动画的特点是可以实现更灵活、更丰富的动画效果。属性动画可以对任意属性进行动画操作,包括Alpha(透明度)、Scale(缩放)、Rotation(旋转)和Translation(平移),甚至可以对自定义属性进行动画操作。属性动画还可以对多个属性同时进行动画操作,可以实现复杂的动画效果。总之,属性动画为开发者提供了更多自由度和创造力,使得Android应用的动画效果更加生动和丰富。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Android 属性动画原理解析](https://blog.csdn.net/mg2flyingff/article/details/112726656)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [【Android属性动画最全解析](https://blog.csdn.net/huweiliyi/article/details/105671079)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值