Android動(dòng)畫(huà)繪制原理(源碼解析)

前言

Android 平臺(tái)提供了三類(lèi)動(dòng)畫(huà)攻泼,一類(lèi)是 Tween 動(dòng)畫(huà)-Animation胖秒,即通過(guò)對(duì)場(chǎng)景里的對(duì)象不斷做圖像變換 ( 平移雷厂、縮放凤藏、旋轉(zhuǎn) ) 產(chǎn)生動(dòng)畫(huà)效果奸忽;第二類(lèi)是 Frame 動(dòng)畫(huà),即順序播放事先做好的圖像揖庄,跟電影類(lèi)似栗菜。最后一種就是3.0之后才出現(xiàn)的屬性動(dòng)畫(huà)PropertyAnimator(在下文我們講幀動(dòng)畫(huà)和補(bǔ)間動(dòng)畫(huà)統(tǒng)一稱為View動(dòng)畫(huà))。如果有人對(duì)ViewGroup內(nèi)部View使用過(guò)View動(dòng)畫(huà)的還知道有l(wèi)ayout-animation蹄梢。

大家對(duì)這三種動(dòng)畫(huà)基本都能熟練的使用疙筹,那么…...?

  • 想知道動(dòng)畫(huà)與界面渲染與屏幕刷新有著什么樣的關(guān)系担忧?
  • 想知道屬性動(dòng)畫(huà)為什么會(huì)發(fā)生內(nèi)存泄露么?

因?yàn)楸疚恼轮袝?huì)有一些屏幕刷新赴肚、Vsync信號(hào)相關(guān)的知識(shí)點(diǎn)避除,讀過(guò)我寫(xiě)的 Android的16ms和垂直同步以及三重緩存Android系統(tǒng)的編舞者Choreographer 這兩篇文章的同學(xué)會(huì)可能會(huì)更容易了解本文章。

接下來(lái)拿起我們的鍵盤(pán)翘盖、鼠標(biāo)和顯示器桂塞,我們將探索從Android源碼(android-23)的角度去探索動(dòng)畫(huà)的實(shí)現(xiàn)~!

動(dòng)畫(huà)的介紹

Drawable Animation

也就是所謂的幀動(dòng)畫(huà)馍驯,F(xiàn)rame動(dòng)畫(huà)阁危。指通過(guò)指定每一幀的圖片和播放時(shí)間,有序的進(jìn)行播放而形成動(dòng)畫(huà)效果汰瘫。

Tween Animation

視圖動(dòng)畫(huà)狂打,也就是所謂補(bǔ)間動(dòng)畫(huà),Tween動(dòng)畫(huà)混弥。指通過(guò)指定View的初始狀態(tài)趴乡、變化時(shí)間、方式蝗拿,通過(guò)一系列的算法去進(jìn)行圖形變換晾捏,從而形成動(dòng)畫(huà)效果,主要有Alpha哀托、Scale仓手、Translate、Rotate四種效果嗽冒。注意:只是在視圖層實(shí)現(xiàn)了動(dòng)畫(huà)效果,并沒(méi)有真正改變View的屬性辛慰。

Property Animation

屬性動(dòng)畫(huà),通過(guò)不斷的改變View的屬性,不斷的重繪而形成動(dòng)畫(huà)效果帅腌。相比于視圖動(dòng)畫(huà)驰弄,View的屬性是真正改變了戚篙。注意:Android 3.0(API 11)以上才支持岔擂。

接下來(lái)我們按照倒敘來(lái)揭開(kāi)一個(gè)一個(gè)動(dòng)畫(huà)的神秘面紗_

Property Animation

屬性動(dòng)畫(huà)的優(yōu)點(diǎn)

  • 屬性動(dòng)畫(huà)顧名思義就是改變了View的屬性塑崖,而不僅僅是繪制的位置规婆。
  • 屬性動(dòng)畫(huà)可以操作的屬性相比于補(bǔ)間動(dòng)畫(huà)大大增加抒蚜,除了常用的平移嗡髓、旋轉(zhuǎn)饿这、縮放撞秋、透明度還有顏色等,基本上能通過(guò)View.setXX來(lái)設(shè)置的屬性,屬性動(dòng)畫(huà)都可以操作,這大大增加了我們?cè)谑褂脛?dòng)畫(huà)時(shí)的靈活性。
  • 屬性動(dòng)畫(huà)分為ObjectAnimator和ValueAnimator,其中ObjectAnimator是繼承于ValueAnimator廓八。

ValueAnimator

ValueAnimator并不會(huì)改變屬性的大小剧蹂,他只是在一段時(shí)間生成某些值烦却。我們需要做的是監(jiān)聽(tīng)這些值得改變從而該改變View的屬性其爵,進(jìn)而產(chǎn)生動(dòng)畫(huà)效果摩渺。

