1. 程式人生 > >Android 動畫原始碼學習篇(一)

Android 動畫原始碼學習篇(一)

概述

Android提供了各種功能強大的應用動畫的使用者介面元素和繪製自定義的二維和三維圖形的應用。我們可以大致按照下面分類來學習理解

  • Animation
    安卓框架提供了兩個動畫系統:屬性動畫和檢視動畫。屬性動畫在一般情況下,是首選的方法來使用,因為它更靈活,並提供更多的功能。除了這兩中動畫,你可以使用Drawable動畫,它允許你載入drawable資源顯示一幀接一個。(5.0以後有場景動畫,有閒時再提)

    • Property Animation

      Android3.0(11級)上使用,屬性動畫是可擴充套件的,並且可以讓您的自定義型別的動畫屬性。

    • View Animation

      檢視動畫

    • Drawable Animation

      可顯示動畫涉及drawable資源一個接一個,像一卷膠捲,比如一個進度轉圈

  • 2D、3D 圖形
    當編寫一個應用程式時,重要的是要考慮你的圖形需求將是什麼。不同的圖形任務是最好的完成與不同的技術。

    • Canvas and Drawables

      安卓提供了一組檢視視窗小部件,為廣泛的使用者介面提供了通用的功能。您還可以擴充套件這些小部件來修改他們的外觀或行為方式。此外,你可以做你自己的自定義的2D使用各種繪圖方法包含在畫布上繪製或建立諸如紋理、按鈕等可畫的物件

    • Hardware Acceleration

      從3.0開始,您可以使用硬體加速功能,大多數繪圖畫布api都會採用,可以進一步提高其效能。

    • OpenGL

      openGL開發暫不涉及。

屬性動畫系是一個強大的框架,允許你做幾乎任何事情。你可以定義一個動畫在時間的任何物件的屬性變化,無論繪製螢幕或屬性動畫更改屬性的(一個物件中的欄位)在指定的時間值。做某事,你指定你希望動畫化的物件屬性。屬性動畫可以定義以下幾個動畫的特點:

  • 持續時間

    你可以指定動畫的持續時間。預設長度為300毫秒。

  • 時間插值

    您可以指定該屬性的值計算為一個動畫的當前時間功能。

  • 重複計數和行為

    你可以指定是否有動畫重複當它達到一個持續時間結束,多少次重複的動畫。您還可以指定是否希望動畫反向回放。設定為反向播放動畫向前然後向後地重複,直到到達重複次數。

  • 動畫設定

    你可以動畫成邏輯組,一起玩或順序或在指定的延遲。

  • 幀重新整理延遲

    可以指定多長時間重新整理你的動畫幀。預設設定為每10毫秒重新整理,但速度在你的應用程式可以重新整理幀最終依賴於系統的整體是如何忙碌,如何快速的系統服務基礎計時器

屬性動畫是怎麼工作的呢?首先,我們通過一個動畫一個簡單的例子。下圖描述了一個假想的物件,動畫以其X屬性,這代表它在螢幕上的水平位置。動畫的持續時間設定為40毫秒和移動的距離是40畫素。每10毫秒,這是預設的幀重新整理率,物體水平移動10畫素。在40ms,動畫停止,和物體兩端在水平位置40。這是一個帶線性插值的動畫例子:在一個恆定的速度運動的目標。

你也可以指定有一個非線性插值動畫。下圖說明了一個假設的物件,在動畫開始時加速,然後減速動畫結束時。物件還是移動40畫素在40毫秒,但非線性。在開始的時候,這個動畫加速到中途點,然後從中途點減速直到動畫結束。如圖2所示,距離在開始和結束的動畫是小於在中間。

以上內容屬於補腦,並沒多少暖用,下面正式進入主題。如果你對下面內容看的頭暈目眩,親你該出去走兩步,吹吹風然後回來繼續戰鬥了。

檢視動畫原始碼學習

Interpolator


public interface Interpolator extends TimeInterpolator {
}

TimeInterpolator (插值器)是 android.animation包下的一個介面

public interface TimeInterpolator {

    float getInterpolation(float input);
}

