Android 设计模式 笔记 - 深入了解属性动画

Posted 鲨鱼丶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 设计模式 笔记 - 深入了解属性动画相关的知识,希望对你有一定的参考价值。

差不多的开发者都应该知道的android提供的三种属性动画:

  • View Animation
  • Drawable Animation
  • Property Animation

但是在Android系统不断更新完善的过程中,他们添加了很多低版本所没有的属性动画,为了兼容这些低版本的动画,他们创建了一个兼容库,NineOldAnimations。我们就拿NineOldAnimations兼容库来看看属性动画:

属性动画的总体设计:

Animation 通过PropertyValuesHolder 来更新对象的目标属性,如果用户没有设定目标属性的Property对象,那么会通过反射的形式调用目标属性的setter方法来更新属性值;否则,通过Property的set方法来设置属性值,这个属性值则通过KeyFrameSet的计算得到,而KeyFrameSet又是通过时间插值器和类型估值器来计算的,在动画执行的过程中不断计算当前时刻目标属性的值,然后 更新属性值来达到动画效果。

属性动画的核心类介绍:

了解核心类的名称和作用

  • ValueAnimation:该类是一个Animation的子类,实现了动画的整个处理逻辑,也是属性动画最核心的类
  • ObjectAnimation:对象属性动画的操作类,继承ValueAnimation,通过该类使用动画的形式操作对象的属性
  • TimeInterpolator:时间插值器,根据时间流逝的百分比来计算当前属性值改变的百分比,系统预置的有线性插值器(LinearInterpolator),加速减速插值器(AccelerateDecelerateInterpolator)和减速插值器(DecelerateInterpolator)等。
  • TypeEvaluator:类型估值器。根据当前属性改变的百分比来计算改变后的属性值,系统预装的有针对整数属性(IntEvaluator),针对浮点属于(FloatEvaluator)和针对Color属性(ArgbEvaluator)
  • Property:属性对象,定义了属性的set和get方法
  • PropertyValuesHolder:持有目标属性Property、setter和getter方法还有关键帧集合的类。
  • KeyFrameSet:存储一个动画的关键帧集合
  • AnimationProxy:在Android 3.0以下是使用View的属性动画的辅助类

流程图:

  • ValueAnimation流程
  1. 开始
  2. 设置动画执行时间,目标对象,属性值
  3. 启动动画
  4. 判断是否延迟执行,否的话跳过5、6两步
  5. 将该动画放到等待队列
  6. 通过handler发送一个延迟消息来延后执行
  7. 执行动画
  8. 根据时间插值器和估值器计算当前属性的值
  9. 判断是否设置了Property,否的话跳过第10步,是的话跳过第11步
  10. 通过Property的set方法更新属性值
  11. 通过反射调用属性的setter方法更新属性值
  12. 判断东动画是否结束,是的话结束,否的话执行第8步。
  • ObJectAnimation流程
  1. 开始
  2. 设置动画执行时间,目标对象,属性值
  3. 启动动画
  4. 判断动画是否延迟启动,否的话跳过第5、6两步
  5. 将该动画加入等待队列
  6. 通过Handler方一个延迟消息来延后执行
  7. 执行动画
  8. 根据时间插值器和估值器计算当前时间属性的值
  9. 判断系统的版本是否小于11,是的话跳过10、否的话跳过11
  10. 通过Android 3.0 以后的setter方法实现动画方法
  11. 通过matrix实现动画方法。
  12. 判断动画是否结束,是的话动画结束,否的话指定第8步

核心原理分析:

我们看下面这行代码,实现一个非常简单的功能,一个View从x轴缩放到原来的0.3倍,动画执行时间为1秒钟:

   public void showSimpleAnimation(View view)
        ValueAnimator colorAnimation = ObjectAnimator.ofFloat(view,"scaleX",0.3f);
        colorAnimation.setDuration(1000) ;
        colorAnimation.start();
    

要看他们的原理我们首先要从ObjectAnimator入手,我们看下ObjectAnimator的ofFloat函数:

    public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) 
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
    

看了代码知道在ObjectAnimator中函数会首先构造一个ObjectAnimator对象,然后根据设置的属性值来初始化各个时间段对应的属性值,这个属性值就是函数里面的values参数,可以看出来values是一个可变参数,如果是一个参数,那么该参数为目标值;如果是两个参数,那么一个是其实值,一个是目标值。我们看下函数setFloatValues的实现:

       @Override
    public void setFloatValues(float... values) 
        //   PropertyValuesHolder[] mValues;
        //mValues 是各个数值的集合
        if (mValues == null || mValues.length == 0) 
            // No values yet - this animator is being constructed piecemeal. Init the values with
            // whatever the current propertyName is
            if (mProperty != null) 
                setValues(PropertyValuesHolder.ofFloat(mProperty, values));
             else 
                setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
            
         else 
            super.setFloatValues(values);
        
    