下邊的動(dòng)畫(huà)就是對(duì)mView進(jìn)行平移:

ValueAnimator animator = ValueAnimator.ofFloat(0, 1000);  
anim.addUpdateListener(new AnimatorUpdateListener() {  
    @Override 
    public void onAnimationUpdate(ValueAnimator animation) {                
    mView.setTranslationX(animation.getAnimatedValue());
    }  
}); 
animator.setDuration(1000).start()

ObjectAnimator

在ValueAnimator的基礎(chǔ)之上摇幻,對(duì)控件的某個(gè)屬性執(zhí)行一次動(dòng)畫(huà)。

相同的對(duì)mView進(jìn)行平移的動(dòng)畫(huà)ObjectAnimator是這樣實(shí)現(xiàn)的:

ObjectAnimator animator=ObjectAnimator.ofFloat (mView,"translationX",0,1000);
animator.setDuration (1000);
animator.start ();

PropertyAnimation流程圖

property-animation.png

屬性動(dòng)畫(huà)代碼的執(zhí)行過(guò)程

start

ObjectAnimator.start

public final class ObjectAnimator extends ValueAnimator {
    /***部分代碼省略***/
    @Override
    public void start() {
        //首先依次判斷了當(dāng)前動(dòng)畫(huà)、等待的動(dòng)畫(huà)憨栽、延遲的動(dòng)畫(huà)中是否有和當(dāng)前動(dòng)畫(huà)相同的動(dòng)畫(huà)
        //若有就把相同的動(dòng)畫(huà)取消掉
        // See if any of the current active/pending animators need to be canceled
        AnimationHandler handler = sAnimationHandler.get();
        if (handler != null) {
            int numAnims = handler.mAnimations.size();
            for (int i = numAnims - 1; i >= 0; i--) {
                if (handler.mAnimations.get(i) instanceof ObjectAnimator) {
                    ObjectAnimator anim = (ObjectAnimator) handler.mAnimations.get(i);
                    if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
                        anim.cancel();
                    }
                }
            }
           /***部分代碼省略***/
        }
        /***部分代碼省略***/
        //然后調(diào)用ValueAnimator.start()方法
        super.start();
    }
}

ValueAnimator.start

public class ValueAnimator extends Animator {
    /***部分代碼省略***/
    protected static ThreadLocal<AnimationHandler> sAnimationHandler =
            new ThreadLocal<AnimationHandler>();
    //保證每個(gè)線程有且只有一個(gè)AnimationHandler
    private static AnimationHandler getOrCreateAnimationHandler() {
        AnimationHandler handler = sAnimationHandler.get();
        if (handler == null) {
            handler = new AnimationHandler();
            sAnimationHandler.set(handler);
        }
        return handler;
    }

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

    private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        /***部分代碼省略***/
        //創(chuàng)建或者獲取animationHandler實(shí)例
        AnimationHandler animationHandler = getOrCreateAnimationHandler();
        animationHandler.mPendingAnimations.add(this);
        if (mStartDelay == 0) {
            // This sets the initial value of the animation, prior to actually starting it running
            if (prevPlayingState != SEEKED) {
                setCurrentPlayTime(0);
            }
            mPlayingState = STOPPED;
            mRunning = true;
            //回調(diào)監(jiān)聽(tīng)器,通知?jiǎng)赢?huà)開(kāi)始
            notifyStartListeners();
        }
        //開(kāi)始動(dòng)畫(huà)
        animationHandler.start();
    }
    //回調(diào)監(jiān)聽(tīng)器锯蛀,通知?jiǎng)赢?huà)開(kāi)始
    private void notifyStartListeners() {
        if (mListeners != null && !mStartListenersCalled) {
            ArrayList<AnimatorListener> tmpListeners =
                    (ArrayList<AnimatorListener>) mListeners.clone();
            int numListeners = tmpListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                tmpListeners.get(i).onAnimationStart(this);
            }
        }
        mStartListenersCalled = true;
    }

    public void setCurrentPlayTime(long playTime) {
        float fraction = mUnscaledDuration > 0 ? (float) playTime / mUnscaledDuration : 1;
        setCurrentFraction(fraction);
    }

    public void setCurrentFraction(float fraction) {
        //初始化動(dòng)畫(huà)
        initAnimation();
        if (fraction < 0) {
            fraction = 0;
        }
        /***部分代碼省略***/
    }
}

AnimationHandler.start

public class ValueAnimator extends Animator {
    /***部分代碼省略***/
    protected static class AnimationHandler implements Runnable {
        /***部分代碼省略***/

        //開(kāi)始動(dòng)畫(huà)
        public void start() {
            scheduleAnimation();
        }

        //發(fā)送VSYNC信號(hào)回調(diào)請(qǐng)求
        private void scheduleAnimation() {
            if (!mAnimationScheduled) {
                mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
                mAnimationScheduled = true;
            }
        }

