WindowManagerService本地窗口動(dòng)畫

一、前言

android的WindowManagerService(簡稱wms)是系統(tǒng)框架一個(gè)非常龐大復(fù)雜的一個(gè)系統(tǒng)模塊于游,它主要由三大塊組成:wms數(shù)據(jù)結(jié)構(gòu)毁葱,wms大遍歷,wms的窗口動(dòng)畫


wms總體圖.png

wms數(shù)據(jù)結(jié)構(gòu)就是wms的所有WindowState(繼承windowcontainer)集合的數(shù)據(jù)結(jié)構(gòu)贰剥,比如有ActivityRecord(包含1個(gè)或者多個(gè)WindowState)倾剿,比如有WindowState,其中ActivityRecord具體表現(xiàn)實(shí)例就是Activity,WindowState具體表現(xiàn)實(shí)例有狀態(tài)欄前痘、導(dǎo)航鍵凛捏、輸入法等。


window.png

wms大遍歷(performSurfacePlacement)就是對(duì)當(dāng)前所有存在的window進(jìn)行窗口大小計(jì)算和窗口繪制狀態(tài)更新芹缔,最后把窗口Surface更新到surfaceflinger坯癣。

wms的窗口動(dòng)畫是其中一個(gè)比較重要的子功能,wms的窗口動(dòng)畫負(fù)責(zé)窗口間的切換動(dòng)畫的實(shí)現(xiàn)最欠。

接下來我們從android動(dòng)畫原理開始來逐步介紹wms的窗口動(dòng)畫

二示罗、android動(dòng)畫的一個(gè)demo

android動(dòng)畫主要有三種類型:view的動(dòng)畫、window的動(dòng)畫芝硬、畫布對(duì)象的動(dòng)畫(ondraw里面的畫圖api)
首先我們來看一個(gè)android動(dòng)畫的簡單實(shí)現(xiàn)的demo

Choreographer mChoreographer = Choreographer.getInstance();
Animation mAnimation = null;

public void start(Animation anim) {
    mAnimation = anim;
    scheduleAnimation();
}

private void scheduleAnimation() {
    mChoreographer.postFrameCallback(Choreographer.CALLBACK_ANIMTION, mUpdateRunnable, null);
}

private Runnable mUpdateRunnable = new Runnable() {
    @Override
    public void run() {
        if (mAnimation != null) {
            long time = SystemClock.uptimeMillis();
            Transformation transform = new Transformation();
            //根據(jù)當(dāng)前time計(jì)算transform
            boolean more = mAnimation.getTransformation(time, transform);
            
            //根據(jù)transform進(jìn)行渲染蚜点,改變view的屬性(大小、位置拌阴、透明度等)绍绘?改變窗口的屬性(大小、位置迟赃、透明度等)陪拘?
            PERFORM_RENDER_WITH_TRANSFORMATION(transform);
            
            //通過time的計(jì)算可以計(jì)算出動(dòng)畫是否繼續(xù)還是結(jié)束
            if (more) {
                scheduleAnimation();
            } else {
                mAnimation = null;
            }
        }
    }
};

從這個(gè)例子可以看出android的動(dòng)畫就是借用Choreographer來通過vsync原理逐幀控制動(dòng)畫的播放(需要對(duì)Choreographer有一定的了解)捺氢,中間update變量transform包含了動(dòng)畫的基本元素:Matrix藻丢、透明度,然后根據(jù)這兩個(gè)元素對(duì)顯示對(duì)象(view或者畫布對(duì)象或者window摄乒?)進(jìn)行當(dāng)前時(shí)間的繪制悠反,逐幀顯示,最終用戶看到的就是一個(gè)動(dòng)畫馍佑,從systrace可以看到


動(dòng)畫systrace.png

ValueAnimator屬性動(dòng)畫的實(shí)現(xiàn)原理也是類似于這個(gè)demo的實(shí)現(xiàn)

三斋否、WindowManagerService窗口動(dòng)畫機(jī)制

android的WindowManagerService窗口動(dòng)畫機(jī)制一直在優(yōu)化進(jìn)步,主要體現(xiàn)在:
1拭荤、在androidP以前的版本茵臭,主要是通過WindowAnimator主動(dòng)畫類中的mChoreographer來通過vsync原理逐幀控制窗口動(dòng)畫的播放
具體窗口的動(dòng)畫變化由WindowStateAnimator的stepAnimationLocked來控制,通過改變窗口的大小舅世、位置旦委、透明度(通過SurfaceControl代理實(shí)現(xiàn)對(duì)surfaceflinger的調(diào)用),來最終達(dá)到窗口動(dòng)畫的實(shí)現(xiàn)


