ViewTree的繪制過程

概述

ViewTree的繪制党远,起點是ViewRootImpl的performTraversals方法

private void performTraversals() {
    ...
    windowSizeMayChange |= measureHierarchy(host, lp, res,desiredWindowWidth,     desiredWindowHeight);
    ...
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    ...
    performDraw();
    ...
}

View系統(tǒng)的繪制流程會從ViewRootImpl的performTraversals()方法中開始, 每一個視圖的繪制過程都必須經(jīng)歷三個最主要的階段onMeasure()宾添、onLayout()和onDraw()。

measure

measure函數(shù)的作用是為整個View樹計算實際的大小, 設置每個View對象的布局大小(“窗口”大小)川抡。實際對應屬性就是View中的mMeasuredHeight(高)和mMeasureWidth(寬)凯亮。方法中參數(shù)widthMeasureSpec和heightMeasureSpec, 這兩個值分別用于確定視圖的寬度和高度的規(guī)格和大小边臼。
MeasureSpec的值由specSize和specMode共同組成的, 其中specSize記錄的是大小, specMode記錄的是規(guī)格。

specMode 含義
EXACTLY 表示父視圖希望子視圖的大小應該是由specSize的值來決定的假消。子元素將被限定在給定的邊界里而忽略它本身大小
AT_MOST 表示子視圖最多只能是specSize中指定的大小, 開發(fā)人員應該盡可能小得去設置這個視圖, 并且保證不會超過specSize
UNSPECIFIED 表示開發(fā)人員可以將視圖按照自己的意愿設置成任意的大小, 沒有任何限制柠并。這種情況比較少見, 不太會用到

View的measure方法代碼:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
        final boolean isExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY &&
                MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
        final boolean matchingSize = isExactly &&
                getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) &&
                getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
        if (forceLayout || !matchingSize &&
                (widthMeasureSpec != mOldWidthMeasureSpec ||
                        heightMeasureSpec != mOldHeightMeasureSpec)) {

            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                ...
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;
        ...
    }

View的onMeasure默認實現(xiàn):

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
               getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
   }

子View不可重寫的setMeasureDimension方法

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        ...
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
        ...
    }

measure()這個方法是final的, 因此我們無法在子類中去重寫這個方法, 說明Android是不允許我們改變View的measure框架的。measure方法中會調(diào)用onMeasure方法富拗。

FrameLayout的onMeasure實現(xiàn):

  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        ...
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                ...
            }
        }
        ...
    }

ViewGroup的measureChildWithMargins方法:

    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

layout

private void performLayout(WindowManager.LayoutParams lp, 
            int desiredWindowWidth, int desiredWindowHeight) {
    ...
    final View host = mView;
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    ...
}

傳入測量出的寬度臼予、高度。

    public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

layout()方法接收四個參數(shù), 分別代表著左啃沪、上粘拾、右、下的坐標, 當然這些坐標是相對于當前視圖的父視圖而言的创千。在layout()方法中, 首先會調(diào)用setFrame()方法來判斷視圖的大小是否發(fā)生過變化, 以確定有沒有必要對當前的視圖進行重繪, 同時還會在這里把傳遞過來的四個參數(shù)分別賦值給mLeft缰雇、mTop入偷、mRight和mBottom這幾個變量。View中的onLayout()方法就是一個空方法, 因為onLayout()過程是為了確定視圖在布局中所在的位置, 而這個操作應該是由布局來完成的, 即父視圖決定子視圖的顯示位置械哟。
ViewGroup的onLayout:

    protected abstract void onLayout(boolean changed,
            int l, int t, int r, int b);

所有ViewGroup的子類都必須重寫這個方法盯串。在onLayout()過程結束后, 我們就可以調(diào)用getWidth()方法和getHeight()方法來獲取視圖的寬高了。

getWidth()方法和getMeasureWidth()方法到底有什么區(qū)別

