Android Window 如何確定大小/onMeasure()多次執(zhí)行原因

改文章為轉(zhuǎn)載來源:
作者:fishforest
鏈接:http://www.reibang.com/p/6e45f42da304
來源:簡書
著作權(quán)歸作者所有序厉。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)疏遏,非商業(yè)轉(zhuǎn)載請注明出處锉屈。

前言

之前系統(tǒng)地分析了View Measure 過程:
Android 自定義View之Measure過程
我們知道父布局根據(jù)自身和子布局的要求給子布局生成測量模式和測量尺寸瓤帚,并封裝在MeasureSpec 對象里觉痛,最終傳遞給子布局讓它最后確定自身的尺寸君旦。
很自然就會想到贡蓖,既然子布局是從父布局拿的測量結(jié)果甩牺,父布局又從它的父布局拿測量結(jié)果蘑志,最終到ViewTree的頂點根View是誰測量的呢?
循著這個問題贬派,從源碼角度一探究竟急但。

系列文章:

Window/WindowManager 不可不知之事
Android Window 如何確定大小/onMeasure()多次執(zhí)行原因

通過本篇文章,你將了解到:

1搞乏、Window 尺寸測量
2波桩、根View 尺寸測量
3、Window请敦、ViewRootImpl镐躲、View 三者關(guān)系

1、Window 尺寸測量

一個小Demo

通過WindowManager.addView(xx)展示一個懸浮窗:

    private void showView() {
        //獲取WindowManager實例
        wm = (WindowManager) App.getApplication().getSystemService(Context.WINDOW_SERVICE);

        //設(shè)置LayoutParams屬性
        layoutParams = new WindowManager.LayoutParams();
        //寬高尺寸
        layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
        layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
        layoutParams.format = PixelFormat.TRANSPARENT;
        //設(shè)置背景陰暗
        layoutParams.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
        layoutParams.dimAmount = 0.6f;

        //Window類型
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        } else {
            layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
        }

        //構(gòu)造TextView
        TextView myView = new TextView(this);
        myView.setText("hello window");
        //設(shè)置背景為紅色
        myView.setBackgroundResource(R.color.colorRed);
        FrameLayout.LayoutParams myParam = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 400);
        myParam.gravity = Gravity.CENTER;
        myView.setLayoutParams(myParam);

        //myFrameLayout 作為rootView
        FrameLayout myFrameLayout = new FrameLayout(this);
        //設(shè)置背景為綠色
        myFrameLayout.setBackgroundColor(Color.GREEN);
        myFrameLayout.addView(myView);

        //添加到window
        wm.addView(myFrameLayout, layoutParams);
    }

上述代碼簡單概述如下:

1侍筛、構(gòu)造TextView萤皂,設(shè)置其背景為紅色
2、構(gòu)造FrameLayout匣椰,設(shè)置其背景為綠色
3裆熙、將TextView作為子View添加到FrameLayout
4、將FrameLayout作為RootView(根View)添加到Window里

懸浮窗展示完整Demo請移步:Window/WindowManager 不可不知之事

注意到

wm.addView(myFrameLayout, layoutParams);

layoutParams 里重點關(guān)注寬、高字段的值入录,我們知道這是給Window的尺寸約束齐媒,以寬為例,設(shè)置不同的值纷跛,看看其效果:
1喻括、wrap_content

layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;

image

可以看出:RootView(FrameLayout) 包裹著TextView,兩者寬度一致贫奠。

2唬血、match_parent

layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;

image

可以看出:RootView(FrameLayout) 寬充滿屏幕

3、設(shè)置具體的值

layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
layoutParams.width = 800;

image

可以看出:RootView(FrameLayout) 寬沒充滿屏幕唤崭,屏幕寬1080px拷恨。

結(jié)合上述三張圖,我們有理由相信谢肾,wm.addView(myFrameLayout, layoutParams) 里的layoutParams 是用來約束myFrameLayout(RootView)腕侄,那么Window尺寸是怎么來的呢?

Window 尺寸的確定

從wm.addView(xx)開始分析芦疏,WindowManager 是個接口冕杠,其實現(xiàn)類是:WindowManagerImpl。

#WindowManagerImpl.java
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        //賦值token酸茴,在啟動Dialog/PopupDialog 會判斷該值
        applyDefaultToken(params);
        //mGlobal 為單例分预,管理所有的ViewRootImpl、RootView
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

