android动画深入分析—属性动画

1,057 阅读7分钟

整体架构

8GEihF.md.png

  • Property:属性对象,主要定义属性的set和get方法
  • PropertyValueHolder:持有目标属性Property,setter和getter方法以及关键帧集合的类
  • KeyframeSet:存储一个动画的关键帧集合

流程分析

创建Animator相关的类

这里以我们经常用ObjectAnimator.ofFloat创建动画进行分析

流程

8GEQhD.png

源码分析

  • 使用ofFloat创建对象Animator对象
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
    }
  • 然后调用setFloatValues,其根据是否设置mProperty,创建PropertyValuesHolder
  • 我们调用动画,一般传递属性名字
setFloatValues(..){
...
if(mVlaus == null || mValuse.length == 0){
setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
} else {
    super.setFloatValues
}
...
}

//如果之前有,
  • 创建PropertyValuesHolder,因为是float,所以创建其子类FloatPropertyValuesHolder
public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
        return new FloatPropertyValuesHolder(propertyName, values);
    }
  • 在FloatPropertyValuesHolder构造方法,会调用setFloatValues,
  • 在其内部调用父类ValueAnimator的setFloatValues,会创建KeyframeSet
  • 其调用KeyframeSet.ofFloat
public void setFloatValues(float... values) {
        mValueType = float.class;
        mKeyframes = KeyframeSet.ofFloat(values);
    }
    
public static KeyframeSet ofFloat(float... values) {
...
new FloatKeyframeSet(keyframes);
...
}

总结

  • 属性动画通过PropertyValuesHolder,保存动画执行过程中的属性和其值,和其setter和getter,
  • ObjectAnimator会通过调用PropertyValuesHolder.setAnimatedValue更新响应的属性值
  • KeyFromeSet用于保存对象的关键帧集合,然后ObjectAnimator会通过其计算动画的值

到这里,动画相关的核心类就创建完成了,接下来就是一些配置,插值器,执行时间,重复模式等。这里不做分析,下面分析动画启动过程中的一些核心逻辑

启动Animator

启动流程图

8GE31H.png

源码分析

我们直接从ValueAnimator.start开始分析

  • 其进行一些检测和状态设置后,调用addAnimationCallback方法
  • 然后判断是否延迟,调用startAnimation进行一些初始化配置
if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}

...
mStarted = true;
mPaused = false;
mRunning = false;
...
addAnimationCallback(0)

if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
    startAnimation()
}

private void startAnimation(){
    initAnimation();
    mRunning = true;
}

接下里我们重点分析addAnimationCallback

  • 其内部调用AnimationHanlder的addAnimationFrameCallback
getAnimationHandler().addAnimationFrameCallback(this, delay);

==AnimationHandler==不是Handler的子类,其内部通过Choreographer的hanlder去发送消息到UI线程

  • 在addAnimationFrameCallback,首先会判断动画的回调数组的大小,0的化,调用postFrameCallback,发送AnimationHanler的内部mFrameCallback,初始动画的执行
//里面的参数,其为AnimationHandler的成员
 private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            doAnimationFrame(getProvider().getFrameTime());
            if (mAnimationCallbacks.size() > 0) {
                getProvider().postFrameCallback(this);
            }
        }
    };

if (mAnimationCallbacks.size() == 0) {
            getProvider().postFrameCallback(mFrameCallback);
    }
  • 然后不过是否为0,最后到会将其添加到mAnimationCallbacks中,如果是延时的,还会添加到mDelayedCallbackStartTime集合中
if (!mAnimationCallbacks.contains(callback)) {
            mAnimationCallbacks.add(callback);
        }

if (delay > 0) {
    mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
 }

-这是上面用到的集合的创建,我们添加的回调,根据情况,会添加到不同数组,其中mAnimationCallbacks要重点注意,后续我们分析动画如何重复计算值,其是关键

private final ArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime =
            new ArrayMap<>();
    private final ArrayList<AnimationFrameCallback> mAnimationCallbacks =
            new ArrayList<>();
    private final ArrayList<AnimationFrameCallback> mCommitCallbacks =
            new ArrayList<>();
  • 对于第一次,其会postFrameCallback,这里先说明其是通过handler发送消息,然后进行一些处理,会被回调执行,这了我们先不分析这个过程,直接看其回调方法的调用
  • 我们看到如果mAnimationCallbacks大小不为0,其就会再次发送,然后再次被回调,这样动画就一直重复计算属性,当动画执行完,其大小为0,就不会在被发送,这就是其重复执行的逻辑
public void doFrame(long frameTimeNanos) {
    doAnimationFrame(getProvider().getFrameTime());
    if (mAnimationCallbacks.size() > 0) {
                getProvider().postFrameCallback(this);
     }
    }
  • 接下来我们看doAnimationFrame,做了什么,其是如何更新动画的属性值的
  • 其内部会调用callback.doAnimationFrame(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);
       }

  • 这个CallBack是我们之前在ValueAnimator中添加的,我们回头看一下它,发现其就是ValueAnimator
getAnimationHandler().addAnimationFrameCallback(this, delay);
  • 接下来我们看ValueAnimator的doAnimationFrame
  • 其调用animateBasedOnTime根据时间计算值,然后会返回是否完成的标志finished
  • 完成的化,调用endAnimation