引數input是一個0到1之間的數。但是返回的值是可以大於1,也是可以小於0的

Interpolator的實現基類抽象類BaseInterpolator,該抽象類的mChangingConfiguration變數,來自Resource資源配置的value,通過TypeArray 獲取value值對它進行了賦值


/**
 * An abstract class which is extended by default interpolators.
 */
abstract public class BaseInterpolator implements Interpolator {
    private int mChangingConfiguration;
    /**
     * @hide
     */
    public int getChangingConfiguration() {
        return mChangingConfiguration;
    }

    /**
     * @hide
     */
    void setChangingConfiguration(int changingConfiguration) {
        mChangingConfiguration = changingConfiguration;
    }
}
...............................................................
  TypedArray a;
  //....略.....      
  setChangingConfiguration(a.getChangingConfigurations());
  a.recycle();

系統定義了BaseInterpolator眾多子類

這些子類的區別在於演算法getInterpolation方法和AttributeSet的屬性解析,這裡就不一一理解,找個簡單點的看看吧


/**
 * An interpolator where the rate of change is constant
 */
@HasNativeInterpolator
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

    public float getInterpolation(float input) {
        return input;
    }

    /** @hide */
    @Override
    public long createNativeInterpolator() {
        return NativeInterpolatorFactoryHelper.createLinearInterpolator();
    }
}
public final class NativeInterpolatorFactoryHelper {
    private NativeInterpolatorFactoryHelper() {}

    public static native long createAccelerateDecelerateInterpolator();
    public static native long createAccelerateInterpolator(float factor);
    public static native long createAnticipateInterpolator(float tension);
    public static native long createAnticipateOvershootInterpolator(float tension);
    public static native long createBounceInterpolator();
    public static native long createCycleInterpolator(float cycles);
    public static native long createDecelerateInterpolator(float factor);
    public static native long createLinearInterpolator();
    public static native long createOvershootInterpolator(float tension);
    public static native long createLutInterpolator(float[] values);
}

註解直譯器和native程式碼暫不涉及了,博主不懂native ,直譯器略懂。這裡推薦一個blog關於插值器的:

Animation

上面已經大概瞭解過Interpolator,下面再來看看Animation抽象類。

public abstract class Animation implements Cloneable {
}

實現了Cloneable具有了原型拷貝功能

    @Override
    protected Animation clone() throws CloneNotSupportedException {
        final Animation animation = (Animation) super.clone();
        animation.mPreviousRegion = new RectF();
        animation.mRegion = new RectF();
        animation.mTransformation = new Transformation();
        animation.mPreviousTransformation = new Transformation();
        return animation;
    }

兩個建構函式,如果有AttributeSet引數,會多一步操作讀取Animation相關的自定義屬性值,例如:Duration、startOffset、fillAfter等屬性。最後都會檢查Interpolator,如果interpolator為空,則會預設一個加速減速插值器

  /**
     * Gurantees that this animation has an interpolator. Will use
     * a AccelerateDecelerateInterpolator is nothing else was specified.
     */
    protected void ensureInterpolator() {
        if (mInterpolator == null) {
            mInterpolator = new AccelerateDecelerateInterpolator();
        }
    }

上面提到的Animation自定義的AttributeSet,讓我們可以在Resource下資原始檔裡面定義,還可以通過程式碼設定他們(當然一般情況不會用到它們)

Animation裡面還有個核心玩意兒AnimationListener

    public static interface AnimationListener {

        void onAnimationStart(Animation animation);

        void onAnimationEnd(Animation animation);

        void onAnimationRepeat(Animation animation);
    }

見名知其意,就不過多解釋方法含義了,這些介面定義的函式是怎麼回撥的呢,這裡就讓人有點凌亂了,先補腦View 動畫流程:

圖形變換通過矩陣實現,每種變換都是一次矩陣運算。Canvas類包含當前矩陣,當前呼叫Canvas.drawBitmap(bm,x,y,Paint)繪製時,android會先把bitmap做一次矩陣運算,然後將運算結果顯示在Canvas上。這樣只需不斷修改Canvas的矩陣並重新整理螢幕,View裡物件就會不停的做圖形變換,動畫就形成了。

