属性动画原理解析详解手机开发

属性动画

Android中的动画分为三种:

  • View Animation 视图动画
  • Tween Animation 补间动画
  • Property Animator 属性动画

属性动画也有两种:

  • ValueAnimator 单一的属性动画(包括ObjectAnimator、TimeAnimator)
  • AnimatorSet 多个属性动画组合

属性动画的使用

属性动画可以用xml或者代码来表达,这里会选择代码的形式,比较容易理解。

属性动画的使用也不难,例如我们做一个位移属性的动画

// 设置位移动画 
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationY", 0, 1000); 
//设置持续时间 
animator.setDuration(1500); 
//设置线性时间插值器 
animator.setInterpolator(new LinearInterpolator()); 
// 开始 
animator.start(); 

动画监听

动画支持添加多个监听器

animator.addListener(new Animator.AnimatorListener() {
    
    @Override 
    public void onAnimationStart(Animator animator) {
    
        // 动画开始 
    } 
  
    @Override 
    public void onAnimationEnd(Animator animator) {
    
        // 动画结束 
    } 
  
    @Override 
    public void onAnimationCancel(Animator animator) {
    
        // 动画取消 
    } 
  
    @Override 
    public void onAnimationRepeat(Animator animator) {
    
        // 动画重复 
    } 
}); 

插值器(Interpolator)

  • 定义:

    利用时间流逝来换算当前的进度的百分比

  • 种类:

  1. LinearInterpolator(线性插值器):匀速动画
  2. AccelerateDecelerateInterpolator(加速减速插值器):动画开始和结束慢,中间快。
  3. DecelerateInterpolator(减速插值器):动画逐渐变慢
  • 使用:

    animator.setInterpolator(new LinearInterpolator());

估值器(TypeEvaluator)

  • 定义:

    利用进度的百分比来换算当前属性值是多少

  • 种类:

  1. IntEvaluator:针对整型属性
  2. FloatEvaluator:针对浮点型属性
  3. ArgbEvaluator:针对Color属性
  • 作用

    结合插值器获取当前的进度比,加上估值器计算出属性值,设置给作用的对象,这样表现出随着时间流逝,对象的属性值会发生不断的变化。这就是属性动画的基本原理。

    同样也可以自定义Interpolator和TypeEvaluator来自定义自己想要的动画效果。

  • 使用

    1.设置TypeEvaluator

    animator.setEvaluator(TypeEvaluator value)

    2.构造函数
    ObjectAnimator animator = Animator.ofObject(TypeEvaluator实例, 起始值, 结束值)

    默认设置为FloatEvaluator
    ObjectAnimator animator = ofFloat(Object target, String propertyName, float... values)

属性动画的两个条件

  1. 对象的类要提供set方法来修改属性值,因为属性动画就是利用反射修改对应的属性,如果对象的属性值没有初始化值,还需要提供get方法来获取属性值

  2. 修改对象属性值的set方法,UI界面应该有所变化,否则也会看不出动画效果

属性动画原理解析

ObjectAnimator初始化

从ObjectAnimator的ofFloat开始,我们看下ObjectAnimator的初始化

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

在ObjectAnimator中,有几个重要的变量

mTarget 表示作用的对象
mPropertyName 表示动画的属性名
mProperty 就是存储着属性类型和名字,跟反射有关

// ObjectAnimator 
private WeakReference<Object> mTarget; 
private String mPropertyName; 
private Property mProperty; 
private boolean mAutoCancel = false; 
 
private ObjectAnimator(Object target, String propertyName) {
    
    setTarget(target); 
    setPropertyName(propertyName); 
} 

这里都是设置动画的初始值

// ObjectAnimator.java 
public void setFloatValues(float... values) {
    
    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); 
    } 
} 
 
// ValueAnimator.java 
public void setFloatValues(float... values) {
    
    if (values == null || values.length == 0) {
    
        return; 
    } 
    if (mValues == null || mValues.length == 0) {
    
        setValues(PropertyValuesHolder.ofFloat("", values)); 
    } else {
    
        PropertyValuesHolder valuesHolder = mValues[0]; 
        valuesHolder.setFloatValues(values); 
    } 
    // New property/values/target should cause re-initialization prior to starting 
    mInitialized = false; 
} 