        // Called by the Choreographer.
        //Choreographer的VSYNC信號(hào)回調(diào)
        @Override
        public void run() {
            mAnimationScheduled = false;
            doAnimationFrame(mChoreographer.getFrameTime());
        }

        private void doAnimationFrame(long frameTime) {
            /***部分代碼省略***/

            // Now process all active animations. The return value from animationFrame()
            // tells the handler whether it should now be ended
            int numAnims = mAnimations.size();
            for (int i = 0; i < numAnims; ++i) {
                mTmpAnimations.add(mAnimations.get(i));
            }
            for (int i = 0; i < numAnims; ++i) {
                ValueAnimator anim = mTmpAnimations.get(i);
                //執(zhí)行動(dòng)畫(huà)
                //doAnimationFrame方法返回ture,則該動(dòng)畫(huà)添加在mEndingAnims隊(duì)列中進(jìn)行end操作
                if (mAnimations.contains(anim) && anim.doAnimationFrame(frameTime)) {
                    mEndingAnims.add(anim);
                }
            }
            /***部分代碼省略***/
            //循環(huán)執(zhí)行,直到endAnimation將mAnimations置空
            if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {
                scheduleAnimation();
            }
        }
    }
}

init

ObjectAnimator.initAnimation

public final class ObjectAnimator extends ValueAnimator {
    /***部分代碼省略***/
    @Override
    void initAnimation() {
        if (!mInitialized) {
            // mValueType may change due to setter/getter setup; do this before calling super.init(),
            // which uses mValueType to set up the default type evaluator.
            final Object target = getTarget();
            if (target != null) {
                final int numValues = mValues.length;
                for (int i = 0; i < numValues; ++i) {
                    mValues[i].setupSetterAndGetter(target);
                }
            }
            super.initAnimation();
        }
    }
}

setupSetterAndGetter

public final class ObjectAnimator extends ValueAnimator {
    /***部分代碼省略***/
    void setupSetterAndGetter(Object target) {
        mKeyframes.invalidateCache();
        if (mProperty != null) {
            /***部分代碼省略***/
        }
        // We can't just say 'else' here because the catch statement sets mProperty to null.
        if (mProperty == null) {
            Class targetClass = target.getClass();
            if (mSetter == null) {
                //初始化mSetter
                setupSetter(targetClass);
            }
            /***部分代碼省略***/
        }
    }
    //初始化mSetter用于以后反射執(zhí)行g(shù)et、set操作
    void setupSetter(Class targetClass) {
        Class<?> propertyType = mConverter == null ? mValueType : mConverter.getTargetType();
        mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", propertyType);
    }
}

animation

ValueAnimator.doAnimationFrame

public class ValueAnimator extends Animator {
    /***部分代碼省略***/
    final boolean doAnimationFrame(long frameTime) {
        /***部分代碼省略***/
        return animationFrame(currentTime);
    }

    boolean animationFrame(long currentTime) {
        boolean done = false;
        switch (mPlayingState) {
        case RUNNING:
        case SEEKED:
            /***部分代碼省略***/
            if (fraction >= 1f) {
                //mCurrentIteration是否等于mRepeatCount
                if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
                    // Time to repeat
                    /***部分代碼省略***/
                } else {
                    //執(zhí)行完這次,該動(dòng)畫(huà)結(jié)束
                    done = true;
                    fraction = Math.min(fraction, 1.0f);
                }
            }
            if (mPlayingBackwards) {
                fraction = 1f - fraction;
            }
            //設(shè)置View的屬性值
            animateValue(fraction);
            break;
        }

        return done;
    }
}

ValueAnimator.animateValue

public class ValueAnimator extends Animator {
    /***部分代碼省略***/
    void animateValue(float fraction) {
        fraction = mInterpolator.getInterpolation(fraction);
        mCurrentFraction = fraction;
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            //PropertyValuesHolder.calculateValue就是計(jì)算每幀動(dòng)畫(huà)所對(duì)應(yīng)的值
            mValues[i].calculateValue(fraction);
        }
        if (mUpdateListeners != null) {
            int numListeners = mUpdateListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                //屬性值得改變的回調(diào)
                mUpdateListeners.get(i).onAnimationUpdate(this);
            }
        }
    }
}

ObjectAnimator.animateValue

public final class ObjectAnimator extends ValueAnimator {
    /***部分代碼省略***/
    @Override
    void animateValue(float fraction) {
        final Object target = getTarget();
        if (mTarget != null && target == null) {
            // We lost the target reference, cancel and clean up.
            cancel();
            return;
        }
        //ValueAnimator.animateValue方法
        super.animateValue(fraction);
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            //設(shè)置target的屬性值注祖,進(jìn)行View的移動(dòng)是晨,產(chǎn)生動(dòng)畫(huà)
            mValues[i].setAnimatedValue(target);
        }
    }
}

