動(dòng)畫源碼解析

目錄介紹

  • 1.Animation和Animator區(qū)別
  • 2.Animation運(yùn)行原理和源碼分析
    • 2.1 基本屬性介紹
    • 2.2 如何計(jì)算動(dòng)畫數(shù)據(jù)
    • 2.3 什么是動(dòng)畫更新函數(shù)
    • 2.4 動(dòng)畫數(shù)據(jù)如何存儲(chǔ)
    • 2.5 Animation的調(diào)用
  • 3.Animator運(yùn)行原理和源碼分析
    • 3.1 屬性動(dòng)畫的基本屬性
    • 3.2 屬性動(dòng)畫新的概念
    • 3.3 PropertyValuesHolder作用
    • 3.4 屬性動(dòng)畫start執(zhí)行流程
    • 3.5 屬性動(dòng)畫cancel和end執(zhí)行流程
    • 3.6 屬性動(dòng)畫pase和resume執(zhí)行流程
    • 3.7 屬性動(dòng)畫與View結(jié)合

好消息

  • 博客筆記大匯總【16年3月到至今】涉馁,包括Java基礎(chǔ)及深入知識(shí)點(diǎn)辫愉,Android技術(shù)博客匹舞,Python學(xué)習(xí)筆記等等,還包括平時(shí)開發(fā)中遇到的bug匯總法挨,當(dāng)然也在工作之余收集了大量的面試題,長期更新維護(hù)并且修正,持續(xù)完善……開源的文件是markdown格式的看疗!同時(shí)也開源了生活博客诊胞,從12年起暖夭,積累共計(jì)47篇[近20萬字],轉(zhuǎn)載請(qǐng)注明出處撵孤,謝謝迈着!
  • 鏈接地址:https://github.com/yangchong211/YCBlogs
  • 如果覺得好,可以star一下邪码,謝謝裕菠!當(dāng)然也歡迎提出建議,萬事起于忽微闭专,量變引起質(zhì)變奴潘!
  • 01.動(dòng)畫機(jī)制總結(jié)
  • 02.動(dòng)畫源碼解析

1.Animation和Animator區(qū)別

  • 對(duì)于 Animation 動(dòng)畫:
    • 實(shí)現(xiàn)機(jī)制是,在每次進(jìn)行繪圖的時(shí)候,通過對(duì)整塊畫布的矩陣進(jìn)行變換,從而實(shí)現(xiàn)一種視圖坐標(biāo)的移動(dòng),但實(shí)際上其在 View內(nèi)部真實(shí)的坐標(biāo)位置及其他相關(guān)屬性始終恒定.
  • 對(duì)于 Animator 動(dòng)畫:
    • Animator動(dòng)畫的實(shí)現(xiàn)機(jī)制說起來其實(shí)更加簡單一點(diǎn),因?yàn)樗鋵?shí)只是計(jì)算動(dòng)畫開啟之后,結(jié)束之前,到某個(gè)時(shí)間點(diǎn)得時(shí)候,某個(gè)屬性應(yīng)該有的值,然后通過回調(diào)接口去設(shè)置具體值,其實(shí) Animator 內(nèi)部并沒有針對(duì)某個(gè) view 進(jìn)行刷新,來實(shí)現(xiàn)動(dòng)畫的行為,動(dòng)畫的實(shí)現(xiàn)是在設(shè)置具體值的時(shí)候,方法內(nèi)部自行調(diào)取的類似 invalidate 之類的方法實(shí)現(xiàn)的.也就是說,使用 Animator ,內(nèi)部的屬性發(fā)生了變化
  • 或者更簡單一點(diǎn)說
    • 前者屬性動(dòng)畫,改變控件屬性影钉,(比如平移以后點(diǎn)擊有事件觸發(fā))
    • 后者補(bǔ)間動(dòng)畫画髓,只產(chǎn)生動(dòng)畫效果(平移之后點(diǎn)無事件觸發(fā),前提是你fillafter=true)

2.Animation運(yùn)行原理和源碼分析

2.1 基本屬性介紹

  • 上一篇文章已經(jīng)對(duì)補(bǔ)間動(dòng)畫做了詳細(xì)的說明平委,不過這里還是需要重復(fù)說一下動(dòng)畫屬性的作用
    • mStartTime:動(dòng)畫實(shí)際開始時(shí)間
    • mStartOffset:動(dòng)畫延遲時(shí)間
    • mFillEnabled:mFillBefore及mFillAfter是否使能
    • mFillBefore:動(dòng)畫結(jié)束之后是否需要進(jìn)行應(yīng)用動(dòng)畫
    • mFillAfter:動(dòng)畫開始之前是否需要進(jìn)行應(yīng)用動(dòng)畫
    • mDuration:單次動(dòng)畫運(yùn)行時(shí)長
    • mRepeatMode:動(dòng)畫重復(fù)模式(RESTART奈虾、REVERSE)
    • mRepeatCount:動(dòng)畫重復(fù)次數(shù)(INFINITE,直接值)
    • mInterceptor:動(dòng)畫插間器
    • mBackgroundColor:動(dòng)畫背景顏色
    • mListener:動(dòng)畫開始、結(jié)束愚墓、重復(fù)回調(diào)監(jiān)聽器