接著看WindowManagerGlobal 的處理:

#WindowManagerGlobal.java
    public void addView(View view, ViewGroup.LayoutParams params,
                        Display display, Window parentWindow) {
        ...
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            ...
            //構(gòu)造ViewRootImpl 對象
            root = new ViewRootImpl(view.getContext(), display);

            //view 作為RootView
            //將傳進(jìn)來的wparams作為該RootView的LayoutParams
            view.setLayoutParams(wparams);

            //記錄對象
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            try {
                //ViewRootImpl 關(guān)聯(lián)RootView
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                ...
            }
        }
    }

由上可知薪捍,在wm.addView(xx)里傳遞進(jìn)來的LayoutParams設(shè)置給了RootView笼痹。
繼續(xù)來看ViewRootImpl.setView(xx)過程。

#ViewRootImpl.java
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ...
                //將LayoutParams記錄到成員變量 mWindowAttributes 里
                //該變量用來描述Window屬性
                mWindowAttributes.copyFrom(attrs);
                ...
                //開啟View layout 三大流程
                requestLayout();
                ...
                try {
                    ...
                    //IPC 通信酪穿,告訴WindowManagerService 要創(chuàng)建Window
                    //將mWindowAttributes 傳入
                    //返回mTmpFrame 表示該Window可以展示的最大尺寸
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                            mTempInsets);
                    //將返回的值記錄到成員變量 mWinFrame 里
                    setFrame(mTmpFrame);
                } catch (RemoteException e) {
                    ...
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }
                ...
            }
        }
    }

上面這段重點關(guān)注2個方面:

1凳干、傳入的LayoutParams記錄到成員變量mWindowAttributes,最后用來約束Window被济。
2救赐、添加Window時返回Window的最大尺寸,最終記錄在成員變量:mWinFrame里溉潭。

綜上所述净响,我們發(fā)現(xiàn):
wm.addView(myFrameLayout, layoutParams) 里的layoutParams不僅約束了RootView少欺,也約束了Window喳瓣。

2、根View 尺寸測量

既然知道了RootView 的layoutParams赞别,依據(jù)我們之前分析過的ViewTree的測量過程:Android 自定義View之Measure過程
可知還需要為RootView生成MeasureSpec對象畏陕。
在setView(xx)過程中調(diào)用了requestLayout 注冊了回調(diào),當(dāng)屏幕刷新信號到來之時執(zhí)行performTraversals()開啟三大流程仿滔。

#ViewRootImpl.java
    private void performTraversals() {
        ...
        //之前記錄的Window LayoutParams
        WindowManager.LayoutParams lp = mWindowAttributes;

        //Window需要的大小
        int desiredWindowWidth;
        int desiredWindowHeight;
        ...

        Rect frame = mWinFrame;
        if (mFirst) {
            ...
            if (shouldUseDisplaySize(lp)) {
                ...
            } else {
                //mWinFrame即是之前添加Window時返回的Window最大尺寸
                desiredWindowWidth = mWinFrame.width();
                desiredWindowHeight = mWinFrame.height();
            }
            ...
        } else {
            ...
        }

        ...
        if (layoutRequested) {
            ...
            //從方法名看應(yīng)該是測量ViewTree -----------(1)
            windowSizeMayChange |= measureHierarchy(host, lp, res,
                    desiredWindowWidth, desiredWindowHeight);
        }
        ...

        if (mFirst || windowShouldResize || insetsChanged ||
                viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
            ...
            try {
                ...
                //重新確定Window尺寸 --------(2)
                relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
                ...
            } catch (RemoteException e) {
            }
            ...
            if (!mStopped || mReportNextDraw) {
                ...
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                        updatedConfiguration) {
                    ...
                    //再次測量ViewTree -------- (3)
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    ...
                }
            }
        } else {
            ...
        }
        ...
        if (didLayout) {
            //對ViewTree 進(jìn)行Layout ---------- (4)
            performLayout(lp, mWidth, mHeight);
            ...
        }
        ...
        if (!cancelDraw) {
            ...
            //開始ViewTree Draw過程 ------- (5)
            performDraw();
        } else {
            ...
        }
    }

來看看標(biāo)注的重點:
(1)
measureHierarchy(xx)