1.View建立動畫物件,設定動畫屬性,呼叫invalidate重新整理螢幕,啟動動畫

2.invalidate方法觸發了onDraw函式

onDraw函式中:

呼叫Animation的getTransformation方法,得到當前時間點的矩陣

將該矩陣設定成Canvas的當前矩陣

呼叫canvas的drawBitmap方法,繪製螢幕。

判斷getTransformation的返回值,若為真,呼叫invalidate方法,重新整理螢幕進入下一幀;若為假,說明動畫完成。

通過上門知識點了解到呼叫Animation的getTransformation函式,跟進函式發現一下三個方法呼叫:

  • fireAnimationStart

  • fireAnimationEnd

  • fireAnimationRepeat

 public boolean getTransformation(long currentTime, Transformation outTransformation) {
        //...........略過.......................
        if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
            if (!mStarted) {
                fireAnimationStart();
                mStarted = true;
                if (USE_CLOSEGUARD) {
                    guard.open("cancel or detach or getTransformation");
                }
            }


            final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
            applyTransformation(interpolatedTime, outTransformation);
        }

        if (expired) {
            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();
            }
        }

      //...............略...................

        return mMore;
    }

這個方法關於計算動畫相關的理解如有所需要請參考這兩篇blog:

// "http://www.jianshu.com/p/fcd9c7e9937e"

// "http://www.w2bc.com/Article/10394"

上面提到的三個方法都類似,這裡以fireAnimationStart()為例,來看看原始碼塊

private void fireAnimationStart() {
        if (mListener != null) {
            if (mListenerHandler == null) mListener.onAnimationStart(this);
            else mListenerHandler.postAtFrontOfQueue(mOnStart);
        }
    }

如果AnimationListener不為空,就直接回調相對應的函式,否則通過handler.postAtFrontOfQueue(Runnable)實現,這裡再次補腦:

handler post 將一個動作入隊安排在非當前執行緒執行。排程訊息是通過一系列的post方法和sendMessage方法

而post方法允許你向訊息佇列中放入一些Runnable物件,在它們被接收到的時候會被呼叫

(實際上post方法也就是將runnable物件包裝在訊息裡,然後再通過sendMessage方法實現)

接著看mOnStart Runnable初始化的地方吧

 public void setListenerHandler(Handler handler) {
        if (mListenerHandler == null) {
            mOnStart = new Runnable() {
                public void run() {
                    if (mListener != null) {
                        mListener.onAnimationStart(Animation.this);
                    }
                }
            };
            mOnRepeat = new Runnable() {
                public void run() {
                    if (mListener != null) {
                        mListener.onAnimationRepeat(Animation.this);
                    }
                }
            };
            mOnEnd = new Runnable() {
                public void run() {
                    if (mListener != null) {
                        mListener.onAnimationEnd(Animation.this);
                    }
                }
            };
        }
        mListenerHandler = handler;
    }

找到這些後問題又來了,setListenerHandler方法在Animation沒發現主動呼叫相關子類也沒發現,這又是 在哪裡初始化的呢?帶著疑問來到了View類與動畫相關的draw相關方法

public boolean draw(...){

  //............略..............

  final Animation a = getAnimation();
        if (a != null) {
            more = drawAnimation(parent, drawingTime, a, scalingRequired);
            concatMatrix = a.willChangeTransformationMatrix();
            if (concatMatrix) {
                mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
            }
            transformToApply = parent.getChildTransformation();
        } else {
  //...........略...............
}

繼續跟進drawAnimation方法

 private boolean drawAnimation(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());
            a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
            if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
            onAnimationStart();
        }

        final Transformation t = parent.getChildTransformation();
        boolean more = a.getTransformation(drawingTime, t, 1f);
         //.......略..............
}

終於發現是三個函式呼叫:setListenerHandler 、getTransformation、initialize,一下都明瞭了,也明白了getTransformation函式是在onAnimationStart後呼叫。