2.2 如何計(jì)算動(dòng)畫數(shù)據(jù)

  • 首先進(jìn)入Animation類予权,然后找到getTransformation方法,主要是分析這個(gè)方法邏輯浪册,如圖所示
    • image
  • 那么這個(gè)方法中做了什么呢扫腺?Animation在其getTransformation函數(shù)被調(diào)用時(shí)會(huì)計(jì)算一幀動(dòng)畫數(shù)據(jù),而上面這些屬性基本都是在計(jì)算動(dòng)畫數(shù)據(jù)時(shí)有相關(guān)的作用村象。
  • 第一步:若startTime為START_ON_FIRST_FRAME(值為-1)時(shí)笆环,將startTime設(shè)定為curTime
  • 第二步:計(jì)算當(dāng)前動(dòng)畫進(jìn)度:
    • normalizedTime = (curTime - (startTime + startOffset))/duration
    • 若mFillEnabled==false:將normalisedTime夾逼至[0.0f, 1.0f]
  • 第三步:判斷是否需要計(jì)算動(dòng)畫數(shù)據(jù):
    • 若normalisedTime在[0.0f, 1.0f],需計(jì)算動(dòng)畫數(shù)據(jù)
    • 若normalisedTime不在[0.0f, 1.0f]:
      • normalisedTime<0.0f, 僅當(dāng)mFillBefore==true時(shí)才計(jì)算動(dòng)畫數(shù)據(jù)
      • normalisedTime>1.0f, 僅當(dāng)mFillAfter==true時(shí)才計(jì)算動(dòng)畫數(shù)據(jù)
  • 第四步:若需需要計(jì)算動(dòng)畫數(shù)據(jù):
    • 若當(dāng)前為第一幀動(dòng)畫厚者,觸發(fā)mListener.onAnimationStart
    • 若mFillEnabled==false:將normalisedTime夾逼至[0.0f, 1.0f]
    • 根據(jù)插間器mInterpolator調(diào)整動(dòng)畫進(jìn)度:
    • interpolatedTime = mInterpolator.getInterpolation(normalizedTime)
    • 若動(dòng)畫反轉(zhuǎn)標(biāo)志位mCycleFlip為true躁劣,則
    • interpolatedTime = 1.0 - normalizedTime
    • 調(diào)用動(dòng)畫更新函數(shù)applyTransformation(interpolatedTime, transformation)計(jì)算出動(dòng)畫數(shù)據(jù)
  • 第五步:若夾逼之前normalisedTime大于1.0f, 則判斷是否需繼續(xù)執(zhí)行動(dòng)畫:
    • 已執(zhí)行次數(shù)mRepeatCount等于需執(zhí)行次數(shù)mRepeated
      • 若未觸發(fā)mListener.onAnimationEnd,則觸發(fā)之
    • 已執(zhí)行次數(shù)mRepeatCount不等于需執(zhí)行次數(shù)mRepeated
      • 自增mRepeatCount
      • 重置mStartTime為-1
      • 若mRepeatMode為REVERSE,則取反mCycleFlip
      • 觸發(fā)mListener.onAnimationRepeat

2.3 什么是動(dòng)畫更新函數(shù)

  • 下面我們來看一下getTransformation方法中的這一行代碼applyTransformation(interpolatedTime, outTransformation)告丢,然后進(jìn)去看看這個(gè)方法惭每。如下所示
    • image
  • 這個(gè)方法的用途是干啥呢?從這個(gè)英文解釋中可以得知:getTransform的助手鳖擒。子類應(yīng)該實(shí)現(xiàn)這一點(diǎn),以應(yīng)用給定的內(nèi)插值來應(yīng)用它們的轉(zhuǎn)換烫止。該方法的實(shí)現(xiàn)應(yīng)該總是替換指定的轉(zhuǎn)換或文檔蒋荚,而不是這樣做的。
  • 都知道Animation是個(gè)抽象類馆蠕,接著我們這些逗比程序員可以看看它的某一個(gè)子類期升,比如看看ScaleAnimation中的applyTransformation方法吧。
    • 是否設(shè)定縮放中心點(diǎn):
      • 若mPivotX==0 且 mPivotY==0:transformation.getMatrix().setScale(sx, sy)
      • 否則:transformation.getMatrix().setScale(sx, sy, mPivotX, mPivotY)
    • image
  • 介紹到這里還是沒有講明白它的具體作用互躬,它是在什么情況下調(diào)用的播赁。不要著急,接下來會(huì)慢慢分析的……

