天天看點

Android 動畫 原了解析一.View動畫二.布局動畫三.屬性動畫四.動畫對比

Android 動畫 原了解析

  • 一.View動畫
    • (一)基本使用
      • 1.代碼實作
      • 2.xml實作
    • (二)原了解析
      • 1.實作原理
      • 2.源碼分析
        • (1)Animation類
        • (2)流程分析
  • 二.布局動畫
    • (一)基本使用
      • 1.xml實作
      • 2.代碼實作
    • (二)原了解析
      • 1.實作原理
      • 2.源碼分析
        • (1)解析設定LayoutAnimationController
        • (2)dispatchDraw()
  • 三.屬性動畫
    • (一)基本使用
      • 1.代碼實作
      • 2.xml實作
    • (二)原了解析
      • 1.實作原理
      • 2.源碼分析
        • (1)建立
        • (2)時間變化
        • (3)值更新
  • 四.動畫對比

android提供兩種動畫方式,一種是最原始的View的動畫,另一種是後來提供的屬性動畫

二者都可以實作大部分動畫效果,也可以通過組合實作各種動畫效果,并且支援xml和java兩種生成方式

二者的實作原理和效果差異性還是比較大的,下面來看看這兩種動畫的使用和實作原理

一.View動畫

View動畫主要使用的是Animation這個類,其子類TranslateAniamtion,AlphaAnimation等實作不同的動畫效果,比如平移,透明度,縮放,旋轉等

AnimationSet類可以管理多個Animation,做組合動畫

(一)基本使用

1.代碼實作

//旋轉動畫(以自身的中心為原點,從0度轉到360度)
RotateAnimation rotateAnimation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
//透明度動畫(從完全不可見到完全可見)
AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
//平移動畫(x軸方向,從自身原點平移500像素)
TranslateAnimation translateAnimation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.ABSOLUTE, 500, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0);
//AnimationSet集合控制多個動畫(無法控制順序,而且執行效果和add順序還有關系...)
AnimationSet animationSet = new AnimationSet(true);
//統一設定所有Animation的duration為1000
animationSet.setDuration(1000);
//動畫結束後保持位置(并不影響實際measure和layout)
animationSet.setFillAfter(true);
//順序添加Animation
animationSet.addAnimation(rotateAnimation);
animationSet.addAnimation(alphaAnimation);
animationSet.addAnimation(translateAnimation);
//view開啟動畫
view.startAnimation(animationSet);
           

由代碼可知,我們可以建立不同的Animation對象,設定不同的參數:fromValue,toValue,type(絕對值,相對于自身,相對于parent等)

當有多個動畫效果時,可以建立多個Animation對象,然後使用一個AnimationSet對象,add這些Animation,也可以設定一些公共參數

最後,通過view.startAnimation(Animation)執行view的動畫

2.xml實作

//在res/anim檔案夾下定義
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000">
    <translate
        android:fromXDelta="0"
        android:toXDelta="500" />
    <alpha
        android:fromAlpha="0"
        android:toAlpha="1" />
</set>
           
//代碼調用
Animation animation = AnimationUtils.loadAnimation(this, R.anim.translate_in);
view.startAnimation(animation);
           

我們也可以通過聲明xml檔案來定義動畫,就對應的AnimationSet對象,就對應的TranslateAnimation對象,其他的等都類似

在代碼裡,我們可以使用AnimationUtils提供的方法将xml解析成Animation對象再使用

(二)原了解析

1.實作原理

  1. Animation對象儲存了需要變化的參數,比如fromValue,toValue等等
  2. Transformation對象維護一個matrix和alpha值,來記錄不同動畫的變化結果
  3. 在ViewGroup中,為每個child調用draw()方法時,會在canvas上應用目前動畫效果的transformation,然後進行繪制
  4. startAnimation後,會調用view的invalidate方法進行重繪,每次繪制完後,如果動畫并沒有完成,還會繼續調用invalidate直至動畫完成
  5. 由(3)知,動畫隻是改變了繪制時的狀态,而不是改變了view的measure和layout的位置和大小等屬性

2.源碼分析

(1)Animation類

Android 動畫 原了解析一.View動畫二.布局動畫三.屬性動畫四.動畫對比