wms歷史版本動(dòng)畫時(shí)序圖.png

有興趣的可以去仔細(xì)研究下這部分代碼的實(shí)現(xiàn)雏亚,雖然是歷史版本的舊代碼缨硝,但是這個(gè)對(duì)wms的學(xué)習(xí)理解有很大的幫助。

/*frameworks/base/services/core/java/com/android/wm/WindowAnimator.java */

/** Locked on mService.mWindowMap. */
private void animateLocked(long frameTimeNs) {

這個(gè)方案有個(gè)很大的缺陷罢低,那就是動(dòng)畫的所有實(shí)現(xiàn)的代碼都包含在wms的主鎖mGlobalLock里面查辩,從動(dòng)畫主要方法的命名后綴locked可以得知,那么意味動(dòng)畫會(huì)跟wms其他所有流程搶CPU資源,就容易導(dǎo)致wms主鎖的卡頓宜岛,在某些復(fù)雜的用戶場(chǎng)景下长踊,容易導(dǎo)致手機(jī)的卡頓,給用戶帶來糟糕的體驗(yàn)萍倡。

2身弊、在androidP及之后的版本,google對(duì)窗口動(dòng)畫進(jìn)行了重構(gòu)遣铝,主要思想是通過ValueAnimator屬性動(dòng)畫來播放窗口動(dòng)畫佑刷,把窗口動(dòng)畫播放從wms主鎖脫離出來,這樣動(dòng)畫就不會(huì)占用wms資源酿炸,從而達(dá)到優(yōu)化系統(tǒng)框架運(yùn)行速度的效果,同時(shí)把部分動(dòng)畫放到app遠(yuǎn)端播放(比如狀態(tài)欄涨冀、導(dǎo)航鍵動(dòng)畫填硕,比如多任務(wù)動(dòng)畫),達(dá)到系統(tǒng)和APP雙端協(xié)調(diào)播放復(fù)雜的跨端動(dòng)畫效果

3鹿鳖、wms的新窗口動(dòng)畫主要分為兩種類型扁眯,LocalAnimationAdapter和RemoteAnimationAdapter,分別實(shí)現(xiàn)了wms本地窗口動(dòng)畫和遠(yuǎn)程窗口動(dòng)畫翅帜。遠(yuǎn)程窗口動(dòng)畫機(jī)制姻檀,主要是為了實(shí)現(xiàn)android的兩個(gè)新功能特意開發(fā)的機(jī)制,一個(gè)是從桌面點(diǎn)擊app圖標(biāo)進(jìn)入app的入場(chǎng)動(dòng)畫和app退出的出場(chǎng)動(dòng)畫涝滴,一個(gè)是在app界面绣版,通過拖動(dòng)底部指示條進(jìn)入桌面的滑動(dòng)效果動(dòng)畫,這兩個(gè)動(dòng)畫效果最先是iphone實(shí)現(xiàn)的歼疮,google為了仿iphone的實(shí)現(xiàn)杂抽,所以開發(fā)了遠(yuǎn)程動(dòng)畫機(jī)制,最終能達(dá)到iphone的動(dòng)畫效果韩脏,提高了android手機(jī)的復(fù)雜動(dòng)畫效果

4缩麸、本文主要介紹android新動(dòng)畫的流程和實(shí)現(xiàn)的原理,主要介紹了LocalAnimationAdapter本地窗口動(dòng)畫實(shí)現(xiàn)原理

四赡矢、新動(dòng)畫機(jī)制Local窗口動(dòng)畫流程

本地窗口動(dòng)畫具體場(chǎng)景:可以在設(shè)置首頁點(diǎn)擊其中一項(xiàng)菜單杭朱,進(jìn)入設(shè)置某項(xiàng)子菜單,然后就會(huì)有一個(gè)Local窗口動(dòng)畫的播放

LocalAnimationAdapter吹散,字面意思就是wms本地窗口動(dòng)畫弧械,該動(dòng)畫在SurfaceAnimationThread(線程名android.anim.lf)線程播放,注意看該類的注釋
(本文剩余源碼基于androidS原生源碼)

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimationThread.java */

/**
 * Thread for running {@link SurfaceAnimationRunner} that does not hold the window manager lock.
 */
public final class SurfaceAnimationThread extends ServiceThread {

這個(gè)才是新動(dòng)畫機(jī)制的核心要義送浊,不占用wms主鎖梦谜,就不會(huì)占用wms的資源,這個(gè)已經(jīng)是對(duì)wms很大的優(yōu)化了,android歷史版本因?yàn)閣ms鎖卡頓的問題太多了

接下來我們通過閱讀源碼來分析LocalAnimationAdapter的實(shí)現(xiàn)流程唁桩,先看下整體的Local窗口動(dòng)畫時(shí)序圖


Local窗口動(dòng)畫時(shí)序圖.png

1闭树、動(dòng)畫播放源頭類AppTransitionController的方法handleAppTransitionReady
在一次wms大遍歷(performSurfacePlacement)流程結(jié)束之后,就會(huì)檢查app transition是否已經(jīng)準(zhǔn)備好荒澡,opening 的app準(zhǔn)備好需要滿足app的starting窗口是否已經(jīng)displayed或者app的window是否已經(jīng)alldrawn报辱,只要滿足其中一個(gè)條件,就說明app的窗口動(dòng)畫流程可以開始了了单山。

/*frameworks/base/services/core/java/com/android/wm/AppTransitionController.java */

    void handleAppTransitionReady() {
        mTempTransitionReasons.clear();
        //檢查app transition是否已經(jīng)準(zhǔn)備好
        if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons)
                || !transitionGoodToGo(mDisplayContent.mChangingContainers,
                        mTempTransitionReasons)) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");

        ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "**** GOOD TO GO");