在瞭解了Animation與View相關的呼叫流程之後,我們再來看看它的子類,主要區別在語TypeArray解析Attribute、initialize、applyTransformation三個函式,關於applyTransformation函式在哪裡有呼叫到請補腦上面的getTransformation方法塊。

 protected void applyTransformation(float interpolatedTime, Transformation t) {
    }
  • ScaleAnimation

  • AlphaAnimation

  • TranslateAnimation

  • RotateAnimation

以上四類動畫什麼意思就不用多說了吧,簡單看看ScaleAnimation吧

public class ScaleAnimation extends Animation {
    private final Resources mResources;

    private float mFromX;
    private float mToX;
    private float mFromY;
    private float mToY;

    private int mFromXType = TypedValue.TYPE_NULL;
    private int mToXType = TypedValue.TYPE_NULL;
    private int mFromYType = TypedValue.TYPE_NULL;
    private int mToYType = TypedValue.TYPE_NULL;

    private int mFromXData = 0;
    private int mToXData = 0;
    private int mFromYData = 0;
    private int mToYData = 0;

    private int mPivotXType = ABSOLUTE;
    private int mPivotYType = ABSOLUTE;
    private float mPivotXValue = 0.0f;
    private float mPivotYValue = 0.0f;

    private float mPivotX;
    private float mPivotY;

    /**
     * Constructor used when a ScaleAnimation is loaded from a resource.
     * 
     * @param context Application context to use
     * @param attrs Attribute set from which to read values
     */
    public ScaleAnimation(Context context, AttributeSet attrs) {
        super(context, attrs);

        mResources = context.getResources();
        //..........略.............
        TypedArray a = context.obtainStyledAttributes(attrs,
                com.android.internal.R.styleable.ScaleAnimation);

        TypedValue tv = a.peekValue(
                com.android.internal.R.styleable.ScaleAnimation_fromXScale);
        mFromX = 0.0f;

        tv = a.peekValue(
                com.android.internal.R.styleable.ScaleAnimation_fromYScale);
        mFromY = 0.0f;
        if (tv != null) {
            if (tv.type == TypedValue.TYPE_FLOAT) {
                // This is a scaling factor.
                mFromY = tv.getFloat();
            } else {
                mFromYType = tv.type;
                mFromYData = tv.data;
            }
        }
        //..........略.............
        a.recycle();
        initializePivotPoint();
    }


    public ScaleAnimation(float fromX, float toX, float fromY, float toY,
            int pivotXType, float pivotXValue, int pivotYType, float pivotYValue) {
        mResources = null;
        mFromX = fromX;
       //..........略.............
    }


    private void initializePivotPoint() {
          //..........略.............
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        float sx = 1.0f;
        float sy = 1.0f;
        float scale = getScaleFactor();

        if (mFromX != 1.0f || mToX != 1.0f) {
            sx = mFromX + ((mToX - mFromX) * interpolatedTime);
        }
        if (mFromY != 1.0f || mToY != 1.0f) {
            sy = mFromY + ((mToY - mFromY) * interpolatedTime);
        }

        if (mPivotX == 0 && mPivotY == 0) {
            t.getMatrix().setScale(sx, sy);
        } else {
            t.getMatrix().setScale(sx, sy, scale * mPivotX, scale * mPivotY);
        }
    }
       //..........略.............
    }

    @Override
    public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);

        mFromX = resolveScale(mFromX, mFromXType, mFromXData, width, parentWidth);
        //............略............
    }
}

通過上面這個類applyTransformation()方法再此證明了View動畫本質就是Matrix變化,重新整理檢視。

AnimationSet 就是動畫集,裡面可以放上面提到的四種動畫。提供addAnimation方法新增到集合

AnimationSet的getTransformation方法則遍歷集合的每個Animation,呼叫Animation自身的

getTransformation方法,applyTransformation方法自然也就各自呼叫自己的實現。

LayoutAnimationController、GridLayoutAnimationController

走馬觀花看過Interpolator和Animation,我們還剩下四各類,Transformation就忽略吧,暫時樓主道不出什麼有價值的東西。