i.Animation類用于存儲和應用其動畫值,當繪制時,會調用到Animation對象的getTransformation方法,并傳遞一個Transformation對象,該方法将動畫目前的參數應用到Transformation對象上

//預設的實作
public boolean getTransformation(long currentTime, Transformation outTransformation) {
	...
    //根據目前時間計算動畫行進比例
    if (duration != 0) {
        normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
                (float) duration;
    } else {
        // time is a step-change with a zero duration
        normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
    }

	//動畫是否結束
    final boolean expired = normalizedTime >= 1.0f || isCanceled();
    mMore = !expired;

    if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);

    if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
        ...
		//應用動畫效果到Transformation對象上
        applyTransformation(interpolatedTime, outTransformation);
    }

    ...
	//傳回值代表動畫是否已經完成
    return mMore;
}
 
//AnimationSet的實作
public boolean getTransformation(long currentTime, Transformation t) {
    ...

	//周遊children,應用每一個動畫效果
    for (int i = count - 1; i >= 0; --i) {
        final Animation a = animations.get(i);

        temp.clear();
        more = a.getTransformation(currentTime, temp, getScaleFactor()) || more;
        t.compose(temp);

        started = started || a.hasStarted();
        ended = a.hasEnded() && ended;
    }

    ...

	//是否所有動畫均已完成
    return more;
}
           

一般動畫就是調用自己的applyTransformation方法,應用動畫參數;AnimationSet類要調用每個animation的applyTransformation方法,應用所有的動畫效果

ii.Animation類應用動畫效果調用applyTransformation方法

//TranslateAnimation
protected void applyTransformation(float interpolatedTime, Transformation t) {
    float dx = mFromXDelta;
    float dy = mFromYDelta;
    if (mFromXDelta != mToXDelta) {
        dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);//根據比例計算目前偏移值
    }
    if (mFromYDelta != mToYDelta) {
        dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
    }
    t.getMatrix().setTranslate(dx, dy);//直接将偏移應用到Transformation的matrix上
}
 
//AlphaAnimation
protected void applyTransformation(float interpolatedTime, Transformation t) {
    final float alpha = mFromAlpha;
    t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));//計算目前alpha值,應用到Transformation中
}
           

由代碼可以看到,Animation類就是将自己的目前動畫值,應用到Transformation的matrix和alpha上,用于繪制時改變繪制狀态

(2)流程分析

i.startAnimation()

public void startAnimation(Animation animation) {
    animation.setStartTime(Animation.START_ON_FIRST_FRAME);
    setAnimation(animation);//儲存Animation對象
    invalidateParentCaches();
    invalidate(true);//重繪
}
           

開啟動畫,其實實質就是記錄Animation對象,調用invalidate進行重繪,重繪時應用Animation

ii.draw()

//ViewGroup為每個child調用draw方法是根據Animation來設定動畫效果
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    ...

    Transformation transformToApply = null;
    boolean concatMatrix = false;
    final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
    final Animation a = getAnimation();
    if (a != null) {//有動畫
        more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);//應用動畫
        ...
    }

    ...

    float alpha = drawingWithRenderNode ? 1 : (getAlpha() * getTransitionAlpha());
    if (transformToApply != null
            || alpha < 1
            || !hasIdentityMatrix()
            || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
        if (transformToApply != null || !childHasIdentityMatrix) {
            ...

            if (transformToApply != null) {
                canvas.concat(transformToApply.getMatrix());//将Transformation的matrix應用到canvas上(concat拼接)
            }

            ...
        }

        ...
    }

    ...

    return more;
}
 
//擷取變換後的Transformation對象
private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
        Animation a, boolean scalingRequired) {
    Transformation invalidationTransform;
    final int flags = parent.mGroupFlags;
    final boolean initialized = a.isInitialized();
    if (!initialized) {//沒有初始化
        a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());//使用child的size和parent的size初始化Animation(因為可以有相對自己、parent等類型)
        a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);//初始化繪制region,初始化時child的區域
        if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
        onAnimationStart();//listener
    }

    final Transformation t = parent.getChildTransformation();
    boolean more = a.getTransformation(drawingTime, t, 1f);//應用Animation的效果到Transformation上
    if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
        ...
    } else {
        invalidationTransform = t;
    }

    if (more) {//如果動畫沒有執行完
        if (!a.willChangeBounds()) {//動畫效果不會導緻繪制區域大于原區域時,繼續invalidate即可
			...
            parent.invalidate(mLeft, mTop, mRight, mBottom);
        } else {//否則要計算新的region,進行invalidate
            ...
            final RectF region = parent.mInvalidateRegion;
            a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
                    invalidationTransform);//根據Transformation擷取新的region
            ...
            final int left = mLeft + (int) region.left;
            final int top = mTop + (int) region.top;
            parent.invalidate(left, top, left + (int) (region.width() + .5f),
                    top + (int) (region.height() + .5f));//invalidate新的region
        }
    }
    return more;
}
           