首先會(huì)獲取當(dāng)前需要opening和closing的app window列表(ActivityRecord類型)

/*frameworks/base/services/core/java/com/android/wm/AppTransitionController.java */

        final ActivityRecord topOpeningApp =
                getTopApp(mDisplayContent.mOpeningApps, false /* ignoreHidden */);
        final ActivityRecord topClosingApp =
                getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */);

然后在applyAnimations方法里面對(duì)window列表進(jìn)行遍歷WindowContainer的動(dòng)畫applyAnimation方法的調(diào)用

/*frameworks/base/services/core/java/com/android/wm/AppTransitionController.java */

     private void applyAnimations(ArraySet<WindowContainer> wcs, ArraySet<ActivityRecord> apps,
            @TransitionOldType int transit, boolean visible, LayoutParams animLp,
            boolean voiceInteraction) {
        final int wcsCount = wcs.size();
        for (int i = 0; i < wcsCount; i++) {
            final WindowContainer wc = wcs.valueAt(i);
            final ArrayList<ActivityRecord> transitioningDescendants = new ArrayList<>();
            for (int j = 0; j < apps.size(); ++j) {
                final ActivityRecord app = apps.valueAt(j);
                if (app.isDescendantOf(wc)) {
                    transitioningDescendants.add(app);
                }
            }
            wc.applyAnimation(animLp, transit, visible, voiceInteraction, transitioningDescendants);
        }
    }

2碍现、在WindowContainer,會(huì)先收集getAnimationAdpater當(dāng)前window的動(dòng)畫適配器

/*frameworks/base/services/core/java/com/android/wm/WindowContainer.java */

    protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
            @TransitionOldType int transit, boolean isVoiceInteraction,
            @Nullable ArrayList<WindowContainer> sources) {
 
        final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
                transit, enter, isVoiceInteraction);

如果是普通的窗口動(dòng)畫米奸,比如app內(nèi)部activity的切換昼接,當(dāng)前的場(chǎng)景是設(shè)置主菜單跳轉(zhuǎn)子菜單,根據(jù)當(dāng)前場(chǎng)景獲取到具體的transit悴晰,transit=TRANSIT_OLD_ACTIVITY_OPEN慢睡,然后再結(jié)合enter為true或者false,可以最終可以找到設(shè)置主菜單的動(dòng)畫xml資源是activity_open_exit.xml铡溪,設(shè)置子菜單的動(dòng)畫xml資源是activity_open_enter.xml漂辐,在獲取到具體xml資源名字后,通過AnimationUtils.loadAnimation方法把xml資源轉(zhuǎn)成Animation對(duì)象棕硫。
之后就會(huì)創(chuàng)建一個(gè)WindowAnimationSpec對(duì)象髓涯,并把Animation對(duì)象作為構(gòu)造方法的第一個(gè)參數(shù)傳給了WindowAnimationSpec