#ViewRootImpl.java
    private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
                                     final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
        int childWidthMeasureSpec;
        int childHeightMeasureSpec;
        boolean windowSizeMayChange = false;
        ...

        //標(biāo)記是否測量成功
        boolean goodMeasure = false;
        //寬度為wrap_content時
        if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
            final DisplayMetrics packageMetrics = res.getDisplayMetrics();
            res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
            int baseSize = 0;
            if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
                baseSize = (int)mTmpValue.getDimension(packageMetrics);
            }
            //baseSize 為預(yù)置的寬度
            //desiredWindowWidth 想要的寬度是否大于預(yù)置寬度
            if (baseSize != 0 && desiredWindowWidth > baseSize) {
                //以baseSize 作為寬度傳入
                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
                //測量----------------- 第一次
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                //如果ViewTree的子布局需要的寬度大于父布局能給的寬度惠毁,則該標(biāo)記被設(shè)置
                if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                    //該標(biāo)記沒被設(shè)置犹芹,說明父布局給的尺寸夠用,測量完成
                    goodMeasure = true;
                } else {
                    //父布局不能滿足子布局的需求鞠绰,嘗試擴(kuò)大寬度
                    //desiredWindowWidth > baseSize腰埂,因此新計算的baseSize要大于原先的baseSize
                    baseSize = (baseSize+desiredWindowWidth)/2;
                    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                    //拿到后繼續(xù)測量----------------- 第二次
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    //繼續(xù)檢測是否滿足
                    if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                        goodMeasure = true;
                    }
                }
            }
        }

        //沒測量好,繼續(xù)測量
        if (!goodMeasure) {
            //可以看出是為RootView 生成MeasureSpec
            //傳入的參數(shù):能給RootView分配的最大尺寸值以及RootView本身想要的尺寸(記錄在LayoutParams里)
            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);

            //既然MeasureSpec 有了蜈膨,那么就可以測量RootView了
            //該過程就是測量整個ViewTree----------------- 第三次
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                //Window尺寸變化了屿笼,用于后續(xù)判斷執(zhí)行performMeasure(xx)
                windowSizeMayChange = true;
            }
        }
        ...
        return windowSizeMayChange;
    }

    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

            case ViewGroup.LayoutParams.MATCH_PARENT:
                //RootView 希望填充Window,則滿足它翁巍,此時它尺寸是確切值
                measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
                break;
            case ViewGroup.LayoutParams.WRAP_CONTENT:
                //RootView 希望根據(jù)自身內(nèi)容來確定尺寸驴一,則設(shè)置為AT_MOST 模式
                measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
                break;
            default:
                //RootView 希望直接指定尺寸值,則滿足它灶壶,此時它尺寸是確切值
                measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
                break;
        }
        return measureSpec;
    }

以上代碼主要做了兩件事:

1肝断、結(jié)合Window尺寸,確定RootView 的測量模式和預(yù)估測量值(MeasureSpec)
2驰凛、根據(jù)第一步的結(jié)果胸懈,發(fā)起對ViewTree的測量(從RootView開始)

(2)
經(jīng)過對ViewTree的測量后,RootView的測量值已經(jīng)確定了恰响。
來看看relayoutWindow(xx):

#ViewRootImpl.java
    private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
                               boolean insetsPending) throws RemoteException {
        ...
        //重新設(shè)置Window大小
        //傳入的尺寸值為RootView的尺寸值
        //返回Window尺寸值存放在 mTmpFrame里
        int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,
                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
                (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
                mTmpFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
                mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout,
                mPendingMergedConfiguration, mSurfaceControl, mTempInsets);
        //關(guān)聯(lián)Window和surface
        if (mSurfaceControl.isValid()) {
            mSurface.copyFrom(mSurfaceControl);
        } else {
            destroySurface();
        }

        //記錄Window 尺寸
        setFrame(mTmpFrame);
        return relayoutResult;
    }

我們發(fā)現(xiàn):

  • Window的尺寸是依賴于RootView的測量尺寸箫荡,并且一般來說appScale=1,也就是說Window尺寸就是RootView的尺寸渔隶。
  • 此處也即是解釋了之前的Demo現(xiàn)象。

而(1)步驟的測量ViewTree是為了確定RootView的尺寸從而在此步驟確定Window尺寸间唉。

(3)(4)(5)

這里分析經(jīng)典問題:onMeasure() 為什么會執(zhí)行多次绞灼?

這三部分即是我們熟知的View 的三大過程,此處值得注意的是:
步驟(1)
在(1)步驟里的measureHierarchy(xx)呈野,我們標(biāo)注了三次測量低矮。