由代碼可知,view動畫,就是在ViewGroup調用draw方法繪制child時,将child的Animation的參數,應用到Transformation上,然後将Transformation的效果應用到canvas上進行繪制

當動畫沒有完畢時,通過不斷調用invalidate重繪來重複這一過程

view動畫實質是在draw方法裡完成,并不會影響view的measure和layout的實際大小和位置

二.布局動畫

Android還提供了一直布局動畫,其是用在ViewGroup上的,可以給這個ViewGroup的每個child指定統一的動畫,實作一定的效果,最常見的比如在ListView的items的出現效果

(一)基本使用

1.xml實作

//在res/anim檔案夾下定義
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:animation="@anim/translate_in"//指定每個child的Animation
    android:delay="50%" />//指定每個child的動畫在上一個child動畫執行了多長時間後開始執行
 
//在ViewGroup中聲明
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layoutAnimation="@anim/layout_translate_in"
    android:orientation="vertical"/>
           

2.代碼實作

//從xml加載出LayoutAnimationController對象
LayoutAnimationController controller = AnimationUtils.loadLayoutAnimation(this, R.anim.layout_translate_in);
//設定到ViewGroup中
container.setLayoutAnimation(controller);
//開啟LayoutAnimation
container.startLayoutAnimation();
           

(二)原了解析

1.實作原理

  1. ViewGroup儲存LayoutAnimationController對象,無論是從屬性中解析出來的還是通過setter設定的
  2. 在ViewGroup的dispatchDraw方法繪制children時,為每個child設定controller裡指定的Animation對象進行執行,controller裡控制執行順序、delay等流程
  3. 由(2)可知,布局動畫實質也是在繪制時,為每個child應用了統一的view動畫而已
  4. ViewGroup隻在第一次繪制時會啟動布局動畫,之後會取消掉相應flag,如果想要多次執行,可以手動調用startLayoutAnimation()

2.源碼分析

(1)解析設定LayoutAnimationController

//ViewGroup解析
case R.styleable.ViewGroup_layoutAnimation:
    int id = a.getResourceId(attr, -1);
    if (id > 0) {
        setLayoutAnimation(AnimationUtils.loadLayoutAnimation(mContext, id));//解析,設定
    }
 
//代碼設定
public void setLayoutAnimation(LayoutAnimationController controller) {
    mLayoutAnimationController = controller;
    if (mLayoutAnimationController != null) {
        mGroupFlags |= FLAG_RUN_ANIMATION;//加入辨別位
    }
}
 
//手動執行動畫
public void startLayoutAnimation() {
    if (mLayoutAnimationController != null) {
        mGroupFlags |= FLAG_RUN_ANIMATION;//加入辨別位
        requestLayout();//重新布局繪制
    }
}
           

通過AnimationUtils.loadLayoutAnimation()解析xml生成LayoutAnimationController對象

通過setLayoutAnimation設定到ViewGroup中,并加入FLAG_RUN_ANIMATION辨別位,後續繪制時使用

手動啟動動畫時,requestLayout導緻重新布局繪制

(2)dispatchDraw()

//dispatchDraw()
if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {//需要有FLAG_RUN_ANIMATION辨別并且設定了controller對象才會進行動畫
    final boolean buildCache = !isHardwareAccelerated();
    for (int i = 0; i < childrenCount; i++) {
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
            final LayoutParams params = child.getLayoutParams();
            attachLayoutAnimationParameters(child, params, i, childrenCount);将view的參數(index,count等)設定params裡,用于計算每個view的動畫參數
            bindLayoutAnimation(child);//将Animation綁定到每個view上
        }
    }
	...
    controller.start();//開啟動畫

    mGroupFlags &= ~FLAG_RUN_ANIMATION;//取消掉辨別位,之後不會再啟動動畫(除非手動啟動)
    mGroupFlags &= ~FLAG_ANIMATION_DONE;

    ...
}
 