ObjectAnimator start()启动

属性动画启动

//ObjectAnimator 
public void start() {
    
    // 取消当前的属性动画 
    AnimationHandler.getInstance().autoCancelBasedOn(this); 
    super.start(); 
} 

直接看ObjectAnimator的父类ValueAnimator

// ValueAnimator 
@Override 
public void start() {
    
    start(false); 
} 
 
// ValueAnimator 
private void start(boolean playBackwards) {
    
    addAnimationCallback(0); // 1. 添加Choreographer动画绘制的回调 
    startAnimation(); // 2. 对外Animatior监听的起始回调 
    // 3. 计算、设置初始值 
    if (mSeekFraction == -1) {
    
        setCurrentPlayTime(0); 
    } else {
    
        setCurrentFraction(mSeekFraction); 
    } 
} 

1. addAnimationCallback()

这部分跟Choreographer有关系,作用是向Choreographer注册一次动画绘制的回调。

// ValueAnimator 
private void addAnimationCallback(long delay) {
    
    getAnimationHandler().addAnimationFrameCallback(this, delay); 
} 
 
// AnimationHandler.java 
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
    
    if (mAnimationCallbacks.size() == 0) {
    // 当前动画为空,若非空则说明已经注册过了 
        // 底层是注册一次Vsync信号 
        getProvider().postFrameCallback(mFrameCallback); 
    } 
    // 添加监听回调 
    if (!mAnimationCallbacks.contains(callback)) {
    
        mAnimationCallbacks.add(callback); 
    } 
    // 添加延迟动画监听回调 
    if (delay > 0) {
    
        mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay)); 
    } 
} 

getAnimationHandler()获取的是AnimationHandler。

getProvider().postFrameCallback(mFrameCallback);
目的是AnimationHandler是向Choreographer注册一次Vsync信号,因为ValueAnimator实现了AnimationFrameCallback,接收信号之后AnimationHandler会回调ValueAnimator的doAnimationFrame()执行一次绘制。

// AnimationHandler.java 
interface AnimationFrameCallback {
    
    boolean doAnimationFrame(long frameTime); 
    void commitAnimationFrame(long frameTime); 
} 

2. startAnimation()

开始动画执行,对所有的属性值进行初始化,并且通知外部的动画监听动画已经开始

// ValueAnimator 
private void startAnimation() {
    
    mAnimationEndRequested = false; 
    initAnimation(); 
    mRunning = true; 
    if (mListeners != null) {
    
        // 通知外部动画监听器 
        notifyStartListeners(); 
    } 
} 
 
//ValueAnimator 
void initAnimation() {
    
    if (!mInitialized) {
    
        int numValues = mValues.length; 
        for (int i = 0; i < numValues; ++i) {
    
            // 初始化默认值 
            mValues[i].init(); 
        } 
        mInitialized = true; 
    } 
} 

3.setCurrentPlayTime/setCurrentFraction

根据是当前动画时长是从0开始,还是从中间开始来计算帧值(属性值)

//ValueAnimator 
public void setCurrentPlayTime(long playTime) {
    
    float fraction = mDuration > 0 ? (float) playTime / mDuration : 1; 
    setCurrentFraction(fraction); 
} 
 
//ValueAnimator 
public void setCurrentFraction(float fraction) {
    
    initAnimation(); 
    fraction = clampFraction(fraction); 
    mStartTimeCommitted = true; // do not allow start time to be compensated for jank 
    if (isPulsingInternal()) {
    
        long seekTime = (long) (getScaledDuration() * fraction); 
        long currentTime = AnimationUtils.currentAnimationTimeMillis(); 
        // Only modify the start time when the animation is running. Seek fraction will ensure 
        // non-running animations skip to the correct start time. 
        mStartTime = currentTime - seekTime; 
    } else {
    
        // If the animation loop hasn't started, or during start delay, the startTime will be 
        // adjusted once the delay has passed based on seek fraction. 
        mSeekFraction = fraction; 
    } 
    mOverallFraction = fraction; 
    final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing); 
    animateValue(currentIterationFraction); 
} 