/*frameworks/base/services/core/java/com/android/wm/WindowContainer.java */

                final Animation a = loadAnimation(lp, transit, enter, isVoiceInteratction);
                AnimationAdapter adapter = new LocalAnimationAdapter(
                        //創(chuàng)建了一個(gè)WindowAnimatonSpec對(duì)象作為LocalAnimationAdapter的初始化參數(shù)
                        new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
                                getDisplayContent().mAppTransition.canSkipFirstFrame(),
                                appRootTaskClipMode, true /* isAppAnimation */, windowCornerRadius),
                        getSurfaceAnimationRunner());

這里創(chuàng)建LocalAnimationAdapter對(duì)象的時(shí)候同時(shí)創(chuàng)建了一個(gè)WindowAnimatonSpec對(duì)象作為LocalAnimationAdapter的初始化參數(shù),這個(gè)類WindowAnimatonSpec比較重要哈扮,是在后續(xù)窗口動(dòng)畫播放的時(shí)候具體的實(shí)現(xiàn)類纬纪,后面再分析
在獲取到具體的Adaper對(duì)象之后,就開始執(zhí)行startAnimation方法灶泵,這個(gè)方法里面主要調(diào)用了mSurfaceAnimator對(duì)象育八,來實(shí)現(xiàn)startAnimation

/*frameworks/base/services/core/java/com/android/wm/WindowContainer.java */

        mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
                mSurfaceFreezer);

tip: WindowContainer這個(gè)類是wms的最重要類之一,它是所有window的基類赦邻,充分學(xué)習(xí)理解該類可以對(duì)wms的所有window的樹狀圖有一定的理解

3髓棋、SurfaceAnimator類,字面上的意思就是window動(dòng)畫實(shí)現(xiàn)是交給它來實(shí)現(xiàn)surfacecontrol的動(dòng)畫(舊窗口動(dòng)畫是通過WindowSurfaceController控制surfacecontrol)惶洲,該類的就是窗口動(dòng)畫的中控按声,它的主要作用是在startAnimation的時(shí)候,對(duì)要進(jìn)行動(dòng)畫的surfacecontrol創(chuàng)建一個(gè)parent的surfacecontrol類型的mLeash對(duì)象恬吕,leash的翻譯是用皮帶系住的意思签则,相當(dāng)于把要進(jìn)行動(dòng)畫的surfacecontrol用皮帶系住,通過操控mLeash對(duì)象來實(shí)現(xiàn)窗口的大小铐料、位置渐裂、透明度等動(dòng)畫屬性的改變豺旬。

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimator.java */

    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
            @AnimationType int type,
            @Nullable OnAnimationFinishedCallback animationFinishedCallback,
            @Nullable SurfaceFreezer freezer) {
        cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
        mAnimation = anim;
        mAnimationType = type;
        mAnimationFinishedCallback = animationFinishedCallback;
        final SurfaceControl surface = mAnimatable.getSurfaceControl();
        mLeash = freezer != null ? freezer.takeLeashForAnimation() : null;
        if (mLeash == null) {
            //重點(diǎn)關(guān)注這個(gè)mLeash對(duì)象,該對(duì)象是窗口動(dòng)畫專屬surfacecontrol包裝對(duì)象
            mLeash = createAnimationLeash(mAnimatable, surface, t, type,
                    mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), 0 /* x */,
                    0 /* y */, hidden, mService.mTransactionFactory);
            mAnimatable.onAnimationLeashCreated(t, mLeash);
        }
        mAnimatable.onLeashAnimationStarting(t, mLeash);
        mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
    }

然后在動(dòng)畫結(jié)束之后柒凉,mLeash對(duì)象會(huì)走銷毀的流程族阅,同時(shí)動(dòng)畫的surfacecontrol進(jìn)行reparent還原操作。

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimator.java */

    static boolean removeLeash(Transaction t, Animatable animatable, @NonNull SurfaceControl leash,
            boolean destroy) {
        boolean scheduleAnim = false;
        final SurfaceControl surface = animatable.getSurfaceControl();
        final SurfaceControl parent = animatable.getParentSurfaceControl();
        final boolean reparent = surface != null;
        if (reparent) {
            if (surface.isValid() && parent != null && parent.isValid()) {
                t.reparent(surface, parent);
                scheduleAnim = true;
            }
        }

窗口動(dòng)畫中控最終調(diào)用了動(dòng)畫的適配類LocalAnimationAdapter的startAnimation方法膝捞。

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimator.java */

        mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);