private void bindLayoutAnimation(View child) {
    Animation a = mLayoutAnimationController.getAnimationForView(child);//根據params拿到布局動畫裡設定的Animation對象
    child.setAnimation(a);//設定給view
}
           

在ViewGroup調用dispatchDraw繪制每個child時,會根據是否有FLAG_RUN_ANIMATION和設定的controller對象來啟動動畫

啟動動畫時,其實就是把指定的Animation對象設定給每個child,其實本質就是給每個view設定view動畫

在設定布局動畫時會加入FLAG_RUN_ANIMATION辨別,隻在ViewGroup第一次繪制時開啟動畫,之後會取消掉辨別,後續再想啟動布局動畫,可以手動startLayoutAnimation()

三.屬性動畫

上面說到的view動畫是基于view的,且在繪制時改變view的展示效果

android提供的屬性動畫其實是一個變化過程,而與view無依賴關系,我們可以通過一個變化過程來完成自己想要的任何變化效果,包括了view,非常好使用

(一)基本使用

1.代碼實作

//1.值動畫
ValueAnimator animator = ValueAnimator.ofInt(0, 100);
animator.setInterpolator(new OvershootInterpolator());//加速器
animator.setRepeatCount(ValueAnimator.INFINITE);//重複次數
animator.setRepeatMode(ValueAnimator.REVERSE);//重複模式
animator.setStartDelay(1000);//開始延遲
animator.setDuration(1000);//duration
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {//更新監聽
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        int value = (int) animation.getAnimatedValue();//拿到目前值使用
        ViewGroup.LayoutParams params = view.getLayoutParams();
        params.width = params.height = value;
        view.setLayoutParams(params);
    }
});
animator.start();//開啟動畫
 
//2.對象動畫(屬性)
//或用"translationX"代替,確定有setTranslationX(float)方法
ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 0, 500);
animator.setAutoCancel(true);//API 18以上,當有其他處理該target的相應property的animator時,自動cancel掉這個
animator.start();
 
//3.組合動畫
//(1).通過多個PropertyValuesHolder組合(同時執行)
PropertyValuesHolder tranXHolder = PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 0, 500);
PropertyValuesHolder rotationXHolder = PropertyValuesHolder.ofFloat(View.ROTATION_X, 0, 360);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, tranXHolder, rotationXHolder);
animator.start();
 
//(2).通過AnimatorSet(控制順序)
ObjectAnimator transAnim = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 0, 500);
ObjectAnimator rotationAnim = ObjectAnimator.ofFloat(view, View.ROTATION, 0, 360);
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0);

animatorSet = new AnimatorSet();
//同步執行
//animatorSet.playTogether(transAnim, rotationAnim, alphaAnim);
//順序執行
//animatorSet.playSequentially(transAnim, rotationAnim, alphaAnim);
//流程控制(在alpha執行完成後,trans和rotation一起執行)
//animatorSet.play(transAnim).with(rotationAnim).after(alphaAnim);
//一個鍊式調用以play為基點展開,假如如果需要多個after或before,可以使用多個鍊式調用(同一個animator對象會當作一個)
animatorSet.play(transAnim).after(rotationAnim);
animatorSet.play(rotationAnim).after(alphaAnim);
//AnimatorSet的duration會覆寫每個Animator的duration
animatorSet.setDuration(1000);
animatorSet.start();
 
//4.View提供的簡易動畫(實質使用的也是屬性動畫ValueAnimator)
//x軸平移到500、旋轉到360度,同時執行
view.animate().translationX(500).rotation(360).setDuration(1000).start();
           
  1. ValueAnimator:一個值變化動畫,模拟了值的變化過程,我們可以拿到任意時刻的變化值去完成我們自己的效果
  2. ObjectAnimator:對象屬性變化動畫,可以設定一個target和property,即把值的變化過程,通過property對象應用到target上,比如view的動畫操作,就變的易如反掌
  3. AnimatorSet:管理多個Animator的管理類,可以控制多個動畫的執行順序等各種方面
  4. PropertyValuesHolder:是Animator類真正建構動畫資訊的類,是以我們可以向一個Animator類裡添加多個PropertyValuesHolder對象,來實作多個動畫同時執行的效果
  5. ViewPropertyAnimator:View類提供的一個管理View動畫的管理類,内部會封裝view的一些基本動畫操作,如平移、縮放等,我們可以通過簡易api建立簡單的動畫效果執行,執行時本質用的也是ValueAnimator類實作

