Animation動畫概述和執(zhí)行原理

1 Animation動畫簡介

Developers:https://developer.android.google.cn/reference/android/view/animation/package-summary

Android中動畫非常常用杀赢,很多效果都需要動畫的配合虫腋,android提供了多種動畫類型,為創(chuàng)建多彩的android程序提供了支持嘱兼。提供的動畫類型包括:補間動畫,幀動畫贤徒,屬性動畫芹壕,補間動畫和幀動畫被稱為視圖動畫汇四。

對于Animation動畫,android提供了兩種機制來創(chuàng)建視圖動畫踢涌,
一種是tweened animation(補間動畫),
一種是frame-by-frame animation(逐幀動畫) 通孽。
Tweened animation 可以實現(xiàn)view一系列簡單的轉(zhuǎn)換(位置,尺寸睁壁,旋轉(zhuǎn)利虫,透明度),
frame-by-frame 通過加載一系列drawable資源堡僻,實現(xiàn)動畫糠惫。

視圖動畫只能作用于View,且動畫類型是固定的钉疫。

補間動畫:確定了view的開始的視圖樣式和結(jié)束的視圖樣式硼讽,動畫過程中系統(tǒng)會補全變化中的狀態(tài),最終就實現(xiàn)了動畫效果牲阁。

補間動畫的種類:

  • translate (平移動畫)
  • scale (縮放動畫)
  • rotate (旋轉(zhuǎn)動畫)
  • alpha (透明度動畫)

補間動畫可以利用xml文件和動畫類進行實現(xiàn)固阁,對應(yīng)的具體動畫類:

  • translate(平移動畫) 對應(yīng) TranslateAnimation
  • scale (縮放動畫) 對應(yīng) ScaleAnimation
  • rotate (旋轉(zhuǎn)動畫) 對應(yīng) RotateAnimation類
  • alpha ( 透明度動畫) 對應(yīng) AlphaAnimation 類

補間動畫一般利用xml文件實現(xiàn),如果利用xml文件實現(xiàn)動畫城菊,需要在res/anim文件夾下穿件動畫文件备燃。

2 Animation 基類

Animation作為補間動畫的基類,具有許多動畫公共的屬性和方法:


在android.view.animation包下凌唬,可以看出是作用于view的并齐。
直接子類有:AlphaAnimation,AnimationSet,RotateAnimation,ScaleAnimatioin,TranslateAnimation。
XML屬性包括:

下面會列舉Animation中公共屬性在xml文件中的表示和代碼類中的設(shè)置方式及效果:
每一項包括Animation中公共屬性在xml文件中的表示和代碼類中的設(shè)置方式及效果

  • android:detachWallpaper 對應(yīng)setDetachWallpaper(boolean):是否在壁紙上運行客税,取值true况褪,flase;
  • android:duration 對應(yīng)setDuration(long):動畫持續(xù)時間更耻,參數(shù)單位為毫秒测垛;
  • android:fillAfter 對應(yīng)setFillAfter(boolean):動畫結(jié)束時view是否保持動畫最后的狀態(tài),默認(rèn)值為false秧均;
  • android:fillBefore 對應(yīng)setFillBefore(boolean):動畫結(jié)束時view是否還原到開始動畫前的狀態(tài)食侮,和fillAfter行為是沖突的,所以只有當(dāng)fillBefore為true或者fillEnabled不為true才生效目胡。默認(rèn)是true
  • android:fillEnabled 對應(yīng)setFillEnabled(boolean):如果 fillEnabled 取值為true锯七,animation將使用fillBefore的值,否則fillBefore將被忽略讶隐。都是在動畫結(jié)束時還原到原來的狀態(tài)起胰。
  • android:interpolator 對應(yīng)setInterpolator(Interpolator):設(shè)定插值器;
  • android:repeatCount對應(yīng)setRepeatCount(int):動畫重復(fù)次數(shù),可以是具體次數(shù)效五,也可以是INFINITE(-1)一直循環(huán)地消。
  • android:repeatMode 對應(yīng)setRepeatMode(int):重復(fù)類型有兩個值,reverse表示倒序回放畏妖,restart表示從頭播放脉执,需要和repeateCount配合使用。
  • android:startOffset對應(yīng)setStartOffset(long):調(diào)用start函數(shù)之后等待開始運行的時間戒劫,單位為毫秒半夷;
  • android:zAdjustment 對應(yīng)setZAdjustment(int)表示被設(shè)置動畫的內(nèi)容運行時在Z軸上的位置(top/bottom/normal),默認(rèn)為normal迅细,一般不需要設(shè)置巫橄。