...
 boolean finished = animateBasedOnTime(currentTime);
    if (finished) {
        endAnimation();
    }
...

我们看动画完成是做了什么处理

  • endAnimation内部调用removeAnimationCallback
  • removeAnimationCallback最终调用AnimationHandler的removeCallback
  • 发现其并没有删除,只是将给位置置为null,那其在什么清除呢
public void removeCallback(AnimationFrameCallback callback) {
        mCommitCallbacks.remove(callback);
        mDelayedCallbackStartTime.remove(callback);
        int id = mAnimationCallbacks.indexOf(callback);
        if (id >= 0) {
            mAnimationCallbacks.set(id, null);
            mListDirty = true;
        }
    }

我们会找到一个cleanUpList方法,其会对null元素进行移除,而起在执行完我们的ValueAnimator.doAnimationFrame会调用

private void doAnimationFrame(long frameTime) {
    
    ...
    cleanUpList()
}

private void cleanUpList() {
        if (mListDirty) {
            for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) {
                if (mAnimationCallbacks.get(i) == null) {
                    mAnimationCallbacks.remove(i);
                }
            }
            mListDirty = false;
        }
    }

通过动画执行完的处理,我们可以学习到,移除大量元素是的一种方法,同时也知道动画执行完后mAnimationCallbacks的大小会变为0,这样就不会重复执行了。

我们接着上面结束动画前的分析,即动画为执行完,其最终如何更新我们target的属性值,我们回到ValueAnimator.doAnimationFrame中,我们知道其通过animateBasedOnTime会返回是否结束标志,我们从此分析如何计算动画执行过程中的值

boolean animateBasedOnTime(long currentTime) {
        boolean done = false;
        //判断是否在允许中,进行一些处理
        if (mRunning) {
            //代码省略
            mOverallFraction = clampFraction(fraction);
            //当前遍历的比例,其会处理反向执行是的值,
            float currentIterationFraction = getCurrentIterationFraction(
                    mOverallFraction, mReversing);
            //在当前比例下的动画值
            animateValue(currentIterationFraction);
        }
        return done;
}
  • 在animateValue中,其会遍历调用所有设置得PropertyValueHolder,然后调用其caculateValue方法,然后调用动画更新监听器
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);
            }
        }

  • PropertyValueHolder会调用FloatKeyframeSet的getFloatValue方法
void calculateValue(float fraction) {
            mFloatAnimatedValue = mFloatKeyframes.getFloatValue(fraction);
}

但是到这里,我们只是找到了如何计算动画执行过程的值,没有看到更新方法的调用,那是因为对于ValueAnimator,我们需要在更新监听其里自己设置,我们一般使用的是objectAnimator,其对animateValue进行了重写

  • 首先判断目标是否存在,如果存在会调用父类的方法,即ValueAnimator的animateValue方法,计算值,
  • 然后调用ProperValueHolder的setAnimatedValue
void animateValue(float fraction) {
        final Object target = getTarget();
        if (mTarget != null && target == null) {
            // We lost the target reference, cancel and clean up. Note: we allow null target if the
            /// target has never been set.
            cancel();
            return;
        }

        super.animateValue(fraction);
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].setAnimatedValue(target);
        }

  • 在PropertyValueHolder中setAnimatedValue通过反射调用我们的设置方法
if (mProperty != null) {
            mProperty.set(target, getAnimatedValue());
        }
        if (mSetter != null) {
            try {
                mTmpValueArray[0] = getAnimatedValue();
                //这里进行调用set方法
                mSetter.invoke(target, mTmpValueArray);
            } catch (InvocationTargetException e) {
                Log.e("PropertyValuesHolder", e.toString());
            } catch (IllegalAccessException e) {
                Log.e("PropertyValuesHolder", e.toString());
            }
        }
总结
  • 动画的不断循环通过判断动画回调数组大小是否大于零,不断发送消息,实现循环
  • 动画执行完后,其会进行移除回调,回调是在全部动画回调执行完,统一移除
  • 动画值的计算是最终是通过KeyFrameSet进行计算
  • 动画对目标属性的改变通过PropertyValueHolder中setAnimatedValue方法,最终反射调用设置方法实现
  • 属性动画没有触发重绘逻辑,所以需要我们在set方法里调用,才能更新View.

Hanlder发送消息

最后,我们看一下在AnimationHandler中,最后如何回调mFameCallback,这里只展示流程图,就不分析代码了,重点在流程。就是调用Choreographer中的FrameHandler,发送消息,对不同情况进行处理,最终调用我们的mFameCallback,细节方面后面有时间在分析。

流程图

8GEGjA.png

属性动画的总结和优点

  1. 如果我们只是改变view的大小,平移,透明,旋转,可以使用ViewProperAnimator,系统对其进行了优化,比我们直接使用ObjectAnimator性能要好,具体细节可以看这篇官方博客
  2. 其真实改变了View的属性,
  3. 系统使用ProperyValueHolder和keyFrameSet计算和更新值,然后值更新后,我们手动重绘,比补间动画性能好且更灵活。