getMeasureWidth()方法在measure()過程結束后就可以獲取到了, 而getWidth()方法要在layout()過程結束后才能獲取到戒良。另外, getMeasureWidth()方法中的值是通過setMeasuredDimension()方法來進行設置的, 而getWidth()方法中的值則是通過視圖右邊的坐標減去左邊的坐標計算出來的体捏。

draw

調(diào)用棧

ViewRootImpl首先會判斷是否要硬件加速。沒有硬件加速糯崎,走drawSoftware方法:

    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {
        ...
        // Draw with software renderer.
        final Canvas canvas;
        ...//設置canvas
        mView.draw(canvas);
        ...
    }

View中的draw方法:

    public void draw(Canvas canvas) {
        ...
        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

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

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

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            // Step 6, draw decorations (scrollbars)
            onDrawScrollBars(canvas);

            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // we're done...
            return;
        }
        ...
    }

    protected void onDraw(Canvas canvas) {
    }

    /**
     * Called by draw to draw the child views. This may be overridden
     * by derived classes to gain control just before its children are drawn
     * (but after its own view has been drawn).
     */
    protected void dispatchDraw(Canvas canvas) {

    }

View的onDraw為空方法, 因為每個視圖的內(nèi)容部分肯定都是各不相同的, 這部分的功能需交給子類去實現(xiàn)几缭。dispatchDraw這一步的作用是對當前視圖的所有子視圖進行繪制。但如果當前的視圖沒有子視圖, 那么也就不需要進行繪制了沃呢。因此你會發(fā)現(xiàn)View中的dispatchDraw()方法又是一個空方法年栓,而ViewGroup的dispatchDraw()方法中就會有具體的繪制代碼。onDrawScrollBars 是對視圖的滾動條進行繪制薄霜。

其他

窗口的UI最終是需要通過SurfaceFlinger服務來統(tǒng)一渲染的, 而SurfaceFlinger服務在渲染窗口的UI之前, 需要計算基于各個窗口的Z軸位置來計算它們的可見區(qū)域某抓。而WindowManagerService服務就是負責計算好每一個窗口的Z軸位置之后, 還需要將它們設置到SurfaceFlinger服務中去, 以便SurfaceFlinger服務可以正確地渲染每一個窗口的UI。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末惰瓜,一起剝皮案震驚了整個濱河市否副,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌崎坊,老刑警劉巖备禀,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異奈揍,居然都是意外死亡曲尸,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門男翰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來另患,“玉大人,你說我怎么就攤上這事蛾绎±セ” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵秘通,是天一觀的道長为严。 經(jīng)常有香客問我敛熬,道長肺稀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任应民,我火速辦了婚禮话原,結果婚禮上夕吻,老公的妹妹穿的比我還像新娘。我一直安慰自己繁仁,他們只是感情好涉馅,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著黄虱,像睡著了一般稚矿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捻浦,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天晤揣,我揣著相機與錄音,去河邊找鬼朱灿。 笑死昧识,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的盗扒。 我是一名探鬼主播跪楞,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼侣灶!你這毒婦竟也來了甸祭?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤褥影,失蹤者是張志新(化名)和其女友劉穎淋叶,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體伪阶,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡煞檩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了栅贴。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片斟湃。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖檐薯,靈堂內(nèi)的尸體忽然破棺而出凝赛,到底是詐尸還是另有隱情,我是刑警寧澤坛缕,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布墓猎,位于F島的核電站,受9級特大地震影響赚楚,放射性物質發(fā)生泄漏毙沾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一宠页、第九天 我趴在偏房一處隱蔽的房頂上張望左胞。 院中可真熱鬧寇仓,春花似錦、人聲如沸烤宙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽躺枕。三九已至服猪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拐云,已是汗流浹背蔓姚。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留慨丐,地道東北人坡脐。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像房揭,于是被迫代替她去往敵國和親备闲。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

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