Animation構(gòu)造函數(shù):一般情況用不到
Animation():duration默認(rèn)0ms,default interpolator,fillBefore默認(rèn)true茵典,fillAfter默認(rèn)false
Animation(Context context, AttributeSet attrs):利用attributeset和context初始化

3 動畫開啟的方法:start()湘换,startNow()

這兩個方法有什么區(qū)別呢?

/**
 * Convenience method to start the animation the first time
 * {@link #getTransformation(long, Transformation)} is invoked.
 */
public void start() {
    setStartTime(-1);
}

/**
 * Convenience method to start the animation at the current time in
 * milliseconds.
 */
public void startNow() {
    setStartTime(AnimationUtils.currentAnimationTimeMillis());
}

看兩個函數(shù)的注釋知道:
start()函數(shù)當(dāng)getTransformation()第一次被調(diào)用的時候開始執(zhí)行统阿。
startNow()動畫被立即執(zhí)行
start和startNow內(nèi)部都是調(diào)用setStartTime函數(shù)彩倚,setStartTime函數(shù)是設(shè)置動畫開始執(zhí)行的時間。start函數(shù)設(shè)置setStartTime(-1)會等待getTransformation第一次執(zhí)行時才開始執(zhí)行動畫扶平,startNow是setStartTime(AnimationUtils.currentAnimationTimeMillis()設(shè)置了具體的開始時間帆离,動畫會立刻開始執(zhí)行。
所以start函數(shù)調(diào)用后不是立即執(zhí)行動畫结澄,startNow是立即執(zhí)行動畫哥谷。

4 動畫真正實現(xiàn)的地方在哪里

Animation是動畫的基類,所以具體動畫的操作一定在其子類中概而,通過分析可知道呼巷,最終實現(xiàn)動畫操作在Animation類的applyTransformation()方法中囱修,各個子類會實現(xiàn)這個方法赎瑰,進行動畫操作。

/**
 * Helper for getTransformation. Subclasses should implement this to apply
 * their transforms given an interpolation value.  Implementations of this
 * method should always replace the specified Transformation or document
 * they are doing otherwise.
 *
 * @param interpolatedTime The value of the normalized time (0.0 to 1.0)
 *        after it has been run through the interpolation function.
 * @param t The Transformation object to fill in with the current
 *        transforms.
 */
protected void applyTransformation(float interpolatedTime, Transformation t) {
}

從Animation類內(nèi)部可知applyTransformation()函數(shù)會被getTransformation()函數(shù)調(diào)用破镰。Transformation類包括matrix,scale,clip等變換信息餐曼。

getTransformation()內(nèi)部調(diào)用了applyTransformation(),來看看getTransformation內(nèi)部的邏輯:

getTransformation()

getTransformation函數(shù)內(nèi)部判斷動畫是否執(zhí)行完畢,如果執(zhí)行完畢返回false鲜漩,如果動畫還沒有執(zhí)行完返回true.

public boolean getTransformation(long currentTime, Transformation outTransformation) {
//如果mStartTime == -1,初始化動畫開始時間
    if (mStartTime == -1) {
        mStartTime = currentTime;
    }

//計算動畫已經(jīng)執(zhí)行到的位置
    final long startOffset = getStartOffset();
    final long duration = mDuration;
    float normalizedTime;
    if (duration != 0) {
        normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
                (float) duration;
    } else {
              normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
    }

//判斷動畫是否被取消或者時間超過1.0f源譬,為true表示動畫結(jié)束或者已經(jīng)被取消
    final boolean expired = normalizedTime >= 1.0f || isCanceled();
//設(shè)置動畫是否完成標(biāo)識
    mMore = !expired;
//處理fillEnable
    if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
//處理其他參數(shù)
    if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
        if (!mStarted) {
            fireAnimationStart();
            mStarted = true;
            if (NoImagePreloadHolder.USE_CLOSEGUARD) {
                guard.open("cancel or detach or getTransformation");
            }
        }

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

        if (mCycleFlip) {
            normalizedTime = 1.0f - normalizedTime;
        }

        final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
//執(zhí)行動畫具體操作
        applyTransformation(interpolatedTime, outTransformation);
    }

//如果動畫已經(jīng)結(jié)束,判斷重復(fù)執(zhí)行操作
    if (expired) {
        if (mRepeatCount == mRepeated || isCanceled()) {
            if (!mEnded) {
                mEnded = true;
                guard.close();
                fireAnimationEnd();
            }
        } else {
    if (mRepeatCount > 0) {
                mRepeated++;
            }

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

            mStartTime = -1;
            mMore = true;

            fireAnimationRepeat();
        }
    }

//動畫還沒有執(zhí)行完
    if (!mMore && mOneMoreTime) {
        mOneMoreTime = false;
        return true;
    }

//所以mMore表示動畫是否執(zhí)行完了孕似,為true時表示還沒有執(zhí)行完
    return mMore;
}