1、第一次:先用預(yù)置寬度測量ViewTree被冒,得到測量結(jié)果
2军掂、發(fā)現(xiàn)第一次測量結(jié)果不滿足,因為存在需要寬度比預(yù)置寬度大的子布局昨悼,于是給子布局更大的寬度蝗锥,再進(jìn)行第二次測量
3、發(fā)現(xiàn)第二次測量結(jié)果仍然不滿足率触,于是用Window能夠拿到的最大寬度再進(jìn)行測量

可以看出measureHierarchy(xx)里至少執(zhí)行一次performMeasure()终议,最多能執(zhí)行三次。而調(diào)用performMeasure()方法,該方法最終會調(diào)用到各個View的onMeasure()穴张。
每次performMeasure() 一定能夠觸發(fā)執(zhí)行onMeasure()嗎细燎?會。
原因:
再次簡單回顧一下measure(xx)代碼:

    public final void measure(xx) {
        ...
        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
        //兩個條件滿足其中一個
        //1皂甘、需要強(qiáng)制layout
        //2玻驻、尺寸發(fā)生改變
        if (forceLayout || needsLayout) {
            ...
            onMeasure();
            ...
        }
        ...
    }

從上面可知,能執(zhí)行onMeasure()偿枕,說明上述條件滿足了击狮。
needsLayout肯定是不滿足的了,因為View尺寸沒變過益老。
那么只能是forceLayout=true了彪蓬。在第一次測量ViewTree的時候,僅僅只是走了Measure過程捺萌,并未走Layout過程档冬。而我們知道PFLAG_FORCE_LAYOUT 標(biāo)記是在Layout結(jié)束后清空的,因此此處PFLAG_FORCE_LAYOUT 標(biāo)記并沒有清空桃纯,當(dāng)然needsLayout=true滿足條件酷誓。
詳細(xì)的Measure/Layout/Draw 系列請移步:

步驟(3)
而在(3)步驟又再次測量了ViewTree,此時View/ViewGroup onMeasure() 再次執(zhí)行态坦。

結(jié)合步驟(1)盐数、步驟(3)總結(jié)一下:

在步驟(1)里至少執(zhí)行了一次測量,最多執(zhí)行三次
而在步驟(3)里也會執(zhí)行一次測量

當(dāng)requestLayout的時候伞梯,步驟(1)一定會執(zhí)行玫氢,也就是說保底執(zhí)行一次performMeasure(xx)->onMeasure()。
而步驟(3)在第一次展示View的時候會執(zhí)行或者窗口尺寸發(fā)生變化會執(zhí)行谜诫。
于是我們得出如下結(jié)論:

1漾峡、第一次展示View的時候,步驟(1) 喻旷、(3)一定會執(zhí)行生逸,因此onMeasure()至少執(zhí)行兩次
2、后續(xù)通過requestLayout()觸發(fā)時且预,不一定執(zhí)行步驟(3)槽袄,因此此時onMeasure()可能只會執(zhí)行一次

這就是onMeasure() 為什么會執(zhí)行多次的原因
什么時候步驟(1)會執(zhí)行三次測量?
一般來說锋谐,步驟(1)里通常只會走第三次測量遍尺,第一次、第二次不會走怀估,因為對于DecorView作為RootView來說狮鸭,lp.width == ViewGroup.LayoutParams.MATCH_PARENT。不滿足走第一次多搀、第二次的條件歧蕉。所以通常只會看到onMeasure()執(zhí)行兩次,而不是多次康铭。
當(dāng)然我們可以滿足它條件惯退,讓onMeasure()執(zhí)行5次。

  #展示懸浮窗
    private void testMeasure() {
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
        layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
        //一定要是WRAP_CONTENT
        layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;

        //TransView為自定義View
        final TransView transView = new TransView(this);
        windowManager.addView(transView, layoutParams);
    }

    #重寫TransView onMeasure(xx)
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //該View 需要寬度大于屏幕寬度
        int width = resolveSizeAndState(10000, widthMeasureSpec, 0);
        setMeasuredDimension(width, View.MeasureSpec.getSize(heightMeasureSpec));
    }

TransView 為自定義View从藤,TransView 作為RootView催跪。
以上代碼主要做了兩件事:

1、約束Window的寬為WRAP_CONTENT
2夷野、讓其子View(TransView)申請大于屏幕寬度的寬度

想要驗證執(zhí)行多少次懊蒸,在TransView onMeasure(xx)打印即可。