LayoutAnimationController和它子類是應用於ViewGroup,可以讓viewGroup的childView按照一定的規則顯示動畫。該類自定義屬性有:

  • delay

    動畫播放的延遲時間

  • animationOrder

    子view播放動畫的順序

  • interpolator

    插值器

  • animation

    view動畫

animationOrder定義的播放順序有三種:順序、倒敘、隨機,預設順序播放。

    public static final int ORDER_NORMAL = 0;
    public static final int ORDER_RANDOM = 2;
    public static final int ORDER_REVERSE = 1;

基本的屬性解析和set get 方法,以及判斷動畫是否結束

 public boolean isDone() {
        return AnimationUtils.currentAnimationTimeMillis() >
                mAnimation.getStartTime() + mMaxDelay + mDuration;
    }

isDone方法在ViewGroup內會呼叫到用於更新flag、重新整理檢視、AnimationListener回撥

  if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&
                mLayoutAnimationController.isDone() && !more) {

            mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
            final Runnable end = new Runnable() {
               public void run() {
                   notifyAnimationListener();
               }
            };
            post(end);
        }

這裡呢不對LayoutAnimationControllerh和他子類GridLayoutAnimationController的原始碼做過多理解,關於xml佈局呼叫和程式碼呼叫這個動畫的用法更詳細資料請參考這篇優質部落格:

“ android:layoutAnimation ”這個屬性只能作用於ViewGroup,這是ViewGroup自定義的屬性

case R.styleable.ViewGroup_layoutAnimation:
                    int id = a.getResourceId(attr, -1);
                    if (id > 0) {
                        setLayoutAnimation(AnimationUtils.loadLayoutAnimation(mContext, id));
                    }
                    break;

這裡通過AnimationUtils解析得到Animation,但是Animation怎麼關聯到childView的呢,帶著疑問我們來到ViewGroup的一個方法塊

 private void bindLayoutAnimation(View child) {
        Animation a = mLayoutAnimationController.getAnimationForView(child);
        child.setAnimation(a);
    }

view繫結Animation又是在哪裡觸發的呢?這裡又涉及到draw的繪製流程,這裡知識補腦來自於

由ViewRoot物件的performTraversals()方法呼叫draw()方法發起繪製該View樹,值得注意的是每次發起繪圖時,並不
  會重新繪製每個View樹的檢視,而只會重新繪製那些“需要重繪”的檢視,View類內部變數包含了一個標誌位DRAWN,當該
檢視需要重繪時,就會為該View新增該標誌位。

   呼叫流程 :
     mView.draw()開始繪製,draw()方法實現的功能如下:
          1 、繪製該View的背景
          2 、為顯示漸變框做一些準備操作,大多數情況下,不需要改漸變框       
          3、呼叫onDraw()方法繪製檢視本身   (每個View都需要過載該方法,ViewGroup不需要實現該方法)
          4、呼叫dispatchDraw ()方法繪製子檢視(如果該View型別不為ViewGroup,即不包含子檢視,不需要過載該方法)
            4.1 dispatchDraw()方法內部會遍歷每個子檢視,呼叫drawChild()去重新回撥每個子檢視的draw()方法

於是乎我們來到dispatchDraw方法,果不其然發現了bindLayoutAnimation()方法的呼叫

  @Override
    protected void dispatchDraw(Canvas canvas) {
        boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
        final int childrenCount = mChildrenCount;
        final View[] children = mChildren;
        int flags = mGroupFlags;

        if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
            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);
                    bindLayoutAnimation(child);
                }
            }
    //..................略...........................
}

view檢視動畫原始碼基本告一段落,就剩下AnimationUtils類了,上面提到的Interpolator和animation都可以在Resource anim 定義動畫,具體怎麼玩就不細說了,自行參考連結

AnimationUtils