2.4 動(dòng)畫數(shù)據(jù)如何存儲(chǔ)

  • 可以看到applyTransformation(float interpolatedTime, Transformation t)這個(gè)方法中帶有一個(gè)Transformation參數(shù)吨铸,那么這個(gè)參數(shù)是干啥呢行拢?
    • 實(shí)際上,Animation的動(dòng)畫函數(shù)getTransformation目的在于生成當(dāng)前幀的一個(gè)Transformation诞吱,這個(gè)Transformation采用alpha以及Matrix存儲(chǔ)了一幀動(dòng)畫的數(shù)據(jù)舟奠,Transformation包含兩種模式:
      • alpha模式:用于支持透明度動(dòng)畫
      • matrix模式:用于支持縮放、平移以及旋轉(zhuǎn)動(dòng)畫
    • 同時(shí)房维,Transformation還提供了許多兩個(gè)接口用于組合多個(gè)Transformation:
      • compose:前結(jié)合(alpha相乘沼瘫、矩陣右乘、邊界疊加)
      • postCompose:后結(jié)合(alpha相乘咙俩、矩陣左乘耿戚、邊界疊加

2.5 Animation的調(diào)用

  • getTransformation這個(gè)函數(shù)究竟是在哪里調(diào)用的湿故?計(jì)算得到的動(dòng)畫數(shù)據(jù)又是怎么被應(yīng)用的?為什么Animation這個(gè)包要放在android.view下面以及Animation完成之后為什么View本身的屬性不會(huì)被改變膜蛔。慢慢看……
    • 要了解Animation坛猪,先從要從Animation的基本使用View.startAnimation開始尋根溯源:如下所示
    • image
  • 接著看看setStartTime這個(gè)方法,主要是設(shè)置一些屬性皂股。
    • image
  • 接著看看setAnimation(animation)方法源碼
    • 設(shè)置要為此視圖播放的下一個(gè)動(dòng)畫墅茉。如果希望動(dòng)畫立即播放,請(qǐng)使用{@link#startAnimation(android.view.animation.Animation)}代替此方法呜呐,該方法允許對(duì)啟動(dòng)時(shí)間和無效時(shí)間進(jìn)行細(xì)粒度控制就斤,但必須確保動(dòng)畫具有啟動(dòng)時(shí)間集,并且當(dāng)動(dòng)畫應(yīng)該啟動(dòng)時(shí)蘑辑,視圖的父視圖(控制子視圖上的動(dòng)畫)將失效洋机。
    public void setAnimation(Animation animation) {
        mCurrentAnimation = animation;
        if (animation != null) {
            if (mAttachInfo != null && mAttachInfo.mDisplayState == Display.STATE_OFF
                    && animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {
                animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());
            }
            animation.reset();
        }
    }
    
  • 接著重點(diǎn)看一下invalidate(true)這個(gè)方法
    • 通過invalidate(true)函數(shù)會(huì)觸發(fā)View的重新繪制,那么在View.draw是怎么走到對(duì)Animation的處理函數(shù)呢洋魂?
    View.draw(Canvas)
    —> ViewGroup.dispatchDraw(Canvas)
    —> ViewGroup.drawChild(Canvas, View, long)
    —> View.draw(Canvas, ViewGroup, long)
    —> View.applyLegacyAnimation(ViewGroup, long, Animation, boolean)
    
    • image
  • 接著看看View中applyLegacyAnimation這個(gè)方法
    private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
            Animation a, boolean scalingRequired) {
        Transformation invalidationTransform;
        final int flags = parent.mGroupFlags;
        //判斷Animation是否初始化
        final boolean initialized = a.isInitialized();
        //如果沒有初始化绷旗,則進(jìn)行初始化
        if (!initialized) {
            a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
            a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
            if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
            //由父視圖組調(diào)用,通知當(dāng)前與此視圖關(guān)聯(lián)的動(dòng)畫的開始忧设。如果重寫此方法刁标,則始終調(diào)用Super.on動(dòng)畫Start()颠通;
            onAnimationStart();
        }
    
        //獲取Transformation對(duì)象
        final Transformation t = parent.getChildTransformation();
        //獲取要在指定時(shí)間點(diǎn)應(yīng)用的轉(zhuǎn)換址晕,這個(gè)方法最終調(diào)用了Animation中的getTransformation方法
        //調(diào)用getTransformation根據(jù)當(dāng)前繪制事件生成Animation中對(duì)應(yīng)幀的動(dòng)畫數(shù)據(jù)
        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;
        }
    
        //下面主要是,根據(jù)動(dòng)畫數(shù)據(jù)設(shè)定重繪制區(qū)域
        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) {
                    parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
                    //調(diào)用ViewGroup.invalidate(int l, int t, int r, int b)設(shè)定繪制區(qū)域
                    parent.invalidate(mLeft, mTop, mRight, mBottom);
                }
            } else {
                if (parent.mInvalidateRegion == null) {
                    parent.mInvalidateRegion = new RectF();
                }
                final RectF region = parent.mInvalidateRegion;
                a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
                        invalidationTransform);
                parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
    
                final int left = mLeft + (int) region.left;
                final int top = mTop + (int) region.top;
                
                //調(diào)用ViewGroup.invalidate(int l, int t, int r, int b)設(shè)定繪制區(qū)域
                parent.invalidate(left, top, left + (int) (region.width() + .5f),
                        top + (int) (region.height() + .5f));
            }
        }
        return more;
    }
    
    • View.applyLegacyAnimation就是Animation大顯神通的舞臺(tái)顿锰,其核心代碼主要分三個(gè)部分
      • 初始化Animation(僅初始化一次)
        • 調(diào)用Animation.initialize(width, height, parentWidth, parentHeight)谨垃,通過View及ParentView的Size來解析Animation中的相關(guān)數(shù)據(jù);
        • 調(diào)用Animation.initializeInvalidateRegion(left, top, right, bottom)來設(shè)定動(dòng)畫的初始區(qū)域硼控,并在fillBefore為true時(shí)計(jì)算Animation動(dòng)畫進(jìn)度為0.0f的數(shù)據(jù)
      • 調(diào)用getTransformation根據(jù)當(dāng)前繪制事件生成Animation中對(duì)應(yīng)幀的動(dòng)畫數(shù)據(jù)
      • 根據(jù)動(dòng)畫數(shù)據(jù)設(shè)定重繪制區(qū)域
        • 若僅為Alpha動(dòng)畫刘陶,此時(shí)動(dòng)畫區(qū)域?yàn)閂iew的當(dāng)前區(qū)域,且不會(huì)產(chǎn)生變化
        • 若包含非Alpha動(dòng)畫牢撼,此時(shí)動(dòng)畫區(qū)域需要調(diào)用Animation.getInvalidateRegion進(jìn)行計(jì)算匙隔,該函數(shù)會(huì)根據(jù)上述生成動(dòng)畫數(shù)據(jù)Thransformation中的Matrix進(jìn)行計(jì)算,并與之前的動(dòng)畫區(qū)域執(zhí)行unio操作熏版,從而獲取動(dòng)畫的完整區(qū)域
        • 調(diào)用ViewGroup.invalidate(int l, int t, int r, int b)設(shè)定繪制區(qū)域
  • 當(dāng)View.applyLegacyAnimation調(diào)用完成之后纷责,View此次繪制的動(dòng)畫數(shù)據(jù)就構(gòu)建完成,之后便回到View.draw(Canvas, ViewGroup, long)應(yīng)用動(dòng)畫數(shù)據(jù)對(duì)視圖進(jìn)行繪制刷新撼短,如下所示:
    • image
    • 重點(diǎn)看到Animation產(chǎn)生的動(dòng)畫數(shù)據(jù)實(shí)際并不是應(yīng)用在View本身的再膳,而是應(yīng)用在RenderNode或者Canvas上的,這就是為什么Animation不會(huì)改變View的屬性的根本所在曲横。另一方面喂柒,我們知道Animation僅在View被繪制的時(shí)候才能發(fā)揮自己的價(jià)值,這也是為什么插間動(dòng)畫被放在Android.view包內(nèi)。

