美文网首页
Android动画系列 --- 属性动画详解

Android动画系列 --- 属性动画详解

作者: 小菜同学丶 | 来源:发表于2021-11-21 18:12 被阅读0次

    前言

    属性动画系统是一个强健的框架,用于为几乎任何内容添加动画效果。您可以定义一个随时间更改任何对象属性的动画,无论其是否绘制到屏幕上。属性动画会在指定时长内更改属性(对象中的字段)的值。要添加动画效果,请指定要添加动画效果的对象属性,例如对象在屏幕上的位置、动画效果持续多长时间以及要在哪些值之间添加动画效果。

    动画构造

    ObjectAnimator animator = ObjectAnimator.ofFloat(text, "translationX", 0f, 100f);
    animator.setDuration(1000);
    animator.start();
    

    一个简单的属性动画实现,它指定了 TextView 中名称为 translationX 的属性在 1000ms 的时间内由 0 变化到 100。translationX 属性是相对于父容器左上角的偏移量,所以整个动画相当于补间动画中 X轴平移动画。

    插值器

    插值器是根据动画时间来计算出特定的动画完成比例的值。在实际应用中,动画可以是匀速运动,也可以是变速运动比如先加速在减速等多种运动方式。Android系统默认提供了九种插值器的实现,我们可以根据自身需求去灵活选择需要的插值器实现。

    说明
    AccelerateDecelerateInterpolator 该插值器的变化率在开始和结束时缓慢但在中间会加快。
    AccelerateInterpolator 该插值器的变化率在开始时较为缓慢,然后会加快。
    AnticipateInterpolator 该插值器先反向变化,然后再急速正向变化。
    AnticipateOvershootInterpolator 该插值器先反向变化,再急速正向变化,然后超过定位值,最后返回到最终值。
    BounceInterpolator 该插值器的变化会跳过结尾处。
    CycleInterpolator 该插值器的动画会在指定数量的周期内重复。
    DecelerateInterpolator 该插值器的变化率开始很快,然后减速。
    LinearInterpolator 该插值器的变化率恒定不变。
    OvershootInterpolator 该插值器会急速正向变化,再超出最终值,然后返回。

    除去默认的九种插值器实现,我们也可以通过实现 TimeInterpolator 接口来自定义插值器。

    估值器

    估值器是根据时间比例算出属性值,默认实现有 Int、Float和rgb值估值器,如果动画对象属性不是以上三种,就需要去实现 TypeEvaluator 接口来计算对应属性的值。

    说明
    IntEvaluator 计算Int类型估值器
    FloatEvaluator 计算Float类型估值器
    ArgbEvaluator 计算颜色argb估值器

    动画原理

    //一个简单的例子
    ObjectAnimator animator = ObjectAnimator.ofFloat(text, "translationX", 0f, 100f);//step1
    animator.setInterpolator(new AccelerateDecelerateInterpolator());
    animator.setDuration(1000);
    animator.start();//step2
    

    ObjectAnimator.ofFloat()中做的事情很简单,对属性对象和属性名称变量进行赋值,然后处理传入的属性值。

    // ObjectAnimator.java
    private WeakReference<Object> mTarget; //动画对象
    private String mPropertyName; //动画对象属性
    public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        //mTarget和mPropertyName赋值
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        //处理传入的动画属性值
        anim.setFloatValues(values);
        return anim;
    }
    

    需要注意的是 propertyName需要在target中有对应的set/get方法实现。values属性值会经过一系列的处理进行保存,在动画进行中获取对应的属性值。我们需要弄清楚values属性值的转换逻辑和存储结构,可以从setFloatValues开始入手分析。

    // ObjectAnimator.java
    PropertyValuesHolder[] mValues;
    public void setFloatValues(float... values) {
        if (mValues == null || mValues.length == 0) {
            if (mProperty != null) {
                setValues(PropertyValuesHolder.ofFloat(mProperty, values));
            } else {
                setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
            }
        } else {
            super.setFloatValues(values);
        }
    }
    

    从上面代码中可以看到构造了一个PropertyValuesHolder对象,PropertyValuesHolder内部主要有两个作用,

    • 通过Keyframes数组存储属性值
    • 获取和设置属性值,根据给出的属性名称和类名通过反射拿到对应的getter/setter方法。

    Keyframes默认有两个扩展IntKeyframesFloatKeyframesKeyframeSet是具体的实现类提供了int,float和Object类型数据的存储转换,另外int和float作为系统可识别类型也有两个默认扩展实现IntKeyframeSetFloatKeyframeSet

    object_animator_keyframe.jpg
    // PropertyValuesHolder.java
    public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
            return new FloatPropertyValuesHolder(propertyName, values);
    }
    
    static class FloatPropertyValuesHolder extends PropertyValuesHolder{
       ...
      Keyframes.FloatKeyframes mFloatKeyframes;
      
      // -----1   
      public FloatPropertyValuesHolder(String propertyName, float... values) {
         super(propertyName); //mPropertyName = propertyName 赋值
         setFloatValues(values); // ###2
      }  
     
      // -----2
      public void setFloatValues(float... values) {
        super.setFloatValues(values); //###3
        mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
      }
      
      // -----3
      public void setFloatValues(float... values) {
        mValueType = float.class;
        mKeyframes = KeyframeSet.ofFloat(values);
       }
       ...
    }
    

    KeyframeSet主要负责将属性值进行包装存储,内部通过Keyframe数组来保存属性值。Keyframe也有intfloatObject的三个默认扩展实现。

    // Keyframe.java
    public abstract class Keyframe implements Cloneable {
      
      public static Keyframe ofInt(float fraction) {
            return new IntKeyframe(fraction);
      }
      
      public static Keyframe ofFloat(float fraction) {
            return new FloatKeyframe(fraction);
      }
      
      public static Keyframe ofObject(float fraction) {
            return new ObjectKeyframe(fraction, null);
      }
      
      static class FloatKeyframe extends Keyframe{ ... }
      static class IntKeyframe extends Keyframe{ ... }
      static class ObjectKeyframe extends Keyframe{ ... }
    }
    
    
    

    KeyframeSet中处理属性值转换的逻辑:

    // KeyframeSet.ofFloat(values)
    // 继承关系 FloatKeyframeSet extends KeyframeSet implements KeyFrames.FloatKeyframes
    // KeyframeSet implements KeyFrames
    // FloatKeyframe extends Keyframe(抽象类)  
    public static KeyframeSet ofFloat(float... values) {
      boolean badValue = false;
      int numKeyframes = values.length;
      //初始化数组容量大小最少为2
      FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
      //输入的值如果是一个 默认补0作为开始值
      if (numKeyframes == 1) {
        keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
        keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
        //非指定类型数值 badValue置为true
        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]);
           //非指定类型数值 badValue置为true
          if (Float.isNaN(values[i])) {
            badValue = true;
          }
        }
      }
      if (badValue) {
        Log.w("Animator", "Bad value (NaN) in float animator");
      }
      return new FloatKeyframeSet(keyframes);
     }
    

    首先初始化FloatKeyframe数据,默认最小容量为2即至少有初始值和结束值。当numKeyframes的大小为1时,则将传入的属性值作为结束值,开始值用0填充。当numKeyframes的大小大于1的时候,先确定属性值数组第一个为初始值,后续根据传入属性值数组的容量大小从1开始遍历赋值,最后返回给定类别的KeyframeSet

    PropertyValuesHolder初始化成功后调用setValues方法对mValues进行赋值,将属性名称和PropertyValuesHolder进行key-value绑定,变更初始化标签mInitialized为false等待重新初始化。

    // ValueAnimator.java
    public void setValues(PropertyValuesHolder... values) {
        int numValues = values.length;
        mValues = values;
        //Map保存属性名称和属性值
        mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
        for (int i = 0; i < numValues; ++i) {
            PropertyValuesHolder valuesHolder = values[i];
            mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
        }
        // 属性变更重新初始化
        mInitialized = false;
    }
    

    属性值的存储处理过程分析完毕,取值和调用setter方法先在start()方法之后在分析,这样整体串下来思路会比较清晰。

    animator.start();
    

    这是开启动画的方法调用,看起来很简单只有两步操作。但实际上内部处理的东西可不止两步,让我们一步一步分析

    // ObjectAnimator.java
    public void start() {
        // AnimationHandler.java
        //step1  
        AnimationHandler.getInstance().autoCancelBasedOn(this);
        ...
        //step2  
        super.start();
    }
    

    先从AnimationHandler.getInstance().autoCancelBasedOn(this)开始,这个方法的作用是将存在的帧回调处理取消。如何来理解可以等分析完super.start()方法在来具体看下,super.start()ValueAnimator中的方法,内部对一些状态进行初始化。

    // ValueAnimator.java
    private void start(boolean playBackwards) {
        //当前线程不存在事件循环抛出异常
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        //是否反转 默认false
        mReversing = playBackwards; // false
        mSelfPulse = !mSuppressSelfPulseRequested; //true
        ...
        //状态初始化  
        mStarted = true;
        mPaused = false;
        mRunning = false;
        mAnimationEndRequested = false;
        //重置时间相关状态
        mLastFrameTime = -1;
        mFirstFrameTime = -1;
        mStartTime = -1;
        //重点方法:添加事件到Handler
        addAnimationCallback(0);
    
        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
            //如果没有延迟则立即启动动画监听
            startAnimation();
            if (mSeekFraction == -1) {
                //尚未开始启动设置播放时间为0
                setCurrentPlayTime(0);
            } else {
                setCurrentFraction(mSeekFraction);
            }
        }
    }
    

    addAnimationCallback做的很简单,判断mSelfPulse,为真则退出否则调用AnimationHandelr中的addAnimationFrameCallback()将自身和延迟时间传入。

    // ValueAnimator.java
    private void addAnimationCallback(long delay) {
        if (!mSelfPulse) {
            return;
        }
        getAnimationHandler().addAnimationFrameCallback(this, delay);
    }
    

    接下来是核心重点类AnimationHadnler 这个类的主要作用是通过Choreographer来设置动画帧回调,等到VSYNC信号的时候,额外执行动画帧回调。getProvider()是获取的MyFrameCallbackProvider对象,内部持有Choreographer实例,getProvider().postFrameCallback(mFrameCallback)实际上是调用了Choreographer中的postFrameCallback(callback)方法。

    / AnimationHandler.java
    private final ArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime =
                new ArrayMap<>();
    private final ArrayList<AnimationFrameCallback> mAnimationCallbacks =
      new ArrayList<>();
    private final ArrayList<AnimationFrameCallback> mCommitCallbacks =
      new ArrayList<>();
    private AnimationFrameCallbackProvider mProvider;
    public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
        if (mAnimationCallbacks.size() == 0) {
           //调用Choreographer中的postFrameCallback()
            getProvider().postFrameCallback(mFrameCallback);
        }
        //将回调添加到列表中
        if (!mAnimationCallbacks.contains(callback)) {
            mAnimationCallbacks.add(callback);
        }
        //如果需要延迟执行,将回调和执行的时间点保存
        if (delay > 0) {
            mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
        }
    }
    

    看一下mFrameCallback声明是Choreographer.FrameCallback的实现,Choreographer在执行postFrameCallback()后,会放入一个类别为CALLBACK_ANIMATION的回调队列中,当VSYNC信号来到是触发doFrame()内部通过doCallbacks来执行回调队列中的回调。代码片段如下:

    // Choreographer-doFrame()
    try {
        ...
        mFrameInfo.markAnimationsStart();
        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);//这个正是我们传入的回调类别
        doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
        ...
    }
    
    // AnimationHandler.java
    //Choreographer 触发 doCallbacks()后会执行改回调
    private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
            @Override
            public void doFrame(long frameTimeNanos) {
                //开始处理执行动画帧
                doAnimationFrame(getProvider().getFrameTime());
                if (mAnimationCallbacks.size() > 0) {
                    //如果还有帧没有处理完,等待下次vsync信号处理
                    getProvider().postFrameCallback(this);
                }
            }
     };
    

    看下触发了动画帧是如何执行的。

    //  AnimationHandler.java
    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;
        }
        //判断callback是否需要延迟执行
        if (isCallbackDue(callback, currentTime)) {
          //执行callback回调
          callback.doAnimationFrame(frameTime);
          ...
        }
      }
      cleanUpList();
    }
    

    显示判断了callback()是否需要延迟执行,如果不需要就开始调用doAnimationFrame开始执行。

    //ValueAnimator.java
    //精简代码显示主要逻辑  
    public final boolean doAnimationFrame(long frameTime) {
        ...
        mLastFrameTime = frameTime;
        //当前时间取帧时间和开始时间最大值
        final long currentTime = Math.max(frameTime, mStartTime);
        boolean finished = animateBasedOnTime(currentTime);
        //执行完毕后结束动画
        if (finished) {
            endAnimation();
        }
        return finished;
    }
    

    获取当前帧时间和开始时间最大值,传入animateBasedOnTime()执行,如果执行完毕,结束动画执行。

    //显示主要逻辑 重复执行逻辑省略
    boolean animateBasedOnTime(long currentTime) {
        boolean done = false;
        if (mRunning) {
            //获取缩放后的时长 默认不缩放
            final long scaledDuration = getScaledDuration();
            //根据时间计算动画比例
            final float fraction = scaledDuration > 0 ?
                    (float)(currentTime - mStartTime) / scaledDuration : 1f;
            ...
            //计算重复执行时候的动画比例  
            mOverallFraction = clampFraction(fraction);
            float currentIterationFraction = getCurrentIterationFraction(
                    mOverallFraction, mReversing);
            //执行属性值得更改
            animateValue(currentIterationFraction);
        }
        return done;
    }
    

    接下来就是执行属性值得更改,首先通过插值器获取过虑后的动画比例,然后通过该动画比例计算出当前属性值按照我们之前的实例这里实际会调用FloatKeyframeSet中的getValue()方法,这个方法内部会根据保存的初始和结束的属性值以及动画比例计算出当前的属性值,当然如果有设置估值器那么会使用估值器计算的属性值返回。

    // ObjectAnimator.java
    void animateValue(float fraction) {
        final Object target = getTarget();
        if (mTarget != null && target == null) {
            cancel();
            return;
        }
    
        super.animateValue(fraction); //ValueAnimator.java中的animateValue()
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            //通过 setter/getter 将属性值变化
            mValues[i].setAnimatedValue(target); 
        }
    }
    
    // ValueAnimator.java 
    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);
            }
            //动画监听onAnimationUpdate()触发
            if (mUpdateListeners != null) {
                int numListeners = mUpdateListeners.size();
                for (int i = 0; i < numListeners; ++i) {
                    mUpdateListeners.get(i).onAnimationUpdate(this);
                }
            }
        }
    

    最后一步就是给动画对象对应属性名称的属性进行赋值。如果我们动画对象是视图,视图绘制关联了某个属性,那么在这里在进行属性更改后触发重绘从而构成我们想要的动画效果。

    // FloatPropertyValuesHolder
    void setAnimatedValue(Object target) {
        if (mFloatProperty != null) {
            mFloatProperty.setValue(target, mFloatAnimatedValue);
            return;
        }
        if (mProperty != null) {
            mProperty.set(target, mFloatAnimatedValue);
            return;
        }
        if (mJniSetter != 0) {
            nCallFloatMethod(target, mJniSetter, mFloatAnimatedValue);
            return;
        }
        if (mSetter != null) {
            try {
                mTmpValueArray[0] = mFloatAnimatedValue;
                mSetter.invoke(target, mTmpValueArray);
            }
        }
    }
    

    附上一份整理的流程图:


    属性动画流程.jpg

    以上就是对属性动画的原理通过源码进行分析,大概的总结一下就是内部通过等待VSYNC信号来执行动画比例和属性值得计算,并通过setter方法重设属性值,触发重绘来构造最后的动画效果。

    相关文章

      网友评论

          本文标题:Android动画系列 --- 属性动画详解

          本文链接:https://www.haomeiwen.com/subject/xdotaltx.html