上圖所示前面四個方法,最終都會呼叫到createAnimationFromXml,下面來看原始碼塊

  private static Animation createAnimationFromXml(Context c, XmlPullParser parser,
            AnimationSet parent, AttributeSet attrs) throws XmlPullParserException, IOException {

        Animation anim = null;

        // Make sure we are on a start tag.
        int type;
        int depth = parser.getDepth();

        while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
               && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            String  name = parser.getName();

            if (name.equals("set")) {
                anim = new AnimationSet(c, attrs);
                createAnimationFromXml(c, parser, (AnimationSet)anim, attrs);
            } else if (name.equals("alpha")) {
                anim = new AlphaAnimation(c, attrs);
            } else if (name.equals("scale")) {
                anim = new ScaleAnimation(c, attrs);
            }  else if (name.equals("rotate")) {
                anim = new RotateAnimation(c, attrs);
            }  else if (name.equals("translate")) {
                anim = new TranslateAnimation(c, attrs);
            } else {
                throw new RuntimeException("Unknown animation name: " + parser.getName());
            }

            if (parent != null) {
                parent.addAnimation(anim);
            }
        }

        return anim;

    }

根據解析出來的標籤名,建立對應的動畫,如果是動畫集AnimationSet不為空,則動畫放入集合。loadLayoutAnimation()、loadInterpolator()方法同理可知,都是根據標籤建立物件。

AnimationUtils還為我們預定義了顯示隱藏左右上下移動的動畫

Drawable Animation 原始碼學習

Drawable Animation 的使用相信大夥兒都比較熟悉了,這裡僅僅簡單的瞭解一下實現原理。

不過是xml佈局繫結還是程式碼呼叫的方式都會呼叫 到 setBackgroundDrawable方法,都會呼叫 AnimationDrawable .start()方法,跟進AnimationDrawable

繼續跟進start方法

 public void start() {
        mAnimating = true;

        if (!isRunning()) {
            // Start from 0th frame.
            setFrame(0, false, mAnimationState.getChildCount() > 1
                    || !mAnimationState.mOneShot);
        }
    }

發現呼叫setFrame方法,而setFrame方法有呼叫到Drawable內部定義四個方法

  public Callback getCallback() {
        if (mCallback != null) {
            return mCallback.get();
        }
        return null;
    }
    public void invalidateSelf() {
        final Callback callback = getCallback();
        if (callback != null) {
            callback.invalidateDrawable(this);
        }
    }
    public void scheduleSelf(Runnable what, long when) {
        final Callback callback = getCallback();
        if (callback != null) {
            callback.scheduleDrawable(this, what, when);
        }
    }
    public void unscheduleSelf(Runnable what) {
        final Callback callback = getCallback();
        if (callback != null) {
            callback.unscheduleDrawable(this, what);
        }
    }

這幾個方法無疑都與Drawable內部定義的Callback有這不清不楚的關係,我們來看看Callback原始碼

/*如果你想實現一個擴充套件子Drawable的動畫drawable,那麼你可以通過setCallBack(android.graphics.drawable.Drawable.Callback)來把你實現的該介面註冊到動畫drawable 
*中。可以實現對動畫的排程和執行 
*/   
public static interface Callback {  
        /** 
         * 當drawable重畫時觸發,這個點上drawable將被置為不可用(起碼drawable展示部分不可用) 
         * @param 要求重畫的drawable 
         */  
        public void invalidateDrawable(Drawable who);  

        /** 
         * drawable可以通過該方法來安排動畫的下一幀。可以僅僅簡單的呼叫postAtTime(Runnable, Object, long)來實現該方法。引數分別與方法的引數對 
         *應 
         * @param who The drawable being scheduled. 
         * @param what The action to execute. 
         * @param when The time (in milliseconds) to run 
         */  
        public void scheduleDrawable(Drawable who, Runnable what, long when);  

        /** 
         *可以用於取消先前通過scheduleDrawable(Drawable who, Runnable what, long when)排程的某一幀。可以通過呼叫removeCallbacks(Runnable,Object)來實現 
         * @param who The drawable being unscheduled. 
         * @param what The action being unscheduled. 
         */  
        public void unscheduleDrawable(Drawable who, Runnable what);  
    }  

瞭解了上門Callback函式定義以後,我們再回過頭來看setFrame就不難理解了,通過引數控制切換Drawable繪製,start 、stop方法也就容易理解了

 public void start() {
        mAnimating = true;
        if (!isRunning()) {
            // Start from 0th frame.
            setFrame(0, false, mAnimationState.getChildCount() > 1
                    || !mAnimationState.mOneShot);
        }
    }
    public void stop() {
        mAnimating = false;

        if (isRunning()) {
            unscheduleSelf(this);
        }
    }