3.Animator運(yùn)行原理和源碼分析

3.1 屬性動(dòng)畫的基本屬性

  • 屬性動(dòng)畫跟補(bǔ)間動(dòng)畫一樣會(huì)包含動(dòng)畫相關(guān)的屬性灾杰,如動(dòng)畫時(shí)長蚊丐、動(dòng)畫播放次數(shù)、延遲時(shí)間艳吠、插間器等等吠撮,為了后面分析動(dòng)畫運(yùn)行流程時(shí)概念更加明確,這里僅僅寫了部分ValueAnimator源碼中的字段讲竿,并做了相應(yīng)的注解
    // 初始化函數(shù)是否被調(diào)用 
    boolean mInitialized = false; 
    // 動(dòng)畫時(shí)長 
    private long mDuration = (long)(300 * sDurationScale); 
    private long mUnscaledDuration = 300; 
    // 動(dòng)畫延時(shí) 
    private long mStartDelay = 0; 
    private long mUnscaledStartDelay = 0; 
    // 動(dòng)畫重復(fù)模式及次數(shù) 
    private int mRepeatCount = 0; 
    private int mRepeatMode = RESTART; 
    // 插間器
    private TimeInterpolator mInterpolator = sDefaultInterpolator; 
    // 動(dòng)畫開始運(yùn)行的時(shí)間點(diǎn) 
    long mStartTime; 
    // 是否需要在掉幀的時(shí)候調(diào)整動(dòng)畫開始時(shí)間點(diǎn) 
    boolean mStartTimeCommitted; 
    // 動(dòng)畫是否反方向運(yùn)行泥兰,當(dāng)repeatMode=REVERSE是會(huì)每個(gè)動(dòng)畫周期反轉(zhuǎn)一次 
    private boolean mPlayingBackwards = false;
    // 當(dāng)前動(dòng)畫在一個(gè)動(dòng)畫周期中所處位置 
    private float mCurrentFraction = 0f; 
    // 動(dòng)畫是否延時(shí) 
    private boolean mStartedDelay = false; 
    // 動(dòng)畫完成延時(shí)的時(shí)間點(diǎn) 
    private long mDelayStartTime; 
    // 動(dòng)畫當(dāng)前所處的狀態(tài):STOPPED, RUNNING, SEEKED 
    int mPlayingState = STOPPED; 
    // 動(dòng)畫是否被啟動(dòng) 
    private boolean mStarted = false; 
    // 動(dòng)畫是否被執(zhí)行(以動(dòng)畫第一幀被計(jì)算為界) 
    private boolean mRunning = false; 
    
    // 回調(diào)監(jiān)聽器 
    // 確保AnimatorListener.onAnimationStart(Animator)僅被調(diào)用一次 
    private boolean mStartListenersCalled = false; 
    // start,end,cancel,repeat回調(diào)
    ArrayList<AnimatorListener> mListeners = null; 
    // pause, resume回調(diào)
    ArrayList<AnimatorPauseListener> mPauseListeners = null;  
    // value更新回調(diào)
    ArrayList<AnimatorUpdateListener> mUpdateListeners = null; 
    