getTransformation函數(shù)又是在哪里執(zhí)行的呢踩娘?

5 View 如何執(zhí)行動畫

分析getTransformation在哪里執(zhí)行我們需要先分析View如何執(zhí)行動畫。
一般的步驟是定義好Animation對象設(shè)置屬性之后喉祭,調(diào)用startAnimation()函數(shù)养渴。
View中startAnimation函數(shù)源碼:

/**
 * Start the specified animation now.
 *
 * @param animation the animation to start now
 */
public void startAnimation(Animation animation) {
    animation.setStartTime(Animation.START_ON_FIRST_FRAME);
    setAnimation(animation);
    invalidateParentCaches();
    invalidate(true);
}

首先設(shè)置了START_ON_FIRST_FRAME表示雷绢,它的值為-1,相當(dāng)于調(diào)用了Animation的start()函數(shù)理卑,然后調(diào)用了setAnimation設(shè)置了animation方法翘紊,之后調(diào)用了invalidateParentCaches和invalidate函數(shù)。startAnimation這個函數(shù)的作用是立即開始執(zhí)行動畫藐唠,所以我們就知道了執(zhí)行動畫需要設(shè)置以上四個參數(shù)帆疟。

再看View 的setAnimation的方法:

/**
 * Sets the next animation to play for this view.
 * If you want the animation to play immediately, use
 * {@link #startAnimation(android.view.animation.Animation)} instead.
 * This method provides allows fine-grained
 * control over the start time and invalidation, but you
 * must make sure that 1) the animation has a start time set, and
 * 2) the view's parent (which controls animations on its children)
 * will be invalidated when the animation is supposed to
 * start.
 *
 * @param animation The next animation, or null.
 */
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();
    }
}

setAnimation 把animation對象設(shè)置給了mCurrentAnimation,然后設(shè)置了animation的startTime,最后調(diào)用了animation的reset函數(shù)。
仔細(xì)閱讀注釋:調(diào)用了setAnimation 方法后宇立,如果想讓動畫執(zhí)行需要兩個條件踪宠,第一個是有個開始執(zhí)行的時間,另外一個是view的父類調(diào)用了invalidated方法妈嘹,這樣動畫才會執(zhí)行殴蓬。
所以還得繼續(xù)觀察invalidateParentCaches函數(shù),內(nèi)部只是設(shè)置了表示蟋滴,再看invalidate(true)方法染厅。

/**
 * This is where the invalidate() work actually happens. A full invalidate()
 * causes the drawing cache to be invalidated, but this function can be
 * called with invalidateCache set to false to skip that invalidation step
 * for cases that do not need it (for example, a component that remains at
 * the same dimensions with the same content).
 *
 * @param invalidateCache Whether the drawing cache for this view should be
 *            invalidated as well. This is usually true for a full
 *            invalidate, but may be set to false if the View's contents or
 *            dimensions have not changed.
 * @hide
 */
public void invalidate(boolean invalidateCache) {
    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}

invalidate() 內(nèi)部其實是調(diào)用了 ViewGroup 的 invalidateChild(),內(nèi)部會一直向上會執(zhí)行 ViewRootImpl 的 invalidateChildInParent() 津函,最終觸發(fā)的是ViewRootImpl 的 performTraversals()肖粮,進而執(zhí)行view的測量,布局尔苦,繪制工作涩馆。(具體流程會在后續(xù)分析view繪制流程時講解)。