Drawable.Callback的實現位置我們可以繼續跟進,對Callback 與Drawable 、View之前的關係一探究竟。

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource{

}

找到drawable 設定Callback呼叫的方法setCallback(this),讓Drawable 快取 Callback的引用。結果找到幾個方法:

  • setForeground

  • setBackgroundDrawable

這兩個方法設定了Callback後都有呼叫到重繪invalidate。

在View初始化解析Attribute,如果有background屬性會呼叫到setBackgroundDrawable,在這裡會為Drawable設定Callback(程式碼呼叫設定Background ResId /drawable ResColo 同樣會呼叫到setBackgroundDrawable).

invalidate方法呼叫又是怎麼一回事呢?invalidate會引起重繪,我們進入View draw方法一觀

 @CallSuper
    public void draw(Canvas canvas) {

        // Step 1, draw the background, if needed
        int saveCount;

        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

      //.........略.................

        }

請仔細檢視註釋

“// Step 1, draw the background, if needed”

google工程師把繪製不湊寫的非常清晰的,現在我們基本可以用一幅圖來小結Drawable Animation

博主學到的就這麼多了,在深入瞭解比較吃力了,暫時就這樣吧。以上內容可能存在誤差,歡迎交流指出,謝謝!

相關推薦

Android 動畫原始碼學習

概述 Android提供了各種功能強大的應用動畫的使用者介面元素和繪製自定義的二維和三維圖形的應用。我們可以大致按照下面分類來學習理解 Animation 安卓框架提供了兩個動畫系統:屬性動畫和檢視動畫。屬性動畫在一般情況下,是首選的方法來使用,因為它更

在路上---學習Python 數據結構和算法 (4) --希爾排序、歸並排序

改進 randint 循環 打印 中一 隨機 關鍵詞 shel 條件 獨白:   希爾排序是經過優化的插入排序算法,之前所學的排序在空間上都是使用列表本身。而歸並排序是利用增加新的空間,來換取時間復雜度的減少。這倆者理念完全不一樣,註定造成的所消耗的時間不同以及空間上的不同

Android框架原始碼解析之Volley