3.2 屬性動(dòng)畫新的概念

  • 屬性動(dòng)畫相對(duì)于插間動(dòng)畫來件引入了一些新的概念
    • 可以暫停和恢復(fù)、可以調(diào)整進(jìn)度题禀,這些概念的引入鞋诗,讓動(dòng)畫的概念更加飽滿起來,讓動(dòng)畫有了視頻播放的概念迈嘹,主要有:
    // 動(dòng)畫是否正在running
    private boolean mRunning = false;
    // 動(dòng)畫是否被開始
    private boolean mStarted = false;
    // 動(dòng)畫是否被暫停 
    boolean mPaused = false; 
    // 動(dòng)畫暫停時(shí)間點(diǎn)削彬,用于在動(dòng)畫被恢復(fù)的時(shí)候調(diào)整mStartTime以確保動(dòng)畫能優(yōu)雅地繼續(xù)運(yùn)行 
    private long mPauseTime; 
    // 動(dòng)畫是否從暫停中被恢復(fù),用于表明動(dòng)畫可以調(diào)整mStartTime
    private boolean mResumed = false; 
    // 動(dòng)畫被設(shè)定的進(jìn)度位置
    float mSeekFraction = -1;
    

3.3 PropertyValuesHolder作用

  • PropertyValuesHolder是用來保存某個(gè)屬性property對(duì)應(yīng)的一組值秀仲,這些值對(duì)應(yīng)了一個(gè)動(dòng)畫周期中的所有關(guān)鍵幀融痛。
    • 動(dòng)畫說到底是由動(dòng)畫幀組成的,將動(dòng)畫幀連續(xù)起來就成了動(dòng)畫呢神僵。
    • Animator可以設(shè)定并保存整個(gè)動(dòng)畫周期中的關(guān)鍵幀雁刷,然后根據(jù)這些關(guān)鍵幀計(jì)算出動(dòng)畫周期中任一時(shí)間點(diǎn)對(duì)應(yīng)的動(dòng)畫幀的動(dòng)畫數(shù)據(jù)
    • 而每一幀的動(dòng)畫數(shù)據(jù)里都包含了一個(gè)時(shí)間點(diǎn)屬性fraction以及一個(gè)動(dòng)畫值mValue,從而實(shí)現(xiàn)根據(jù)當(dāng)前的時(shí)間點(diǎn)計(jì)算當(dāng)前的動(dòng)畫值保礼,然后用這個(gè)動(dòng)畫值去更新property對(duì)應(yīng)的屬性
    • Animator被稱為屬性動(dòng)畫的原因沛励,因?yàn)樗恼麄€(gè)動(dòng)畫過程實(shí)際上就是不斷計(jì)算并更新對(duì)象的屬性這個(gè)后面詳細(xì)講解。
  • 那么保存property使用什么存儲(chǔ)的呢炮障?看代碼可知:數(shù)組
    • image
  • PropertyValuesHolder由Property及Keyframes組成目派,其中Property用于描述屬性的特征:如屬性名以及屬性類型,并提供set及get方法用于獲取及設(shè)定給定Target的對(duì)應(yīng)屬性值胁赢;Keyframes由一組關(guān)鍵幀Keyframe組成企蹭,每一個(gè)關(guān)鍵幀由fraction及value來定量描述,于是Keyframes可以根據(jù)給定的fraction定位到兩個(gè)關(guān)鍵幀智末,這兩個(gè)關(guān)鍵幀的fraction組成的區(qū)間包含給定的fraction谅摄,然后根據(jù)定位到的兩個(gè)關(guān)鍵幀以及設(shè)定插間器及求值器就可以計(jì)算出給定fraction對(duì)應(yīng)的value。
    • PropertyValuesHolder的整個(gè)工作流程
      • 首先通過setObjectValues等函數(shù)來初始化關(guān)鍵幀組mKeyframes吹害,必要的情況下(如ObjectAnimator)可以通過setStartValue及setEndValue來設(shè)定第一幀及最末幀的value螟凭,以上工作只是完成了PropertyValuesHolder的初始化,
      • 之后就可以由Animator在繪制動(dòng)畫幀的時(shí)候通過fraction來調(diào)用calculateValue計(jì)算該fraction對(duì)應(yīng)的value(實(shí)際上是由mKeyframes的getValue方法做出最終計(jì)算)它呀,獲得對(duì)應(yīng)的value之后螺男,一方面可以通過getAnimatedValue提供給Animator使用棒厘,
      • 另一方面也可以通過setAnimatedValue方法直接將該值設(shè)定到相應(yīng)Target中去,這樣PropertyValuesHolder的職責(zé)也就完成呢下隧。