所以需要執(zhí)行動畫時允坚,最終會觸發(fā)一次view樹形結(jié)構(gòu)的遍歷繪制工作魂那,動畫的執(zhí)行應(yīng)該在view的繪制過程中進行吧雹。

看View類頂部關(guān)于Animation的注釋:

* You can attach an {@link Animation} object to a view using
* {@link #setAnimation(Animation)} or
* {@link #startAnimation(Animation)}. The animation can alter the scale,
* rotation, translation and alpha of a view over time. If the animation is
* attached to a view that has children, the animation will affect the entire
* subtree rooted by that node. When an animation is started, the framework will
* take care of redrawing the appropriate views until the animation completes.
* </p>

最后一句當(dāng)animation 開始運行后颊糜,framework 將關(guān)注重新繪制view視圖知道動畫結(jié)束吨岭,所以動畫跟隨view的繪制一起執(zhí)行南吮。對應(yīng)上面的結(jié)論雳灾,動畫開始時會觸發(fā)view樹的重新繪制顶别。

View繪制過程中會調(diào)用view的draw方法审编,draw方法內(nèi)部會調(diào)用applyLegacyAnimation剪决。

//**
 * Utility function, called by draw(canvas, parent, drawingTime) to handle the less common
 * case of an active Animation being run on the view.
 */
private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
        Animation a, boolean scalingRequired) {
    Transformation invalidationTransform;
    final int flags = parent.mGroupFlags;
    final boolean initialized = a.isInitialized();
//動畫還沒有初始化拗胜,就初始化動畫并告訴子view蔗候,當(dāng)前view添加了動畫
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();
//獲取動畫是否執(zhí)行完
    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;
    }

//如果動畫沒有結(jié)束,循環(huán)調(diào)用埂软,會觸發(fā)view樹的遍歷繪制
    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;
                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;
            parent.invalidate(left, top, left + (int) (region.width() + .5f),
                    top + (int) (region.height() + .5f));
        }
    }
    return more;
}

applyLegacyAnimation這個函數(shù)內(nèi)部調(diào)用了getTransformation函數(shù)锈遥,最終動畫得到執(zhí)行,getTransformation函數(shù)會返回動畫是否完成的狀態(tài),完成為false所灸,沒完成為true儿礼,如果沒有完成會再次遍歷view樹進行繪制。

所以viewgroup下的任何一個view執(zhí)行動畫庆寺,那么都會導(dǎo)致view執(zhí)行整個繪制流程蚊夫,最終會調(diào)用viewGroup的dispatchDraw()然后內(nèi)部又調(diào)用drawChild去繪制各個子View,子view內(nèi)部調(diào)用draw方法繪制自身知纷。

view 動畫怎么繪制的呢陵霉?