前幾天面試CVTE,HR面掛了。讓內部一個學長幫我查看了一下面試官評價,發現二面面試官的評價如下: 廣度OK,但缺乏深究能力,深度與實踐不足 原始碼:只能說流程,細節程式碼不清楚,retrofit和volley都是。 感覺自己一方面:自己面試技巧有待提高吧(框

Android 滑動效果入門—— ViewFlipper

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

現在很火的 vue 學習

記錄一下自己的學習過程,以及學習中遇到的問題。 學習什麼東西首先要知道為什麼去學習?學習它主要可以做什麼? 那什麼是vue.js? vue是一個輕量級框架,與其他重量級的框架不同的是,vue採用自底向上增量開發的設計。使用vue只需要關注檢視層,不過使用起來讓我感覺最不錯的是vue的響應

Spring原始碼學習筆記 bean是怎麼生成的

bean 實在 bean 重新整理過程中產生的,首先我們看下 bean 的重新整理方法。下面是 AbstractApplicationContext 的 refresh 方法。 @Override public void refresh() throws

Java 7 原始碼學習系列——String

String表示字串,Java中所有字串的字面值都是String類的例項,例如“ABC”。字串是常量,在定義之後不能被改變,字串緩衝區支援可變的字串。因為 String 物件是不可變的,所以可以共享它們。例如: String str = "abc"; 相當於 char data[] =

逆向學習

開始學習逆向了,工欲善其事,必先利其器,第一篇,先記錄先od的各個功能。 這裡先借用一張網上偷來的圖 反彙編視窗:顯示被除錯程式的反彙編程式碼,標題欄上的地址、HEX 資料、反彙編、註釋可以通過在視窗中右擊出現的選單 介面選項->隱藏標題 或&nbs

開源中國APP Android原始碼分析系列

簡述 這篇文章是基於OSCHINA Android客戶端4.1.7版本的分析,之前很多人都分析過原始碼,但是都是幾年前的程式碼分析,隨著時間的推移,開源中國的原始碼也在變化,接下來的一段時間我將分享我通過學習開源中國的程式碼所獲得東西。 啟動頁面 研究一個A

【Qt】通過QtCreator原始碼學習Qt:pro檔案

1、學習目的 學習pro檔案的語法規則,這在跨平臺專案中會經常用到。和條件編譯相似,在pro中可以根據平臺選擇不同的編譯模組、檔案,還可以向原始碼中傳遞變數等。 2、學習方法 通過學習QtCreator原始碼中的pro檔案,來掌握pro檔案語法規則,下面以qtcreator.

比特幣原始碼學習筆記

https://github.com/trottier/original-bitcoin 前言 從事區塊鏈的開發,不瞭解其底層核心技術是不夠的。許多人在看了比特幣白皮書之後仍然不清楚比特幣是怎樣實現的,因為比特幣的原始碼設計精巧,有許多設計白皮書未曾提及,加上本身

SimplifyReader原始碼學習音樂播放功能總結

寫在前面: SimplifyReader是我第一個用心研究原始碼的app,在這裡首先感謝開原始碼的分享者。 這也是我的第一篇部落格,希望可以記錄下來學習中的經驗和總結。也歡迎大家指正錯誤,共同進步。 最後,分享給大家

java 學習

俗話說;凡事預則立,不預則廢.那麼到底如何學習java web 呢?下面是我自己的一些建議(不喜勿噴,補充私聊) 首先 我們需要了解什麼是java web 給段百科解釋 連線 http://baike.baidu.com/link?url=6dqNLu-49BGNfBNSz

weex+android原生開發學習筆記

移動端開發(使用Weex+android原生開發筆記):         npm install weex-toolkit -g              2》、用 weex create 命令來建立一個空的模板專案:         weex create awes

Android Gallery3D原始碼學習總結——Cache快取及資料處理流程

第一,在應用程式中有三個執行緒存在:主執行緒(隨activity的宣告週期啟動銷燬)、feed初始化執行緒(進入程式時只執行一次,用於載入相簿初始資訊)、feed監聽執行緒(一直在跑,監聽相簿和相片的變更)。 第二,不考慮CacheService 啟動的主要流程歸納如下: 1

2016 Android 動畫 詳解 乾貨

本系列文章會借鑑一些前輩的經典帖子。這裡對這些巨人表示感謝。 在最後我會放出一些根據這些動畫實現的小遊戲,以及原碼,不足的地方歡迎切磋。 Android的動畫一開始分為兩種方式實現 第一種就是:逐幀動畫 實現原理很簡單就是將一個完整的動畫拆分成一張張單獨的

springFramework 原始碼學習日記原始碼下載與編譯

 抽空學習springFramework原始碼,後續每天有時間都將學習心得發表,配合教程《spring 技術內幕》作者:計文柯出版社:機械工業出版社原始碼下載時間2012-9-2 下午4點,svn下載https://github.com/SpringSource/spr

Android Gallery3d原始碼學習總結——繪製流程drawThumbnails

此函式控制相簿表格頁、相片表格頁、時間分類表格頁的展示,非常重要。以下以相簿表格頁為例進行講解,其他的就舉一反三吧。準備輸入引數 final GridDrawables drawables = mDrawables;         final DisplayList d

Activity之間的動畫切換學習筆記

Activity之間的動畫切換 首先什麼是Transition? 安卓5.0中Activity和Fragment變換是建立在名叫Transitions的安卓新特性之上的。這個誕生於4.4的transition框架為在不同的UI狀態之間產生動畫效果提供了

OBS原始碼學習筆記

obs-app.cpp是main入口檔案,然後通過load_debug_privilege函式,修改了下程序的許可權,呼叫base_set_crash_handler設定全域性的crash_handler,crash_param;設定def_log_handler函式作為日誌列印函式;接下來的判斷命令列引數,