我们首先看到mValues的对象是ObjectAnimator父类ValueAnimator的一个PropertyValuesHolder对象,这个类是该动画库的一个核心类之一。他的作用就是保存属性的名称和setter'和getter方法,以及他的目标值。我们看下这个类的定义:

public class PropertyValuesHolder implements Cloneable 

   //属性名称
    String mPropertyName;

   //属性对象
    protected Property mProperty;

    //属性setter方法
    Method mSetter = null;

   //属性getter方法
    private Method mGetter = null;

    //属性值的类型:float,int等
    Class mValueType;

   //动画关键帧的即可,即在duration时间内的动画帧集合,它保存的是在每个时刻该属性的对应值
    KeyframeSet mKeyframeSet = null;


    //代码省略


    public static PropertyValuesHolder ofFloat(String propertyName, float... values) 
        //构造的是FloatPropertyValuesHolder对象
        return new FloatPropertyValuesHolder(propertyName, values);
    

   //代码省略


    //内部类,Float类型的PropertyValuesHolder对象
    static class FloatPropertyValuesHolder extends PropertyValuesHolder 

       //
        private FloatProperty mFloatProperty; //float类型属性

        FloatKeyframeSet mFloatKeyframeSet; //动画的关键帧
        float mFloatAnimatedValue;

      //代码省略

        public FloatPropertyValuesHolder(Property property, float... values) 
            super(property);
            //设置目标属性值
            setFloatValues(values);
            if (property instanceof  FloatProperty) 
                mFloatProperty = (FloatProperty) mProperty;
            
        

        //设置动画的目标值
        @Override
        public void setFloatValues(float... values) 
            //调用父类方法
            super.setFloatValues(values);
            //获取动画的关键帧
            mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet;
        
        //计算当前的动画值
        @Override
        void calculateValue(float fraction) 
            mFloatAnimatedValue = mFloatKeyframeSet.getFloatValue(fraction);
        

       //代码省略

可以看出这个类是属性和属性值的辅助类,它保存了属性的名称,setter,getter,以及属性在duration时间段内各个时刻对应的属性数值(mKeyframeSet)。动画执行时,动画库只需要根据动画的执行时间,就可以在mKeyframeSet查找到对应的属性值,然后修改执行动画对象的目标属性值,然后连续执行这个过程就可以达到这个动画的效果。上面我们给的例子是属性是scaleX,目标属性值是0.3f。因此,这个例子对应的属性类是FloatPropertyValuesHolder,当然还有IntPropertyValuesHolder等之类的,其实都是一样理解。

在PropertyValuesHolder的内部类FloatPropertyValuesHolder的构造函数调用了setFloatValues来设置动画的目标值。然后才能通过getFloatValue函数获取到动画的关键帧。

这个方法的实现我们看下:

    public void setFloatValues(float... values) 
        mValueType = float.class;
        mKeyframeSet = KeyframeSet.ofFloat(values);
    

可以看到这个方法的调用了 KeyframeSet.ofFloat(values);函数,追踪程序:

    public static KeyframeSet ofFloat(float... values) 
        boolean badValue = false;
        int numKeyframes = values.length;
        FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
        if (numKeyframes == 1) 
            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) 
                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);
    

我们看到关键帧的计算就在ofFloat函数中。如果用户设置了一个目标值,那么这个值就是最终值,他的其实值被默认为0;如果用户设置了大于1的目标值,这些关键帧都会被存储到KetFrameSet对象中。设置完关键帧之后,我们就可以调用start()方法启动动画了,我们去看下OnjectAnimator对象的start()方法:

    private void start(boolean playBackwards) 
        //判断looper是否为空,
        if (Looper.myLooper() == null) 
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        
        //设置基本状态
        mPlayingBackwards = playBackwards;
        mCurrentIteration = 0;
        mPlayingState = STOPPED;
        //启动动画
        mStarted = true;
        mStartedDelay = false;
        mPaused = false;
        //将该动画加入到等待执行的动画队列中
        AnimationHandler animationHandler = getOrCreateAnimationHandler();
        animationHandler.mPendingAnimations.add(this);
        //判断是否延迟
        if (mStartDelay == 0) 
            
            setCurrentPlayTime(0);
            //设置动画的开始执行时间,因为动画会有一个duration
            //这个开始执行时间+duration就是结束时间
            mPlayingState = STOPPED;
            mRunning = true;
            //触发动画监听器
            notifyStartListeners();
        
        
        animationHandler.start();
    

    @Override
    public void start() 
        start(false);
    

我们看到了,这个函数总共做了几件事:

  • 启动动画操作设置了一些基本参数
  • 把自己添加到了待执行的动画列表中
  • 通过AnimationHandler启动了动画






以上是关于Android 设计模式 笔记 - 深入了解属性动画的主要内容,如果未能解决你的问题,请参考以下文章

Android 设计模式 笔记 - 深入了解WindowManager

Android动画深入分析

Android进阶知识——Android动画深入分析

Android进阶知识——Android动画深入分析

Android进阶知识——Android动画深入分析

Android知识点剖析系列:深入了解layout_weight属性