2.xml實作

//在res/animator檔案夾下定義
<set xmlns:android="http://schemas.android.com/apk/res/android"//AnimatorSet
    android:duration="1000"
    android:ordering="sequentially">
    <animator//ValueAnimator
        android:valueFrom="500"
        android:valueTo="1000" />
    <animator
        android:valueFrom="0"
        android:valueTo="100" />
</set>
           
//代碼調用
Animator animator = AnimatorInflater.loadAnimator(this, R.animator.alpha_animator);
           

通過工具類AnimatorInflater.loadAnimator方法可以解析生成Animator對象

(二)原了解析

1.實作原理

Android 動畫 原了解析一.View動畫二.布局動畫三.屬性動畫四.動畫對比
  1. 通過建立(有靜态工廠方法)Animator類來設定動畫的各種參數,包括duration、repeat、interpolator等,還有值變化參數
  2. 所有的值變化參數會交由PropertyValuesHolder的靜态工廠方法,建構出相應的PropertyValuesHolder對象,該類是真正用于管理、計算、更新值變化的類
  3. PropertyValuesHolder内部,會将傳入的值變化參數,封裝成一個個相應的KeyFrame對象,維護在一個相應的KeyFrameSet對象中,儲存下來,擷取目前值時使用
  4. KeyFrameSet在擷取目前值參數時,會使用維護的KeyFrame清單,計算得到目前的值
  5. AnimationHandler類為線程單例類,其内部使用的是一個線程單例類Choreographer,用來綁定目前線程looper,發送消息,執行回調的,回調的callback就是AnimationFrameCallback類,Animator實作了該接口
  6. 在動畫開始(start())時,會使用AnimationHandler發送小心,callback為Animator自己,每次回調時,根據目前時間比例fraction,計算目前值,然後繼續發送消息,直到動畫完成

2.源碼分析

這裡我們先以建立一個簡單的ValueAnimator為例,逐漸分析下它的建立、時間變化、值更新過程:

(1)建立

//靜态工廠方法建立
public static ValueAnimator ofInt(int... values) {
    ValueAnimator anim = new ValueAnimator();
    anim.setIntValues(values);
    return anim;
}
 
//将這些值變化,生成Holder對象儲存
public void setIntValues(int... values) {
    ...
	setValues(PropertyValuesHolder.ofInt("", values));
    mInitialized = false;//flag
}
 
//Holder中靜态工廠方法生成相應的Holder對象
public static PropertyValuesHolder ofInt(String propertyName, int... values) {
    return new IntPropertyValuesHolder(propertyName, values);
}
//建構Holder時,将values建構成List<KeyFrame>對象,儲存到KeyFrameSet對象中
public IntPropertyValuesHolder(String propertyName, int... values) {
    super(propertyName);//儲存property相關資訊
    setIntValues(values);//設定值變化參數
}
public void setIntValues(int... values) {
    mValueType = int.class;
    mKeyframes = KeyFrameSet.ofInt(values);//生成相應的KeyFrameSet對象
}
 
//靜态工廠方法生成KeyFrameSet對象
public static KeyframeSet ofInt(int... values) {
    int numKeyframes = values.length;
    IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];//将每個階段的值變化儲存為KeyFrame對象
    if (numKeyframes == 1) {
        keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
        keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
    } else {
        keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);//第一幀fraction為0
        for (int i = 1; i < numKeyframes; ++i) {
            keyframes[i] =
                    (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);//每一幀fraction為index/(count-1),按比例分
        }
    }
    return new IntKeyframeSet(keyframes);//将這些值變化幀儲存到KeyFrameSet中
}
 