3.4 屬性動(dòng)畫start執(zhí)行流程

  • 首先看看start方法奢人,默認(rèn)是false,這個(gè)參數(shù)是干嘛的呢淆院?這個(gè)參數(shù)是動(dòng)畫是否應(yīng)該開始反向播放何乎。
    • 啟動(dòng)動(dòng)畫播放。這個(gè)版本的start()使用一個(gè)布爾標(biāo)志土辩,指示動(dòng)畫是否應(yīng)該反向播放支救。該標(biāo)志通常為false,但如果從反向()方法調(diào)用拷淘,則可以將其設(shè)置為true各墨。通過調(diào)用此方法啟動(dòng)的動(dòng)畫將在調(diào)用此方法的線程上運(yùn)行。這個(gè)線程應(yīng)該有一個(gè)活套(如果不是這樣的話启涯,將拋出一個(gè)運(yùn)行時(shí)異常)贬堵。另外,如果動(dòng)畫將動(dòng)畫化視圖層次結(jié)構(gòu)中對(duì)象的屬性结洼,那么調(diào)用線程應(yīng)該是該視圖層次結(jié)構(gòu)的UI線程黎做。
    @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");
        }
        mReversing = playBackwards;
        mSelfPulse = !mSuppressSelfPulseRequested;
        if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
            if (mRepeatCount == INFINITE) {
                float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
                mSeekFraction = 1 - fraction;
            } else {
                mSeekFraction = 1 + mRepeatCount - mSeekFraction;
            }
        }
        mStarted = true;
        mPaused = false;
        mRunning = false;
        mAnimationEndRequested = false;
        mLastFrameTime = -1;
        mFirstFrameTime = -1;
        mStartTime = -1;
        addAnimationCallback(0);
    
        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
            startAnimation();
            if (mSeekFraction == -1) {
                setCurrentPlayTime(0);
            } else {
                setCurrentFraction(mSeekFraction);
            }
        }
    }
    
  • 然后接著看addAnimationCallback(0)這行代碼,從字面意思理解是添加動(dòng)畫回調(diào)callback
    • 可以看到通過getAnimationHandler()創(chuàng)建了一個(gè)AnimationHandler對(duì)象松忍。
    • 然后在看看addAnimationFrameCallback()這個(gè)方法蒸殿,看命名應(yīng)該是專門處理動(dòng)畫相關(guān)的。實(shí)際上里面的邏輯大概是:通過Choreographer向底層注冊(cè)下一個(gè)屏幕刷新信號(hào)監(jiān)聽挽铁,然后將需要運(yùn)行的動(dòng)畫添加到列表中伟桅,如果延遲時(shí)間大于0,則說明動(dòng)畫是一個(gè)延遲開始的動(dòng)畫叽掘,那么加入Delay隊(duì)列里。
    • image
    • image
    • 然后看看動(dòng)畫是用什么存儲(chǔ)的呢玖雁?mAnimationCallbacks是一個(gè)ArrayList更扁,每一項(xiàng)保存的是 AnimationFrameCallback 接口的對(duì)象,看命名這是一個(gè)回調(diào)接口
  • AnimationHandler的作用主要是什么呢赫冬?
    • 是一個(gè)定時(shí)任務(wù)處理器浓镜,根據(jù)Choreographer的脈沖周期性地完成指定的任務(wù),由于它是一個(gè)線程安全的靜態(tài)變量劲厌,因此運(yùn)行在同一線程中的所有Animator共用一個(gè)定時(shí)任務(wù)處理器膛薛,這樣的好處在于:一方面可以保證Animator中計(jì)算某一時(shí)刻動(dòng)畫幀是在同一線程中運(yùn)行的,避免了多線程同步的問題补鼻;另一方面哄啄,該線程下所有動(dòng)畫共用一個(gè)處理器雅任,可以讓這些動(dòng)畫有效地進(jìn)行同步,從而讓動(dòng)畫效果更加優(yōu)雅咨跌。
    • image
  • 然后在回到start(boolean playBackwards)方法中沪么,查看startAnimation()源碼。
    • 內(nèi)部調(diào)用锌半,通過將動(dòng)畫添加到活動(dòng)動(dòng)畫列表來啟動(dòng)動(dòng)畫禽车。必須在UI線程上調(diào)用。
    • 通過notifyStartListeners()這個(gè)方法刊殉,刷新動(dòng)畫listener殉摔,也就是通知?jiǎng)赢嬮_始呢。
    • image
  • 接著看initAnimation()初始化動(dòng)畫操作邏輯
    • 在處理動(dòng)畫的第一個(gè)動(dòng)畫幀之前立即調(diào)用此函數(shù)记焊。如果存在非零<code>startDelay</code>钦勘,則在延遲結(jié)束后調(diào)用該函數(shù),它負(fù)責(zé)動(dòng)畫的最終初始化步驟亚亲。
    • image