PropertyValuesHolder

PropertyValuesHolder這個(gè)類(lèi)的意義就是罩缴,它其中保存了動(dòng)畫(huà)過(guò)程中所需要操作的屬性和對(duì)應(yīng)的值箫章。我們通過(guò)ofFloat(Object target, String propertyName, float… values)構(gòu)造的動(dòng)畫(huà)炉抒,ofFloat()的內(nèi)部實(shí)現(xiàn)其實(shí)就是將傳進(jìn)來(lái)的參數(shù)封裝成PropertyValuesHolder實(shí)例來(lái)保存動(dòng)畫(huà)狀態(tài)焰薄。在封裝成PropertyValuesHolder實(shí)例以后,后期的各種操作也是以PropertyValuesHolder為主的亩码。

ObjectAnimator.ofFloat

我們先看看我們之前的代碼中構(gòu)造ObjectAnimator的方法:

public final class ObjectAnimator extends ValueAnimator {
    /***部分代碼省略***/
    private ObjectAnimator(Object target, String propertyName) {
        setTarget(target);
        setPropertyName(propertyName);
    }
    public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        //構(gòu)造ObjectAnimator
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
    }
    //設(shè)置屬性值
    public void setPropertyName(@NonNull String propertyName) {
        /***部分代碼省略***/
        mPropertyName = propertyName;
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }

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

構(gòu)造FloatPropertyValueHolder

public class PropertyValuesHolder implements Cloneable {
    /***部分代碼省略***/
    public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
        return new FloatPropertyValuesHolder(propertyName, values);
    }
}
        
public class PropertyValuesHolder implements Cloneable {
    /***部分代碼省略***/
    static class FloatPropertyValuesHolder extends PropertyValuesHolder {
        public FloatPropertyValuesHolder(String propertyName, float... values) {
            super(propertyName);
            setFloatValues(values);
        }
        /***部分代碼省略***/
        @Override
        public void setFloatValues(float... values) {
            super.setFloatValues(values);
            mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
        }
        @Override
        void setAnimatedValue(Object target) {
            /***部分代碼省略***/
            if (mSetter != null) {
                try {
                    mTmpValueArray[0] = mFloatAnimatedValue;
                    //反射操作target的屬性鞭光,通過(guò)set惰许、get方法
                    mSetter.invoke(target, mTmpValueArray);
                } catch (InvocationTargetException e) {
                    Log.e("PropertyValuesHolder", e.toString());
                } catch (IllegalAccessException e) {
                    Log.e("PropertyValuesHolder", e.toString());
                }
            }
        }
    }
}

屬性動(dòng)畫(huà)的內(nèi)存泄露

  • 上面講述到 ValueAnimator.AnimationHandler.doAnimationFrame 的時(shí)候說(shuō)過(guò),這個(gè)方法會(huì)循環(huán)執(zhí)行佩伤。
  • 因?yàn)?ValueAnimator.AnimationHandler.doAnimationFrame 每次執(zhí)行完動(dòng)畫(huà)(如果動(dòng)畫(huà)沒(méi)有結(jié)束)生巡,都在再一次請(qǐng)求Vsync同步信號(hào)回調(diào)給自己孤荣。
  • Choreographer 的回調(diào)都配post進(jìn)入了當(dāng)前線程的looper隊(duì)列中垃环。
  • mRepeatCount 無(wú)窮大,會(huì)導(dǎo)致該循環(huán)會(huì)一直執(zhí)行下去劲赠,即使關(guān)閉當(dāng)前的頁(yè)面也不會(huì)停止秸谢。

Drawable Animation

幀動(dòng)畫(huà)使用

animalist.xml

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item
        android:drawable="@mipmap/c_1"
        android:duration="50" />
    <item
        android:drawable="@mipmap/c_2"
        android:duration="50" />
     <!--  省略...  -->
    <item
        android:drawable="@mipmap/circle_19"
        android:duration="50" />
    <item
        android:drawable="@mipmap/circle_20"
        android:duration="50" />
</animation-list>

layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.ansen.frameanimation.sample.MainActivity">

    <ImageView
        android:id="@+id/image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/animlist" />

</LinearLayout>

java類(lèi)使用

ImageView image = (ImageView) findViewById(R.id.image);
AnimationDrawable animationDrawable = (AnimationDrawable) image.getDrawable();
animationDrawable.start();

DrawableAnimation流程圖

drawable-animation.png

幀動(dòng)畫(huà)代碼執(zhí)行過(guò)程

start