//靜态工廠方法生成KeyFrame對象
public static Keyframe ofInt(float fraction, int value) {
    return new IntKeyframe(fraction, value);
}
IntKeyframe(float fraction, int value) {
    mFraction = fraction;//儲存fraction
    mValue = value;//儲存value
	...
}
           

(2)時間變化

//start()啟動動畫
private void start(boolean playBackwards) {
    ...
	//發起回調,進行下一次動畫變化
    AnimationHandler animationHandler = AnimationHandler.getInstance();
	//添加Animator為callback,進行下次動畫變化
    animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));

    if (mStartDelay == 0 || mSeekFraction >= 0) {
        startAnimation();//開啟動畫
        if (mSeekFraction == -1) {
            setCurrentPlayTime(0);//執行第一幀
        } else {
            setCurrentFraction(mSeekFraction);
        }
    }
}
 
//添加回調,發起消息
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
    if (mAnimationCallbacks.size() == 0) {
        getProvider().postFrameCallback(mFrameCallback);//使用Choreographer,通過handler發起message,回調到FrameCallback
    }
    if (!mAnimationCallbacks.contains(callback)) {
        mAnimationCallbacks.add(callback);//添加callback
    }
	...
}
 
//FrameCallback處理
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
        doAnimationFrame(getProvider().getFrameTime());//執行目前動畫變化,進行更新
        if (mAnimationCallbacks.size() > 0) {
            getProvider().postFrameCallback(this);//繼續發起message,回調到自己,直到所有動畫執行完畢,mAnimationCallbacks就為空集合了,就不用發起message了
        }
    }
};
           

(3)值更新

值更新

//1.開始動畫
 private void start(boolean playBackwards) {
    ...
	//加入回調,繪制每一幀
    AnimationHandler animationHandler = AnimationHandler.getInstance();
    animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));
	//立即開始,繪制第一幀或指定幀
    if (mStartDelay == 0 || mSeekFraction >= 0) {
        startAnimation();//開始動畫,初始化一些屬性
        if (mSeekFraction == -1) {
            setCurrentPlayTime(0);//繪制第一幀,時間相當于0(剛開始)
        } else {
            setCurrentFraction(mSeekFraction);
        }
    }
}
 
public void setCurrentPlayTime(long playTime) {
    float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;//根據duration計算指定時間的fraction
    setCurrentFraction(fraction);//調用該方法可以從任一時間點執行
}
 
public void setCurrentFraction(float fraction) {
    ...
	//校準fraction
    final float currentIterationFraction = getCurrentIterationFraction(fraction);
	//調用該方法進行統一的更新步驟
    animateValue(currentIterationFraction);
}
 
//2.callback回調doAnimationFrame()方法,繪制每一幀
public final void doAnimationFrame(long frameTime) {
    AnimationHandler handler = AnimationHandler.getInstance();
    if (mLastFrameTime == 0) {
        //首次繪制(有開始延遲)
        handler.addOneShotCommitCallback(this);
        if (mStartDelay > 0) {
            startAnimation();
        }
        ...
    }
    mLastFrameTime = frameTime;
	//處理暫停、重新開機等狀态
    if (mPaused) {
        mPauseTime = frameTime;
        handler.removeCallback(this);
        return;
    } else if (mResumed) {
        mResumed = false;
        ...
        handler.addOneShotCommitCallback(this);
    }
    
	//根據目前時間,調用animateBasedOnTime()方法計算fraction
    final long currentTime = Math.max(frameTime, mStartTime);
    boolean finished = animateBasedOnTime(currentTime);

	//處理結束動畫
    if (finished) {
        endAnimation();
    }
}
 
boolean animateBasedOnTime(long currentTime) {
    boolean done = false;
    if (mRunning) {
		//根據duration、repeat,計算目前時間的fraction
        final long scaledDuration = getScaledDuration();
        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);
        ...
        mOverallFraction = clampFraction(fraction);
		//校準fraction,最終也調用到animateValue()方法統一處理fraction變化
        float currentIterationFraction = getCurrentIterationFraction(mOverallFraction);
        animateValue(currentIterationFraction);
    }
    return done;
}
           

由代碼可知,無論是動畫剛開始第一幀,還是callback回調繪制每一幀,都會根據目前時間和動畫起始時間以及repeat、duration等資訊,算出目前的時間比例fraction,然後統一調用animateValue(fraction)進行動畫更新