另外悯搔,除了上述原因?qū)е耾nMeasure()執(zhí)行多次外骑丸,onMeasure()有可能執(zhí)行更多次,比如FrameLayout在測量子布局的時候妒貌,在某些條件下會再次觸發(fā)child.measure()過程通危,此時算起來子布局的onMeasure()執(zhí)行次數(shù)可能就會更多了,有興趣可以看看FrameLayout->onLayout(xx)灌曙。

onMeasure()為什么要執(zhí)行兩次

我們知道了會執(zhí)行兩次的原因菊碟,為什么這么設(shè)計呢?
不考慮特殊情況在刺,View在第一次展示的時候會執(zhí)行兩次onMeasure(xx)逆害。
前面提到過只要執(zhí)行了requestLayout(),步驟(1)一定會執(zhí)行蚣驼。
步驟(1)執(zhí)行的目的是為了獲取RootView的測量值忍燥,而RootView的測量值會在relayoutWindow(xx)用來重新確定Window的寬高,而步驟(3)的執(zhí)行在relayoutWindow(xx)之后隙姿,因此步驟(1)就有執(zhí)行的必要了梅垄。

至此,我們知道了RootView與Window的尺寸是如何確定了输玷。

3队丝、Window、ViewRootImpl欲鹏、View 三者關(guān)系

上邊涉及到了Window机久、RootView,所用的方法基本都是ViewRootImpl里提供的赔嚎。那么三者到底是個什么關(guān)系呢膘盖?
RootView需要添加到Window里才能展示胧弛,但是Window并不是直接管理RootView,而是通過ViewRootImpl進(jìn)行管理侠畔。

image

ViewRootImpl可以看做是Window结缚、RootView的中間者,負(fù)責(zé)協(xié)調(diào)這兩者软棺。

那么Window與RootView又是什么關(guān)系的红竭?
你可能已經(jīng)發(fā)現(xiàn)了,addToDisplay(xx)并沒有傳入RootView喘落,那么RootView是如何添加到Window里呢茵宪?
實際上,這里的添加說法比較擬人化瘦棋。
在relayoutWindow(xx)里稀火,傳入了mSurfaceControl,返回后就與Surface mSurface 建立了聯(lián)系赌朋。也就是說底層的Surface與Java 層的Surface關(guān)聯(lián)起來了憾股。而通過Surface,能夠拿到Canvas箕慧。而每個View的繪制都需要關(guān)聯(lián)Canvas服球,以此類推,View就與Surface關(guān)聯(lián)了颠焦,那么在View上進(jìn)行的繪制都會反饋到Surface上斩熊。這也就是說View添加到了Window。

image

本文基于Android 10.0

作者:fishforest
鏈接:http://www.reibang.com/p/6e45f42da304
來源:簡書
著作權(quán)歸作者所有伐庭。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)粉渠,非商業(yè)轉(zhuǎn)載請注明出處。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末圾另,一起剝皮案震驚了整個濱河市霸株,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌集乔,老刑警劉巖去件,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異扰路,居然都是意外死亡尤溜,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門汗唱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宫莱,“玉大人,你說我怎么就攤上這事哩罪∈诎裕” “怎么了巡验?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長碘耳。 經(jīng)常有香客問我显设,道長,這世上最難降的妖魔是什么藏畅? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任敷硅,我火速辦了婚禮功咒,結(jié)果婚禮上愉阎,老公的妹妹穿的比我還像新娘。我一直安慰自己力奋,他們只是感情好榜旦,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著景殷,像睡著了一般溅呢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上猿挚,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天咐旧,我揣著相機(jī)與錄音,去河邊找鬼绩蜻。 笑死铣墨,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的办绝。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼于样!你這毒婦竟也來了挪圾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤降淮,失蹤者是張志新(化名)和其女友劉穎超埋,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體佳鳖,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡纳本,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了腋颠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片繁成。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖淑玫,靈堂內(nèi)的尸體忽然破棺而出巾腕,到底是詐尸還是另有隱情面睛,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布尊搬,位于F島的核電站叁鉴,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏佛寿。R本人自食惡果不足惜幌墓,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望冀泻。 院中可真熱鬧常侣,春花似錦、人聲如沸弹渔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肢专。三九已至舞肆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間博杖,已是汗流浹背椿胯。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留剃根,地道東北人哩盲。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像跟继,于是被迫代替她去往敵國和親种冬。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354