3.5 屬性動(dòng)畫cancel和end執(zhí)行流程

  • 先看看cancel中的源碼
    • 可以得知彻采,cancel只會(huì)處理那些正在運(yùn)行或者等待開始運(yùn)行的動(dòng)畫,大概的處理邏輯是這樣的:
      • 調(diào)用AnimatorListener.onAnimationCancel
      • 然后調(diào)用Animator.endAnimation
        • 通過removeAnimationCallback()把該動(dòng)畫從AnimationHandler的所有列表中清除
        • 調(diào)用AnimatorListener.onAnimationEnd
        • 復(fù)位動(dòng)畫所有狀態(tài):如mPlayingState = STOPPED捌归、mRunning=false肛响、mReversing = false、mStarted = false等等
    • image
    • image
  • 再看看end中的源碼
    • end相對(duì)于cancel來說有兩個(gè)區(qū)別:一個(gè)是會(huì)處理所有動(dòng)畫惜索;另一個(gè)是會(huì)計(jì)算最末一幀動(dòng)畫值特笋。其具體的處理邏輯如下所示:
      • 若動(dòng)畫尚未開始:調(diào)用Animatior.startAnimation讓動(dòng)畫處于正常運(yùn)行狀態(tài)
      • 計(jì)算最后一幀動(dòng)畫的動(dòng)畫值:animateValue(mPlayingBackwards ? 0f : 1f)
      • 結(jié)束動(dòng)畫就調(diào)用endAnimation這個(gè)方法,上面已經(jīng)分析了該方法的作用
    • image

3.6 屬性動(dòng)畫pase和resume執(zhí)行流程

  • 先看看pause方法中的源碼
    • 先看在Animator中的pause方法巾兆,然后看ValueAnimator中的pause方法可知:
    • 僅僅在動(dòng)畫已開始(isStarted()==true)且當(dāng)前為非暫停狀態(tài)時(shí)才進(jìn)行以下處理
      • 置位:mPaused = true
      • 循環(huán)遍歷調(diào)用AnimatorPauseListener.onAnimationPause
      • 清空暫停時(shí)間:mPauseTime = -1
      • 復(fù)位mResumed = false
    //在ValueAnimator中
    public void pause() {
        boolean previouslyPaused = mPaused;
        super.pause();
        if (!previouslyPaused && mPaused) {
            mPauseTime = -1;
            mResumed = false;
        }
    }
    
    //在Animator中
    public void pause() {
        if (isStarted() && !mPaused) {
            mPaused = true;
            if (mPauseListeners != null) {
                ArrayList<AnimatorPauseListener> tmpListeners =
                        (ArrayList<AnimatorPauseListener>) mPauseListeners.clone();
                int numListeners = tmpListeners.size();
                for (int i = 0; i < numListeners; ++i) {
                    tmpListeners.get(i).onAnimationPause(this);
                }
            }
        }
    }
    
    • 做完這些處理之后猎物,等下一幀動(dòng)畫的到來,當(dāng)doAnimationFrame被調(diào)用角塑,此時(shí)若仍然處于暫停狀態(tài)蔫磨,就會(huì)做如下截?fù)?
      • 這樣就阻止了動(dòng)畫的正常運(yùn)行,并記錄下來動(dòng)畫暫停的時(shí)間圃伶,確钡倘纾恢復(fù)之后能讓動(dòng)畫調(diào)整到暫停之前的動(dòng)畫點(diǎn)正常運(yùn)行,具體怎么起作用就要看resume的作用窒朋。
    • image
  • 先看看resume方法中的源碼
    • 先看在ValueAnimator中的resume方法搀罢,然后看Animator中的resume方法可知:
      • 置位:mResumed = true
      • 復(fù)位:mPaused = false
      • 調(diào)用AnimatorPauseListener.onAnimationResume
    //在ValueAnimator中
    @Override
    public void resume() {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be resumed from the same " +
                    "thread that the animator was started on");
        }
        if (mPaused && !mResumed) {
            mResumed = true;
            if (mPauseTime > 0) {
                addAnimationCallback(0);
            }
        }
        super.resume();
    }
    
    //在Animator中
    public void resume() {
        if (mPaused) {
            mPaused = false;
            if (mPauseListeners != null) {
                ArrayList<AnimatorPauseListener> tmpListeners =
                        (ArrayList<AnimatorPauseListener>) mPauseListeners.clone();
                int numListeners = tmpListeners.size();
                for (int i = 0; i < numListeners; ++i) {
                    tmpListeners.get(i).onAnimationResume(this);
                }
            }
        }
    }
    
    • 當(dāng)doAnimationFrame被調(diào)用,此時(shí)若處于恢復(fù)狀態(tài)(mResume==true)侥猩,就會(huì)做如下補(bǔ)償處理
      • 這樣就讓暫停的時(shí)間從動(dòng)畫的運(yùn)行過程中消除
    • image