public class AnimationDrawable extends DrawableContainer implements Runnable, Animatable {
    /***代碼部分省略***/
    @Override
    public void start() {
        mAnimating = true;

        if (!isRunning()) {
            // Start from 0th frame.
            setFrame(0, false, mAnimationState.getChildCount() > 1
                    || !mAnimationState.mOneShot);
        }
    }
    //設(shè)置當(dāng)前展示第幾幀
    private void setFrame(int frame, boolean unschedule, boolean animate) {
        if (frame >= mAnimationState.getChildCount()) {
            return;
        }
        mAnimating = animate;
        mCurFrame = frame;
        selectDrawable(frame);
        //如果取消下一幀任務(wù),或者這已經(jīng)是當(dāng)前最后一幀最铁,則取消當(dāng)幀動(dòng)畫(huà)任務(wù)
        if (unschedule || animate) {
            unscheduleSelf(this);
        }
        if (animate) {
            // Unscheduling may have clobbered these values; restore them
            mCurFrame = frame;
            mRunning = true;
            scheduleSelf(this, SystemClock.uptimeMillis() + mAnimationState.mDurations[frame]);
        }
    }
    //安排動(dòng)畫(huà)繪制任務(wù)
    public void scheduleSelf(Runnable what, long when) {
        //該Callback是當(dāng)前AnimationDrawable綁定的View
        final Callback callback = getCallback();
        //判斷當(dāng)前綁定的View是否被銷(xiāo)毀
        if (callback != null) {
            callback.scheduleDrawable(this, what, when);
        }
    }
}

scheduleDrawable

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
    /***部分代碼省略***/   
    @Override
    public void scheduleDrawable(Drawable who, Runnable what, long when) {
        if (verifyDrawable(who) && what != null) {
            final long delay = when - SystemClock.uptimeMillis();
            if (mAttachInfo != null) {
                //請(qǐng)求Vsync信號(hào)同步
                mAttachInfo.mViewRootImpl.mChoreographer.postCallbackDelayed(
                        Choreographer.CALLBACK_ANIMATION, what, who,
                        Choreographer.subtractFrameDelay(delay));
            } else {
                ViewRootImpl.getRunQueue().postDelayed(what, delay);
            }
        }
    }
}

run

public class AnimationDrawable extends DrawableContainer implements Runnable, Animatable {
    /***代碼部分省略***/
    //Choreographer的Vsync同步回調(diào)
    @Override
    public void run() {
        nextFrame(false);
    }
    //繼續(xù)執(zhí)行下一幀動(dòng)畫(huà)
    private void nextFrame(boolean unschedule) {
        int nextFrame = mCurFrame + 1;
        final int numFrames = mAnimationState.getChildCount();
        final boolean isLastFrame = mAnimationState.mOneShot && nextFrame >= (numFrames - 1);

        // Loop if necessary. One-shot animations should never hit this case.
        if (!mAnimationState.mOneShot && nextFrame >= numFrames) {
            nextFrame = 0;
        }
        //新一輪的循環(huán)又開(kāi)始
        setFrame(nextFrame, unschedule, !isLastFrame);
    }
}

其他

CallBack的綁定

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
    /***部分代碼省略***/
    @Deprecated
    public void setBackgroundDrawable(Drawable background) {
        /***部分代碼省略***/
        //清除之前的背景
        if (mBackground != null) {
            mBackground.setCallback(null);
            unscheduleDrawable(mBackground);
        }

        if (background != null) {
            /***部分代碼省略***/
            //Drawable綁定當(dāng)前的View
            background.setCallback(this);
            if (background.isStateful()) {
                background.setState(getDrawableState());
            }
            background.setVisible(getVisibility() == VISIBLE, false);
            mBackground = background;

            applyBackgroundTint();

            if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
                mPrivateFlags &= ~PFLAG_SKIP_DRAW;
                requestLayout = true;
            }
        } else {
            /***部分代碼省略***/
        }

        computeOpaqueFlags();

        if (requestLayout) {
            requestLayout();
        }

        mBackgroundSizeChanged = true;
        invalidate(true);
    }
}

內(nèi)存方面

幀動(dòng)畫(huà)相比較屬性動(dòng)畫(huà)而言可能會(huì)出現(xiàn)OOM,因?yàn)樵诩业拿恳粠膱D片會(huì)占用很大的內(nèi)存空間膊夹。

幀動(dòng)畫(huà)不會(huì)出現(xiàn)內(nèi)存泄露的問(wèn)題:

public abstract class Drawable {
    /***部分代碼省略***/
    //持有當(dāng)前View的弱引用放刨,當(dāng)View回收之后嘉栓,沒(méi)辦法繼續(xù)下一幀的展示
    private WeakReference<Callback> mCallback = null;
    public Callback getCallback() {
        if (mCallback != null) {
            return mCallback.get();
        }
        return null;
    }
}

Tween Animation

補(bǔ)間動(dòng)畫(huà)的使用