4坦刀、LocalAnimationAdapter是窗口動(dòng)畫的適配類,繼承自AnimationAdaper蔬咬,LocalAnimationAdapter實(shí)現(xiàn)的是wms本地窗口動(dòng)畫鲤遥,所以LocalAnimationAdapter可以理解成本地窗口動(dòng)畫的中轉(zhuǎn)類。在startAnimation方法里面林艘,調(diào)用了SurfaceAnimationRunner來最終實(shí)現(xiàn)動(dòng)畫的播放盖奈。

/*frameworks/base/services/core/java/com/android/wm/LocalAnimationAdapter.java */

    @Override
    public void startAnimation(SurfaceControl animationLeash, Transaction t,
            @AnimationType int type, OnAnimationFinishedCallback finishCallback) {
        mAnimator.startAnimation(mSpec, animationLeash, t,
                () -> finishCallback.onAnimationFinished(type, this));
    }

5、SurfaceAnimationRunner是本地窗口動(dòng)畫真正的實(shí)現(xiàn)類北启,主要需要關(guān)注的方法是startAnimationLocked卜朗,首先這個(gè)方法已經(jīng)通過mChoreographer切換到SurfaceAnimationThread線程來執(zhí)行,然后創(chuàng)建了ValueAnimator屬性動(dòng)畫對(duì)象咕村,交由ValueAnimator屬性動(dòng)畫對(duì)象的addUpdateListener方法來實(shí)現(xiàn)逐幀控制動(dòng)畫mLeash對(duì)象(surfacecontrol類型)的變化,具體的update方法的實(shí)現(xiàn)是在WindowAnimatonSpec類的apply方法里面蚊俺。

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimationRunner.java */

    private void startAnimationLocked(RunningAnimation a) {
        final ValueAnimator anim = mAnimatorFactory.makeAnimator();

        anim.overrideDurationScale(1.0f);
        anim.setDuration(a.mAnimSpec.getDuration());
        anim.addUpdateListener(animation -> {
            synchronized (mCancelLock) {
                if (!a.mCancelled) {
                    final long duration = anim.getDuration();
                    long currentPlayTime = anim.getCurrentPlayTime();
                    if (currentPlayTime > duration) {
                        currentPlayTime = duration;
                    }
                    applyTransformation(a, mFrameTransaction, currentPlayTime);
                }
            }

            scheduleApplyTransaction();
        });

        ………………
        a.mAnim = anim;
        mRunningAnimations.put(a.mLeash, a);

        //窗口動(dòng)畫最終調(diào)用了屬性動(dòng)畫播放
        anim.start();
        anim.doAnimationFrame(mChoreographer.getFrameTime());
    }

再來看下apply方法的具體實(shí)現(xiàn)懈涛,通過之前以具體xml資源創(chuàng)建的mAnimation對(duì)象,根據(jù)當(dāng)前時(shí)間片currentPlayTime獲取到當(dāng)前的tmp.transformation泳猬,對(duì)leash對(duì)象實(shí)現(xiàn)了Matrix(大小批钠,位置),Alpha得封,Crop等transformation變化埋心,再通過Transaction 交給surfaceflinger顯示,從而實(shí)現(xiàn)了動(dòng)畫當(dāng)前時(shí)間片的顯示效果忙上。對(duì)比舊動(dòng)畫機(jī)制拷呆,這個(gè)transformation變化是在WindowStateAnimator類里面實(shí)現(xiàn)的。為什么要重點(diǎn)關(guān)注這個(gè)方法呢疫粥?因?yàn)槿绻翱趧?dòng)畫出bug了(位置大小不對(duì)茬斧?透明度異常?)梗逮,就可以在這個(gè)方法里面打印window的相關(guān)參數(shù)來初步定位原因项秉。