以下就是利用插值器和估值器,结合时长来计算出属性值,并设置给对应的对象属性。

//ObjectAnimator 
@Override 
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); 
    } 
} 
 
//ValueAnimator 
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); 
        } 
    } 
} 

计算属性值

//PropertyValuesHolder 
void calculateValue(float fraction) {
    
    Object value = mKeyframes.getValue(fraction); 
    mAnimatedValue = mConverter == null ? value : mConverter.convert(value); 
} 

将计算结果赋值给对象

//PropertyValuesHolder 
void setAnimatedValue(Object target) {
    
    if (mProperty != null) {
    
        mProperty.set(target, getAnimatedValue()); 
    } 
    if (mSetter != null) {
    
        try {
    
            mTmpValueArray[0] = getAnimatedValue(); 
            mSetter.invoke(target, mTmpValueArray); 
        } catch (InvocationTargetException e) {
    
            Log.e("PropertyValuesHolder", e.toString()); 
        } catch (IllegalAccessException e) {
    
            Log.e("PropertyValuesHolder", e.toString()); 
        } 
    } 
} 

以上只是做了一些属性值的计算和赋值动作,并没有真正绘制起来

AnimationHandler

// AnimationHandler.java 
private final ArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime = 
        new ArrayMap<>(); 
private final ArrayList<AnimationFrameCallback> mAnimationCallbacks = 
        new ArrayList<>(); 
private final ArrayList<AnimationFrameCallback> mCommitCallbacks = 
        new ArrayList<>(); 

mFrameCallback实现了Choreographer.FrameCallback的接口,是动画绘制的关键:

  1. doAnimationFrame() 绘制动画
  2. 当前动画还有继续的时候(mAnimationCallbacks.size() > 0),继续往递归添加回调,这样就可以再次计算属性值、再次绘制,动画的效果就连续起来了

那么,mFrameCallback是什么时候执行的呢?

这跟Choreographer和Vsync信号有关,参考《Choreographer》

总之先知道Choreographer总会回调调用doFrame()

// AnimationHandler.java 
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
    
    @Override 
    public void doFrame(long frameTimeNanos) {
    
        doAnimationFrame(getProvider().getFrameTime()); 
        if (mAnimationCallbacks.size() > 0) {
    
            getProvider().postFrameCallback(this); 
        } 
    } 
}; 
 
// AnimatorHandler.java 
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); 
    } 
} 

AnimationHanlder的doAnimationFrame也是调用了ValueAnimator的doAnimationFrame()

// 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; 
        } 
        if (isCallbackDue(callback, currentTime)) {
    
            // 调用ValueAnimator的doAnimationFrame 
            callback.doAnimationFrame(frameTime); 
            if (mCommitCallbacks.contains(callback)) {
    
                getProvider().postCommitCallback(new Runnable() {
    
                    @Override 
                    public void run() {
    
                        // 修正动画 
                        commitAnimationFrame(callback, getProvider().getFrameTime()); 
                    } 
                }); 
            } 
        } 
    } 
    cleanUpList(); 
} 

ValueAnimator实现了AnimationFrameCallback

// AnimationHandler.java 
interface AnimationFrameCallback {
    
    boolean doAnimationFrame(long frameTime); 
    void commitAnimationFrame(long frameTime); 
} 

看ValueAnimator的doAnimationFrame做了哪些动作