Animation translateAnimation = new TranslateAnimation(0, 100, 0, 0);
translateAnimation.setDuration(500);
translateAnimation.setInterpolator(new AccelerateInterpolator());
translateAnimation.setFillAfter(true);//設(shè)置動(dòng)畫(huà)結(jié)束后保持當(dāng)前的位置(即不返回到動(dòng)畫(huà)開(kāi)始前的位置)
imageView.startAnimation(translateAnimation);

TweenAnimation流程圖

tween-animation.png

補(bǔ)間動(dòng)畫(huà)代碼的執(zhí)行過(guò)程

start

View:

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {    
    //部分代碼省略
    public void startAnimation(Animation animation) {
        animation.setStartTime(Animation.START_ON_FIRST_FRAME);
        setAnimation(animation);
        invalidateParentCaches();
        invalidate(true);
    }
    void invalidate(boolean invalidateCache) {
        invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
    }
    void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
        if (mGhostView != null) {
            mGhostView.invalidate(true);
            return;
        }

        if (skipInvalidate()) {
            return;
        }

        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
                || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
                || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
                || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
            //部分代碼省略
            // Propagate the damage rectangle to the parent view.
            final AttachInfo ai = mAttachInfo;
            final ViewParent p = mParent;
            if (p != null && ai != null && l < r && t < b) {
                final Rect damage = ai.mTmpInvalRect;
                damage.set(l, t, r, b);
                //執(zhí)行ViewParent的invalidateChild方法
                p.invalidateChild(this, damage);
            }
            //部分代碼省略
        }
    }
}

ViewGroup

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    /***部分代碼省略***/
    public final void invalidateChild(View child, final Rect dirty) {
        ViewParent parent = this;
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            /***部分代碼省略***/
            do {
                /***部分代碼省略***/
                //向頂部的View便利找到根View,即:ViewRootImpl
                //執(zhí)行ViewRootImpl的invalidateChildInParent方法
                parent = parent.invalidateChildInParent(location, dirty);
                if (view != null) {
                    // Account for transform on current parent
                    Matrix m = view.getMatrix();
                    if (!m.isIdentity()) {
                        RectF boundingRect = attachInfo.mTmpTransformRect;
                        boundingRect.set(dirty);
                        m.mapRect(boundingRect);
                        dirty.set((int) (boundingRect.left - 0.5f),
                                (int) (boundingRect.top - 0.5f),
                                (int) (boundingRect.right + 0.5f),
                                (int) (boundingRect.bottom + 0.5f));
                    }
                }
                /***部分代碼省略***/
            } while (parent != null);
        }
    }
}

ViewRootImpl

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {  
    /***部分代碼省略***/

    @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
        /***部分代碼省略***/
        invalidateRectOnScreen(dirty);
        return null;
    }

    private void invalidateRectOnScreen(Rect dirty) {
        /***部分代碼省略***/
        if (!mWillDrawSoon && (intersected || mIsAnimating)) {
            //開(kāi)始View的繪制任務(wù)
            scheduleTraversals();
        }
    }
}

之前寫(xiě)過(guò)一篇文章 ViewRootImpl的獨(dú)白,我不是一個(gè)View(布局篇) 其中 ViewRootImpl對(duì)mView進(jìn)行操作 講述了再ViewRootImpl 中View的繪制叉抡。

draw

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {    
    //部分代碼省略
    public void draw(Canvas canvas) {
        /***部分代碼省略***/
        //如果有子 View(DecorView當(dāng)然有子View)褥民,就會(huì)調(diào)用dispatchDraw() 將繪制事件通知給子 View洗搂。
        //ViewGroup 重寫(xiě)了 dispatchDraw()耘拇,調(diào)用了 drawChild()
        //drawChild() 調(diào)用了子 View 的 draw(Canvas, ViewGroup, long)
    }
    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
        /***部分代碼省略***/
        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);
            concatMatrix = a.willChangeTransformationMatrix();
            if (concatMatrix) {
                mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
            }
            transformToApply = parent.getChildTransformation();
        } else {
            /***部分代碼省略***/
        }
        /***部分代碼省略***/
    }

    private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
            Animation a, boolean scalingRequired) {
        /***部分代碼省略***/
        //繪制動(dòng)畫(huà)的當(dāng)前幀惫叛,并獲取當(dāng)前動(dòng)畫(huà)的狀態(tài)(是否繼續(xù)運(yùn)行)
        boolean more = a.getTransformation(drawingTime, t, 1f);
        if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
            if (parent.mInvalidationTransformation == null) {
                parent.mInvalidationTransformation = new Transformation();
            }
            invalidationTransform = parent.mInvalidationTransformation;
            a.getTransformation(drawingTime, invalidationTransform, 1f);
        } else {
            invalidationTransform = t;
        }
        //如果動(dòng)畫(huà)沒(méi)有結(jié)果
        if (more) {
            if (!a.willChangeBounds()) {
                if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) ==
                        ViewGroup.FLAG_OPTIMIZE_INVALIDATE) {
                    parent.mGroupFlags |= ViewGroup.FLAG_INVALIDATE_REQUIRED;
                } else if ((flags & ViewGroup.FLAG_INVALIDATE_REQUIRED) == 0) {
                    // The child need to draw an animation, potentially offscreen, so
                    // make sure we do not cancel invalidate requests
                    parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
                    //進(jìn)行繪制
                    parent.invalidate(mLeft, mTop, mRight, mBottom);
                }
            } else {
                /***部分代碼省略***/
                //進(jìn)行繪制
                parent.invalidate(left, top, left + (int) (region.width() + .5f),
                        top + (int) (region.height() + .5f));
            }
        }
        return more;
    }
}