在callback回調到doAnimationFrame()方法繪制每一幀時,會實時處理動畫的各種狀态,比如pause,resume,end等

開始動畫時,會進行一些屬性的初始化,即調用initAnimation方法,這個方法會初始化PropertyValuesHolder等重要屬性,在具體更新動畫前,我們要看一下,究竟需要初始化哪些屬性:

initAnimation

//1.ValueAnimator
void initAnimation() {
    if (!mInitialized) {
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].init();//調用所有PropertyValuesHolder的init方法初始化
        }
        mInitialized = true;
    }
}
 
//2.ObjectAnimator
void initAnimation() {
    if (!mInitialized) {
        final Object target = getTarget();
        if (target != null) {
            final int numValues = mValues.length;
            for (int i = 0; i < numValues; ++i) {
                mValues[i].setupSetterAndGetter(target);//調用所有PropertyValuesHolder的該方法,進行屬性setter/getter方法的反射初始化
            }
        }
        super.initAnimation();//super
    }
}
 
//3.PropertyValuesHolder的init方法
void init() {
	//初始化TypeEvaluator
    if (mEvaluator == null) {
        mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
                (mValueType == Float.class) ? sFloatEvaluator :
                null;
    }
	//将Evaluator傳遞到KeyFrameSet中使用
    if (mEvaluator != null) {
        mKeyframes.setEvaluator(mEvaluator);
    }
}
 
//4.PropertyValuesHolder初始化setter/getter
//FloatPropertyValuesHolder.setupSetter
void setupSetter(Class targetClass) {
	//傳遞進來的就是Property對象
    if (mProperty != null) {
        return;
    }
    // Check new static hashmap<propName, int> for setter method
    synchronized (sJNISetterPropertyMap) {
		//get in cache
        ...
        if (!wasInMap) {
			//根據指定的propertyName生成反射setter名:setXxx
            String methodName = getMethodName("set", mPropertyName);
            try {
				//反射查找target類中的該方法Method對象
                mJniSetter = nGetFloatMethod(targetClass, methodName);
            }
			//cache
			...
        }
    }
    ...
}
           

由代碼可知,ValueAnimator初始化主要是初始化PropertyValuesHolder,主要是初始化TypeEvaluator,用于計算值變化

ObjectAnimator除了會處理上述屬性,還會處理property相關的内容,正如前面所說,ObjectAnimator可以指定Property對象或者propertyName,來更新target,當使用Property對象時不作處理,而當使用propertyName時,就需要根據這個name,反射将相應的Method找到,更新時用其來更新target

說完了初始化,下面就來看看,當拿到fraction後,如何應用到動畫上的:

update

//1.ValueAnimator
void animateValue(float fraction) {
    fraction = mInterpolator.getInterpolation(fraction);//Interpolator變速器計算目前速率下的fraction
    mCurrentFraction = fraction;
    int numValues = mValues.length;
    for (int i = 0; i < numValues; ++i) {
        mValues[i].calculateValue(fraction);//使用fraction,更新每個PropertyValuesHolder的目前狀态值
    }
	//listener->update
    if (mUpdateListeners != null) {
        int numListeners = mUpdateListeners.size();
        for (int i = 0; i < numListeners; ++i) {
            mUpdateListeners.get(i).onAnimationUpdate(this);
        }
    }
}
 
//2.ObjectAnimator
void animateValue(float fraction) {
    ...
    super.animateValue(fraction);//super
    int numValues = mValues.length;
    for (int i = 0; i < numValues; ++i) {
        mValues[i].setAnimatedValue(target);//更新完每個PropertyValuesHolder的狀态值後,将每個狀态應用到target上
    }
}
 
//3.應用到target上
//FloatPropertyValueHolder
void setAnimatedValue(Object target) {
	//直接使用指定的property對象進行應用
    if (mFloatProperty != null) {
        mFloatProperty.setValue(target, mFloatAnimatedValue);
        return;
    }
    ...
	//反射調用Method,應用到target上
    if (mSetter != null) {
        try {
            mTmpValueArray[0] = mFloatAnimatedValue;
            mSetter.invoke(target, mTmpValueArray);
        } catch (InvocationTargetException e) {
            Log.e("PropertyValuesHolder", e.toString());
        } catch (IllegalAccessException e) {
            Log.e("PropertyValuesHolder", e.toString());
        }
    }
}
 