//ValueAnimator.java 
public final boolean doAnimationFrame(long frameTime) {
    
    if (mStartTime < 0) {
    
        // First frame. If there is start delay, start delay count down will happen *after* this 
        // frame. 
        mStartTime = mReversing 
                ? frameTime 
                : frameTime + (long) (mStartDelay * resolveDurationScale()); 
    } 
    // Handle pause/resume 
    if (mPaused) {
    
        mPauseTime = frameTime; 
        removeAnimationCallback(); 
        return false; 
    } else if (mResumed) {
    
        mResumed = false; 
        if (mPauseTime > 0) {
    
            // Offset by the duration that the animation was paused 
            mStartTime += (frameTime - mPauseTime); 
        } 
    } 
if (!mRunning) {
    
        if (mStartTime > frameTime && mSeekFraction == -1) {
    
            return false; 
        } else {
    
            mRunning = true; 
            startAnimation(); 
        } 
    } 
    if (mLastFrameTime < 0) {
    
        if (mSeekFraction >= 0) {
    
            long seekTime = (long) (getScaledDuration() * mSeekFraction); 
            mStartTime = frameTime - seekTime; 
            mSeekFraction = -1; 
        } 
        mStartTimeCommitted = false; // allow start time to be compensated for jank 
    } 
    mLastFrameTime = frameTime; 
    final long currentTime = Math.max(frameTime, mStartTime); 
    boolean finished = animateBasedOnTime(currentTime); 
    if (finished) {
    
        endAnimation(); 
    } 
    return finished; 
} 
  1. 控制动画的停止和暂停
  2. 控制动画的开始和结束
  3. animateBasedOnTime()会去根据时长重新赋值
boolean animateBasedOnTime(long currentTime) {
    
        ... 
        animateValue(currentIterationFraction); 
        return done; 
} 

animateBaseOnTime()调用animateValue,重新计算赋值属性值。

但是这里会引发一个疑问:
从上到下,都只是说明了动画一直在随着时间流逝不停地修改属性值,并没有将属性值绘制到界面UI上

如果你了解了Choreographer,你就会知道ViewRootImpl跟Choreographer息息相关的。

Choreographer

我们重点看下Choreographer怎么处理postFrameCallback()

// Choreographer 
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
    
    postCallbackDelayedInternal(CALLBACK_ANIMATION, 
                callback, FRAME_CALLBACK_TOKEN, delayMillis); 
} 
 
private void postCallbackDelayedInternal(int callbackType, 
            Object action, Object token, long delayMillis) {
    
 
    mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); 
 
    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); 
                msg.arg1 = callbackType; 
                msg.setAsynchronous(true); // 
                mHandler.sendMessageAtTime(msg, dueTime); 
} 

Choreographer主要处理四种类型的动作,根据四种动作会有四个数组mCallbackQueues,对应存储着各自的回调

// 输入 
public static final int CALLBACK_INPUT = 0; 
// 动画 
public static final int CALLBACK_ANIMATION = 1; 
// 遍历,执行measure、layout、draw 
public static final int CALLBACK_TRAVERSAL = 2; 
// 遍历完成的提交操作,用来修正动画启动时间 
public static final int CALLBACK_COMMIT = 3; 

继续看Choreographer在运行在当前主线程的loop中,主线程的loop优先处理同步消息。

case MSG_DO_SCHEDULE_CALLBACK: 
      doScheduleCallback(msg.arg1); 
      break; 

doScheduleCallback内部也会发送一个MSG_DO_FRAME消息

case MSG_DO_FRAME: 
      doFrame(System.nanoTime(), 0); 
      break; 
void doFrame(long frameTimeNanos, int frame) {
    
     mFrameInfo.markInputHandlingStart(); 
     // 处理输入事件 
     doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); 
 
     mFrameInfo.markAnimationsStart(); 
     // 处理动画事件(修改属性值) 
     doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); 
 
     mFrameInfo.markPerformTraversalsStart(); 
     // 触发ViewRootImpl绘制 
     doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); 
 
     // 触发动画修正 
     doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); 
} 

最后CALLBACK_TRAVERSAL,会引发出ViewRootImpl重新发起一次绘制流程。

可以参考《Choreographer》

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/app/6248.html

(0)
上一篇 2021年7月17日 00:44
下一篇 2021年7月17日 00:44

相关推荐

发表回复

登录后才能评论