running

public abstract class Animation implements Cloneable { 
    /***部分代碼省略***/
    public boolean getTransformation(long currentTime, Transformation outTransformation) {
        /***部分代碼省略***/
        //執(zhí)行時(shí)間是否過(guò)期
        final boolean expired = normalizedTime >= 1.0f;
        mMore = !expired;
        //動(dòng)畫(huà)進(jìn)度為0.0~1.0之間
        if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);

        if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
            /***部分代碼省略***/
            //插值器計(jì)算動(dòng)畫(huà)執(zhí)行進(jìn)度
            final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
            //真正的動(dòng)畫(huà)效果代碼執(zhí)行處(通過(guò)矩陣變化)
            applyTransformation(interpolatedTime, outTransformation);
        }
        //如果動(dòng)畫(huà)繪制完成
        if (expired) {
            //判斷動(dòng)畫(huà)是否需要繼續(xù)循環(huán)
            if (mRepeatCount == mRepeated) {
                if (!mEnded) {
                    mEnded = true;
                    guard.close();
                    fireAnimationEnd();
                }
            } else {
                if (mRepeatCount > 0) {
                    mRepeated++;
                }

                if (mRepeatMode == REVERSE) {
                    mCycleFlip = !mCycleFlip;
                }

                mStartTime = -1;
                mMore = true;

                fireAnimationRepeat();
            }
        }
        if (!mMore && mOneMoreTime) {
            mOneMoreTime = false;
            return true;
        }
        return mMore;
    }
}

其他

通過(guò)代碼分析可以證明補(bǔ)間動(dòng)畫(huà)也不會(huì)存在內(nèi)存泄露的問(wèn)題夸浅,因?yàn)樗强恐鳹iew的繪制來(lái)完成每一幀動(dòng)效的展示题篷。

使用動(dòng)畫(huà)的注意事項(xiàng)

OOM的問(wèn)題

這個(gè)問(wèn)題主要出現(xiàn)在幀動(dòng)畫(huà)中厅目,當(dāng)圖片數(shù)量過(guò)多的且圖片較大的時(shí)候就極易出現(xiàn)OOM损敷,這個(gè)在實(shí)際的開(kāi)發(fā)中要尤其注意拗馒,盡量避免使用幀動(dòng)畫(huà)诱桂。

內(nèi)存泄漏的問(wèn)題

在屬性動(dòng)畫(huà)中有一類(lèi)無(wú)限循環(huán)的動(dòng)畫(huà),這類(lèi)動(dòng)畫(huà)需要在Activity退出時(shí)及時(shí)停止友绝,否則導(dǎo)致Activity無(wú)法釋放從而造成內(nèi)存泄露迁客,通過(guò)驗(yàn)證發(fā)現(xiàn)View動(dòng)畫(huà)(幀動(dòng)畫(huà)和補(bǔ)間動(dòng)畫(huà))并不存在此問(wèn)題辞槐。

兼容性問(wèn)題

動(dòng)畫(huà)在3.0以下的系統(tǒng)上有兼容性問(wèn)題榄檬,在某些特殊場(chǎng)景可能無(wú)法正常工作鹿榜,因此要做好適配工作。

View動(dòng)畫(huà)的問(wèn)題

View動(dòng)畫(huà)對(duì)View的影像做動(dòng)畫(huà),并不是真正的改變View的狀態(tài)怀薛,因此有時(shí)候會(huì)出現(xiàn)動(dòng)畫(huà)完成后View無(wú)法影藏的現(xiàn)象枝恋,即setVisibility(View.GONE)失效了焚碌,這個(gè)時(shí)候只要調(diào)用view.clearAnimation()清除View動(dòng)畫(huà)即可解決此問(wèn)題。

不要使用px

在進(jìn)行動(dòng)畫(huà)的過(guò)程中知押,要盡量使用dp台盯,使用px會(huì)導(dǎo)致在不同的設(shè)備上有不同的效果畏线。

動(dòng)畫(huà)元素的交互