既然知道了動畫是在view的draw函數(shù)中繪制的,我們看一下view的draw函數(shù)踊挠。
draw三個參數(shù)的方法:
可以看到內(nèi)部獲取了Animation和getChildTransformation,然后對畫布進行了變換效床,就實現(xiàn)了對view的動畫操作憋沿。

 /**
     * This method is called by ViewGroup.drawChild() to have each child view draw itself
     */
    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        boolean more = false沪猴;
        Transformation transformToApply = null;
        boolean concatMatrix = false;
        final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
        //獲取動畫
        final Animation a = getAnimation();
        if (a != null) {
        //有動畫,通知執(zhí)行
            more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
            concatMatrix = a.willChangeTransformationMatrix();
            if (concatMatrix) {
                mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
            }
            //獲取Transformtion信息
            transformToApply = parent.getChildTransformation();
        } else {
            运嗜。。担租。。阱洪。。
        }
        。承璃。。。隘梨。程癌。。
        int restoreTo = -1;
        //執(zhí)行動畫之前保存畫布
        if (!drawingWithRenderNode || transformToApply != null) {
            restoreTo = canvas.save();
        }
        //對畫布進行操作
        if (offsetForScroll) {
            canvas.translate(mLeft - sx, mTop - sy);
        } else {
            if (!drawingWithRenderNode) {
                canvas.translate(mLeft, mTop);
            }
            if (scalingRequired) {
                if (drawingWithRenderNode) {
                    // TODO: Might not need this if we put everything inside the DL
                    restoreTo = canvas.save();
                }
                // mAttachInfo cannot be null, otherwise scalingRequired == false
                final float scale = 1.0f / mAttachInfo.mApplicationScale;
                canvas.scale(scale, scale);
            }
        }

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

                if (offsetForScroll) {
                    transX = -sx;
                    transY = -sy;
                }

                if (transformToApply != null) {
                    if (concatMatrix) {
                        if (drawingWithRenderNode) {
                            renderNode.setAnimationMatrix(transformToApply.getMatrix());
                        } else {
                            // Undo the scroll translation, apply the transformation matrix,
                            // then redo the scroll translate to get the correct result.
                            canvas.translate(-transX, -transY);
                            canvas.concat(transformToApply.getMatrix());
                            canvas.translate(transX, transY);
                        }
                        parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
                    }

                    float transformAlpha = transformToApply.getAlpha();
                    if (transformAlpha < 1) {
                        alpha *= transformAlpha;
                        parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
                    }
                }

                if (!childHasIdentityMatrix && !drawingWithRenderNode) {
                    canvas.translate(-transX, -transY);
                    canvas.concat(getMatrix());
                    canvas.translate(transX, transY);
                }
            }

            // Deal with alpha if it is or used to be <1
            if (alpha < 1 || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
                if (alpha < 1) {
                    mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_ALPHA;
                } else {
                    mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_ALPHA;
                }
                parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
                if (!drawingWithDrawingCache) {
                    final int multipliedAlpha = (int) (255 * alpha);
                    if (!onSetAlpha(multipliedAlpha)) {
                        if (drawingWithRenderNode) {
                            renderNode.setAlpha(alpha * getAlpha() * getTransitionAlpha());
                        } else if (layerType == LAYER_TYPE_NONE) {
                            canvas.saveLayerAlpha(sx, sy, sx + getWidth(), sy + getHeight(),
                                    multipliedAlpha);
                        }
                    } else {
                        // Alpha is handled by the child directly, clobber the layer's alpha
                        mPrivateFlags |= PFLAG_ALPHA_SET;
                    }
                }
            }
        } else if ((mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {
            onSetAlpha(255);
            mPrivateFlags &= ~PFLAG_ALPHA_SET;
        }
      轴猎。嵌莉。。捻脖。锐峭。。可婶。

        //恢復(fù)畫布
        if (restoreTo >= 0) {
            canvas.restoreToCount(restoreTo);
        }
        if (more && hardwareAcceleratedCanvas) {
            if (a.hasAlpha() && (mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {
                // alpha animations should cause the child to recreate its display list
                //還有動畫繼續(xù)通知
                invalidate(true);
            }
        }

        mRecreateDisplayList = false;

        return more;
    }

繪制子view都會先對畫布狀態(tài)進行保存save()沿癞,繪制完后,又會恢復(fù)restore()矛渴,所以一個view的繪制不會影響另外一個子view的繪制椎扬,但如果該view是viewgroup,會影響到其所有的子view的繪制具温,所以動畫發(fā)生時不是類似調(diào)用invalidate蚕涤,只繪制view自身,而是由上而下铣猩,重繪ViewGroup導(dǎo)致了繪制子View钻趋,子view繪制,只是變換了自己所在的畫布的坐標(biāo)系剂习,其實屬性沒有改變蛮位。
Android動畫就是通過父view來不斷調(diào)整子view的畫布canvas坐標(biāo)系來實現(xiàn)的,發(fā)生動畫的其實是父View而不是該view鳞绕。所以 補間動畫其實只是調(diào)整了子view畫布canvas的坐標(biāo)系失仁,其實并沒有修改任何屬性,所以只能在原位置才能處理觸摸事件们何。

以上我們反向推導(dǎo)了動畫執(zhí)行的過程萄焦,下面總結(jié)一下:
當(dāng)view調(diào)用了 View.startAnimation() 時動畫并沒有馬上就執(zhí)行,會觸發(fā)遍歷view樹的繪制冤竹,
調(diào)用到 View 的 draw() 方法拂封,如果 View 有綁定動畫,那么會去調(diào)用applyLegacyAnimation()冒签,內(nèi)部調(diào)用 getTransformation() 來根據(jù)當(dāng)前時間計算動畫進度萧恕,緊接著調(diào)用 applyTransformation() 并傳入動畫進度來應(yīng)用動畫。getTransformation() 會返回動畫是否執(zhí)行完成的狀態(tài)朴读, applyLegacyAnimation() 會根據(jù) getTransformation() 的返回值來決定是否通知 ViewRootImpl 再發(fā)起一次遍歷請求衅金,遍歷 View 樹繪制簿煌,重復(fù)上面的步驟啦吧,直到動畫結(jié)束授滓。

補間動畫的繪制實際上是父布局不停地改變自己的Canvas坐標(biāo),而子view雖然位置沒有變化在孝,但是畫布所在Canvas的坐標(biāo)發(fā)生了變化視覺效果也就發(fā)生了變化私沮,其實并沒有修改任何屬性仔燕,所以只能在原位置才能處理觸摸事件晰搀。

Animation動畫概述和執(zhí)行原理
Android動畫之補間動畫TweenAnimation
Android動畫之逐幀動畫FrameAnimation
Android動畫之插值器簡介和系統(tǒng)默認(rèn)插值器
Android動畫之插值器Interpolator自定義
Android動畫之視圖動畫的缺點和屬性動畫的引入
Android動畫之ValueAnimator用法和自定義估值器
Android動畫之ObjectAnimator實現(xiàn)補間動畫和ObjectAnimator自定義屬性
Android動畫之ObjectAnimator中ofXX函數(shù)全解析-自定義Property外恕,TypeConverter鳞疲,TypeEvaluator
Android動畫之AnimatorSet聯(lián)合動畫用法
Android動畫之LayoutTransition布局動畫
Android動畫之共享元素動畫
Android動畫之ViewPropertyAnimator(專用于view的屬性動畫)
Android動畫之Activity切換動畫overridePendingTransition實現(xiàn)和Theme Xml方式實現(xiàn)
Android動畫之ActivityOptionsCompat概述
Android動畫之場景變換Transition動畫的使用
Android動畫之Transition和TransitionManager使用
Android動畫之圓形揭露動畫Circular Reveal
Android 動畫之 LayoutAnimation 動畫
Android動畫之視圖動畫的缺點和屬性動畫的引入

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市翎朱,隨后出現(xiàn)的幾起案子拴曲,更是在濱河造成了極大的恐慌澈灼,老刑警劉巖叁熔,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荣回,死亡現(xiàn)場離奇詭異心软,居然都是意外死亡删铃,警方通過查閱死者的電腦和手機猎唁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門诫隅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來逐纬,“玉大人肮街,你說我怎么就攤上這事嫉父∪葡剑” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵围小,是天一觀的道長肯适。 經(jīng)常有香客問我框舔,道長,這世上最難降的妖魔是什么樱溉? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任福贞,我火速辦了婚禮挖帘,結(jié)果婚禮上肠套,老公的妹妹穿的比我還像新娘猖任。我一直安慰自己朱躺,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著源请,像睡著了一般谁尸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抽碌,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天货徙,我揣著相機與錄音,去河邊找鬼赏迟。 笑死瀑梗,一個胖子當(dāng)著我的面吹牛裳扯,可吹牛的內(nèi)容都是我干的饰豺。 我是一名探鬼主播冤吨,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼漩蟆,長吁一口氣:“原來是場噩夢啊……” “哼妓蛮!你這毒婦竟也來了捺癞?” 一聲冷哼從身側(cè)響起髓介,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤筋现,失蹤者是張志新(化名)和其女友劉穎矾飞,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凰慈,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡森篷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了买乃。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钓辆。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡功戚,死狀恐怖似嗤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情乘粒,我是刑警寧澤灯萍,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布旦棉,位于F島的核電站熊痴,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏诊笤。R本人自食惡果不足惜讨跟,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一晾匠、第九天 我趴在偏房一處隱蔽的房頂上張望梯刚。 院中可真熱鬧向叉,春花似錦嗦董、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冗栗。三九已至供搀,卻和暖如春葛虐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背屿脐。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工的诵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留西疤,地道東北人代赁。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓芭碍,卻偏偏與公主長得像孽尽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子狐蜕,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,060評論 2 355

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