/*frameworks/base/services/core/java/com/android/wm/WindowAnimatonSpec.java */

    @Override
    public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) {
        final TmpValues tmp = mThreadLocalTmps.get();
        tmp.transformation.clear();
        mAnimation.getTransformation(currentPlayTime, tmp.transformation);
        tmp.transformation.getMatrix().postTranslate(mPosition.x, mPosition.y);
        t.setMatrix(leash, tmp.transformation.getMatrix(), tmp.floats);
        t.setAlpha(leash, tmp.transformation.getAlpha());

        boolean cropSet = false;
        if (mRootTaskClipMode == ROOT_TASK_CLIP_NONE) {
            if (tmp.transformation.hasClipRect()) {
                t.setWindowCrop(leash, tmp.transformation.getClipRect());
                cropSet = true;
            }
        } else {
            mTmpRect.set(mRootTaskBounds);
            if (tmp.transformation.hasClipRect()) {
                mTmpRect.intersect(tmp.transformation.getClipRect());
            }
            t.setWindowCrop(leash, mTmpRect);
            cropSet = true;
        }
    }

6、ValueAnimator類慷彤,從上面的介紹可以得知娄蔼,窗口動(dòng)畫的最終本質(zhì)就是一個(gè)ValueAnimator屬性動(dòng)畫怖喻,理解了這一點(diǎn),就相當(dāng)于把窗口動(dòng)畫簡單化了岁诉,最終的實(shí)現(xiàn)就類比于我們普通app的屬性動(dòng)畫的實(shí)現(xiàn)(app屬性動(dòng)畫的對(duì)象是view锚沸,窗口屬性動(dòng)畫的對(duì)象是window),只不過整個(gè)流程比較復(fù)雜而已唉侄,但是最終的實(shí)現(xiàn)原理是一樣的咒吐,殊途同歸,這個(gè)才是android窗口動(dòng)畫機(jī)制的精髓所在属划。

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimationRunner.java */

ValueAnimator anim = mAnimatorFactory.makeAnimator();
anim.addUpdateListener(animation -> {
            applyTransformation(a, mFrameTransaction, currentPlayTime);
        });
anim.start();

總結(jié)

本文只是講解了WindowManagerService窗口動(dòng)畫之本地窗口動(dòng)畫的原生實(shí)現(xiàn)流程恬叹,主要是WindowManagerService的子類比較多,所以我們從動(dòng)畫的源頭handleAppTransitionReady一步一步分析了它的整個(gè)流程同眯,從整個(gè)流程來看绽昼,本地窗口動(dòng)畫的邏輯比較清晰,線路也比較單一须蜗,比較容易學(xué)習(xí)和理解硅确,最終我們看到,窗口動(dòng)畫的原理就是一個(gè)屬性動(dòng)畫明肮,在動(dòng)畫update方法里面操控了窗口surface的屬性變化菱农,從而實(shí)現(xiàn)了窗口動(dòng)畫的逐幀播放。另外還有一個(gè)重點(diǎn)需要關(guān)注到柿估,那就是wms的窗口動(dòng)畫不需要占用wms主鎖循未,而且是單獨(dú)線程,這樣的設(shè)計(jì)也能在一定程度上優(yōu)化系統(tǒng)卡頓的問題秫舌。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末的妖,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子足陨,更是在濱河造成了極大的恐慌嫂粟,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件墨缘,死亡現(xiàn)場(chǎng)離奇詭異星虹,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)飒房,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門搁凸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人狠毯,你說我怎么就攤上這事护糖。” “怎么了嚼松?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵嫡良,是天一觀的道長锰扶。 經(jīng)常有香客問我,道長寝受,這世上最難降的妖魔是什么坷牛? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮很澄,結(jié)果婚禮上京闰,老公的妹妹穿的比我還像新娘。我一直安慰自己甩苛,他們只是感情好蹂楣,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著讯蒲,像睡著了一般痊土。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上墨林,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天赁酝,我揣著相機(jī)與錄音,去河邊找鬼旭等。 笑死酌呆,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的搔耕。 我是一名探鬼主播肪笋,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼度迂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起猜揪,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤惭墓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后而姐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腊凶,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年拴念,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了钧萍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡政鼠,死狀恐怖风瘦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情公般,我是刑警寧澤万搔,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布胡桨,位于F島的核電站,受9級(jí)特大地震影響瞬雹,放射性物質(zhì)發(fā)生泄漏昧谊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一酗捌、第九天 我趴在偏房一處隱蔽的房頂上張望呢诬。 院中可真熱鬧,春花似錦胖缤、人聲如沸尚镰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽钓猬。三九已至,卻和暖如春撩独,著一層夾襖步出監(jiān)牢的瞬間敞曹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國打工综膀, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留澳迫,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓剧劝,卻偏偏與公主長得像橄登,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子讥此,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

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