將View移動(dòng)(平移)后寝殴,在Android3.0之前的系統(tǒng)上蚣常,不管是View動(dòng)畫(huà)還是屬性動(dòng)畫(huà)史隆,新位置均無(wú)法觸發(fā)單擊事件,同時(shí)老位置任然可以觸發(fā)單擊事件粘姜。盡管View已經(jīng)在視覺(jué)上不存在了孤紧,將View移回原位置以后号显,原位置的單擊事件繼續(xù)生效躺酒。從3.0開(kāi)始羹应,屬性動(dòng)畫(huà)的單擊事件觸發(fā)位置為移動(dòng)以后的位置,但View動(dòng)畫(huà)仍然在原位置雳刺。

硬件加速

使用動(dòng)畫(huà)的過(guò)程中掖桦,建議開(kāi)啟硬件加速枪汪,這樣會(huì)提交動(dòng)畫(huà)的流暢性料饥。

文章到這里就全部講述完啦,若有其他需要交流的可以留言哦~原叮!~奋隶!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末唯欣,一起剝皮案震驚了整個(gè)濱河市境氢,隨后出現(xiàn)的幾起案子萍聊,更是在濱河造成了極大的恐慌悦析,老刑警劉巖强戴,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骑歹,死亡現(xiàn)場(chǎng)離奇詭異道媚,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)狸剃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)钞馁,“玉大人匿刮,你說(shuō)我怎么就攤上這事熟丸」庑撸” “怎么了纱兑?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵潜慎,是天一觀的道長(zhǎng)铐炫。 經(jīng)常有香客問(wèn)我驳遵,道長(zhǎng),這世上最難降的妖魔是什么唆迁? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任唐责,我火速辦了婚禮鼠哥,結(jié)果婚禮上朴恳,老公的妹妹穿的比我還像新娘于颖。我一直安慰自己森渐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著浪秘,像睡著了一般蚪缀。 火紅的嫁衣襯著肌膚如雪询枚。 梳的紋絲不亂的頭發(fā)上金蜀,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天渊抄,我揣著相機(jī)與錄音护桦,去河邊找鬼二庵。 笑死催享,一個(gè)胖子當(dāng)著我的面吹牛因妙,可吹牛的內(nèi)容都是我干的票髓。 我是一名探鬼主播洽沟,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼玲躯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了橱野?” 一聲冷哼從身側(cè)響起水援,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蜗元,失蹤者是張志新(化名)和其女友劉穎奕扣,沒(méi)想到半個(gè)月后惯豆,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體奔害,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡楷兽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了华临。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芯杀。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖雅潭,靈堂內(nèi)的尸體忽然破棺而出揭厚,到底是詐尸還是另有隱情寻馏,我是刑警寧澤棋弥,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站诚欠,受9級(jí)特大地震影響顽染,放射性物質(zhì)發(fā)生泄漏漾岳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一粉寞、第九天 我趴在偏房一處隱蔽的房頂上張望尼荆。 院中可真熱鬧,春花似錦唧垦、人聲如沸捅儒。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)巧还。三九已至,卻和暖如春坊秸,著一層夾襖步出監(jiān)牢的瞬間麸祷,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工褒搔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留阶牍,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓星瘾,卻偏偏與公主長(zhǎng)得像走孽,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子琳状,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 本篇文章已授權(quán)微信公眾號(hào) guolin_blog (郭霖)獨(dú)家發(fā)布 最近下班時(shí)間都用來(lái)健身還有看書(shū)了磕瓷,博客被晾了一...
    請(qǐng)叫我大蘇閱讀 25,973評(píng)論 8 97
  • 目錄介紹 1.Animation和Animator區(qū)別 2.Animation運(yùn)行原理和源碼分析2.1 基本屬性介...
    楊充211閱讀 1,506評(píng)論 1 2
  • Android 的動(dòng)畫(huà)分類(lèi): View視圖動(dòng)畫(huà)(補(bǔ)間動(dòng)畫(huà) / 逐幀動(dòng)畫(huà)) 屬性動(dòng)畫(huà)Android 動(dòng)畫(huà) 0x01 ...
    simplehych閱讀 552評(píng)論 0 2
  • 本文假定你已經(jīng)對(duì)屬性動(dòng)畫(huà)有了一定的了解,至少使用過(guò)屬性動(dòng)畫(huà)念逞。下面我們就從屬性動(dòng)畫(huà)最簡(jiǎn)單的使用開(kāi)始生宛。 ObjectA...
    楊偉喬閱讀 631評(píng)論 0 0
  • 動(dòng)畫(huà) 動(dòng)畫(huà)主要分為補(bǔ)間(Tween)動(dòng)畫(huà) 、逐幀(Frame )動(dòng)畫(huà)和屬性動(dòng)畫(huà)肮柜。補(bǔ)間(Tween)動(dòng)畫(huà):對(duì)場(chǎng)景內(nèi)的...
    Android小工ing閱讀 277評(píng)論 0 0