//4.Property
//View.TRANSLATION_X
public static final Property<View, Float> TRANSLATION_X = new FloatProperty<View>("translationX") {
    @Override
    public void setValue(View object, float value) {
        object.setTranslationX(value);//直接調用view的setTranslationX方法進行移動
    }
	...
};
           

由代碼可知,更新值時,Animator會首先用我們指定的Interpolation變速器,計算目前速率下fraction的真實值

然後使用目前fraction去更新所有PropertyValuesHolder,即所有動畫過程的目前值(calculateValue)

最後通知Listener進行更新,比如ValueAnimator中,我們通過listener的update方法來拿到目前真實值,來進行處理

而對于ObjectAnimator來說,我們給其設定的Property對象,或者通過propertyName反射出來的setter方法,在計算完所有PropertyValuesHolder值後,就可以直接應用到target上了,不需要我們自己處理

下面就來看看,PropertyValuesHolder是如何根據fraction來更新狀态值的:

calculateValue

//1.calculateValue
//FloatPropertyValuesHolder
void calculateValue(float fraction) {
	//直接使用的是Holder維護的KeyFrameSet擷取最新值,并儲存下來
    mFloatAnimatedValue = mFloatKeyframes.getFloatValue(fraction);
}
 
//2.KeyFrameSet.getValue
//FloatKetFrameSet
public float getFloatValue(float fraction) {
    //隻有兩幀的情況、fraction大于1的情況等
	//普通情況
    FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);//前一幀
    for (int i = 1; i < mNumKeyframes; ++i) {
        FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i);//後一幀
        if (fraction < nextKeyframe.getFraction()) {//找到目前fraction位于之間的兩個幀
            final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
            float intervalFraction = (fraction - prevKeyframe.getFraction()) /
                (nextKeyframe.getFraction() - prevKeyframe.getFraction());//根據目前fraction,計算在兩幀之間的fraction
            float prevValue = prevKeyframe.getFloatValue();
            float nextValue = nextKeyframe.getFloatValue();
            // Apply interpolator on the proportional duration.
            if (interpolator != null) {
                intervalFraction = interpolator.getInterpolation(intervalFraction);
            }
            return mEvaluator == null ?
                    prevValue + intervalFraction * (nextValue - prevValue) :
                    ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
                        floatValue();//用TypeEvaluator,計算preValue至nextValue之間,比例為fraction的值
        }
        prevKeyframe = nextKeyframe;
    }
    // shouldn't get here
    return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).floatValue();
}
           

由代碼可知,PropertyValuesHolder計算值,其實就是通過一開始建立的幀集合(KeyFrameSet)去計算目前值

KeyFrameSet使用其維護的List,首先通過目前fraction,找到所在區間的前後兩個KeyFrame,然後計算fraction在其内部的intervalFraction

通過TypeEvaluator,得到preValue和nextValue之間,比例為intervalFraction的值,就是目前的狀态值

至此,一次動畫值的更新就完成了,每次callback回調改變一次,連起來的繪制效果,就是動畫

四.動畫對比

以上就是android提供的View動畫和屬性動畫的使用與原理,簡單做個對比:

  1. 使用上,兩者提供的基本功能都差不多,也都可以通過java代碼和xml兩種形式定義和生成
  2. 在動畫集合上來說,AnimationSet和AnimatorSet都可以管理多個動畫,但是AnimatorSet支援順序控制等方式,可以更加靈活的組合動畫
  3. View動畫是與View強行綁定的,隻能用于View;而屬性動畫是一個動畫過程的抽象,既可以友善的完成View動畫的效果,也可以自己定義任何的動畫效果
  4. View動畫是基于draw過程的,即動畫效果是在View的draw過程時候完成的,隻改變了繪制區域,而并沒有改變View的真實位置和大小以及相應區域,容易造成一定的混亂;而屬性動畫改變的是View真實的位置和大小
  5. View動畫支援了布局動畫,屬性動畫沒有,可以通過自己的邏輯控制來實作

綜上所述,其實屬性動畫幾乎可以支援全部情況下的動畫,可以替代View動畫,也是現在用的最多的。