3.7 屬性動(dòng)畫與View結(jié)合

  • 屬性動(dòng)畫如何去實(shí)現(xiàn)View的變換榔至?
    • 是根據(jù)計(jì)算出來的動(dòng)畫值去修改View的屬性,如alpha欺劳、x唧取、y铅鲤、scaleX、scaleY兵怯、translationX彩匕、translationY等等,這樣當(dāng)View重繪時(shí)就會(huì)產(chǎn)生作用媒区,隨著View連續(xù)不斷地被重繪驼仪,就會(huì)產(chǎn)生絢爛多彩的動(dòng)畫。
  • 接著看setTarget這個(gè)方法源碼
    • 如果是使用ValueAnimator類袜漩,那么直接通過mAnimator.setTarget(view)設(shè)置view
    • 如果是使用ObjectAnimator绪爸,那么直接通過ObjectAnimator.ofFloat(view, type, start, end)設(shè)置view,最終還是會(huì)調(diào)用setTarget方法宙攻。注意ObjectAnimator實(shí)現(xiàn)了ValueAnimator類
    • ObjectAnimator是可以在動(dòng)畫幀計(jì)算完成之后直接對(duì)Target屬性進(jìn)行修改的屬性動(dòng)畫類型奠货,相對(duì)于ValueAnimator來說更加省心省力
  • 相比ValueAnimator類,ObjectAnimator還做了許多操作座掘,ObjectAnimator與 ValueAnimator類的區(qū)別:
    • ValueAnimator 類是先改變值递惋,然后 手動(dòng)賦值 給對(duì)象的屬性從而實(shí)現(xiàn)動(dòng)畫;是 間接 對(duì)對(duì)象屬性進(jìn)行操作溢陪;
    • ObjectAnimator 類是先改變值萍虽,然后 自動(dòng)賦值 給對(duì)象的屬性從而實(shí)現(xiàn)動(dòng)畫;是 直接 對(duì)對(duì)象屬性進(jìn)行操作形真;
  • 個(gè)人感覺屬性動(dòng)畫源碼分析十分具有跳躍性杉编。不過還好沒有關(guān)系,只需要理解其大概運(yùn)作原理就可以呢咆霜。

關(guān)于其他內(nèi)容介紹

01.關(guān)于博客匯總鏈接

02.關(guān)于我的博客

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蛾坯,隨后出現(xiàn)的幾起案子光酣,更是在濱河造成了極大的恐慌,老刑警劉巖偿衰,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挂疆,死亡現(xiàn)場離奇詭異,居然都是意外死亡下翎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門宝当,熙熙樓的掌柜王于貴愁眉苦臉地迎上來视事,“玉大人,你說我怎么就攤上這事庆揩±” “怎么了跌穗?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長虏辫。 經(jīng)常有香客問我蚌吸,道長,這世上最難降的妖魔是什么砌庄? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任羹唠,我火速辦了婚禮,結(jié)果婚禮上娄昆,老公的妹妹穿的比我還像新娘佩微。我一直安慰自己,他們只是感情好萌焰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布哺眯。 她就那樣靜靜地躺著,像睡著了一般扒俯。 火紅的嫁衣襯著肌膚如雪奶卓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天撼玄,我揣著相機(jī)與錄音夺姑,去河邊找鬼。 笑死互纯,一個(gè)胖子當(dāng)著我的面吹牛瑟幕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播留潦,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼只盹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了兔院?” 一聲冷哼從身側(cè)響起殖卑,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎坊萝,沒想到半個(gè)月后孵稽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡十偶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年菩鲜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惦积。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡接校,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出狮崩,到底是詐尸還是另有隱情蛛勉,我是刑警寧澤鹿寻,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站诽凌,受9級(jí)特大地震影響毡熏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜侣诵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一痢法、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧窝趣,春花似錦疯暑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至洗鸵,卻和暖如春越锈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背膘滨。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來泰國打工甘凭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人火邓。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓丹弱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親铲咨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子躲胳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

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

  • 1 背景 不能只分析源碼呀,分析的同時(shí)也要整理歸納基礎(chǔ)知識(shí)纤勒,剛好有人微博私信讓全面說說Android的動(dòng)畫坯苹,所以今...
    未聞椛洺閱讀 2,705評(píng)論 0 10
  • 1. 前言 上一篇文章《Android Animation運(yùn)行原理詳解》介紹了插間動(dòng)畫的原理,而Android3....
    SparkInLee閱讀 13,593評(píng)論 5 52
  • 本文假定你已經(jīng)對(duì)屬性動(dòng)畫有了一定的了解摇天,至少使用過屬性動(dòng)畫粹湃。下面我們就從屬性動(dòng)畫最簡單的使用開始。 ObjectA...
    楊偉喬閱讀 639評(píng)論 0 0
  • 本筆記的原文本鏈接 Property Animation Overview 屬性動(dòng)畫總覽 The property...
    Jaesoon閱讀 1,098評(píng)論 2 3
  • Garend 和春雪學(xué)焦點(diǎn)一期班(2018.6.27)堅(jiān)持原創(chuàng)分享第39天 奇美今天期末考試結(jié)束泉坐,可以稍微...
    奇美小碩閱讀 195評(píng)論 0 0