Android UI繪製流程及原理

1、繪製流程源碼路徑

一、Activity加載ViewRootImpl

ActivityThread.handleResumeActivity() 
--> WindowManagerImpl.addView(decorView, layoutParams) 
--> WindowManagerGlobal.addView()

二、ViewRootImpl啓動View樹的遍歷

ViewRootImpl.setView(decorView, layoutParams, parentView)
-->ViewRootImpl.requestLayout()
-->scheduleTraversals()
-->TraversalRunnable.run()
-->doTraversal()
-->performTraversals()(performMeasure、performLayout、performDraw)

2、View繪製流程

一、measure

(1)MeasureSpec是什麼?

重寫過onMeasure()方法都知道,測量須要用到MeasureSpec類獲取View的測量模式和大小,那麼這個類是怎樣存儲這兩個信息呢?html

留心觀察的話會發現,onMeasure方法的兩個參數實際是32位int類型數據,即:canvas

00 000000 00000000 00000000 00000000

而其結構爲 mode + size ,前2位爲mode,然後30位爲size。app

==> getMode()方法(measureSpec --> mode):
private static final int MODE_SHIFT = 30;
// 0x3轉換爲二進制即爲:11
// 左移30位後:11000000 00000000 00000000 00000000
private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

public static int getMode(int measureSpec) {
    // 與MODE_MASK按位與運算後,即將低30位清零,結果爲mode左移30位後的值
    return (measureSpec & MODE_MASK);
}

getSize()方法同理。ide

==> makeMeasureSpec()方法(mode + size --> measureSpec):
public static int makeMeasureSpec(
    @IntRange(from = 0, 
        to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size, 
    @MeasureSpecMode int mode) {
    if (sUseBrokenMakeMeasureSpec) {
        return size + mode;
    } else {
        return (size & ~MODE_MASK) | (mode & MODE_MASK);
    }
}

這裏解釋一下,按位或左側爲size的高2位清零後的結果,右側爲mode的低30位清零後的結果,二者按位或運算的結果正好爲高2位mode、低30位size,例:佈局

01000000 00000000 00000000 00000000 | 
00001000 00001011 11110101 10101101 =
01001000 00001011 11110101 10101101

二進制計算規則可參考:http://www.javashuo.com/article/p-mskvidun-ek.htmlspa

==> 測量模式:
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY     = 1 << MODE_SHIFT;
public static final int AT_MOST     = 2 << MODE_SHIFT;

UNSPECIFIED:父容器不對View做任何限制,系統內部使用。rest

EXACTLY:精確模式,父容器檢測出View大小,即爲SpecSize;對應LayoutParams中的match_parent和指定大小的狀況。code

AT_MOST:最大模式,父容器指定可用大小,View的大小不能超出這個值;對應wrap_content。orm

(2)ViewGroup的測量流程

回到ViewRootImpl的performMeasure方法,這裏傳入的參數爲頂層DecorView的測量規格,其測量方式爲:htm

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

    case ViewGroup.LayoutParams.MATCH_PARENT:
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}

match_parent和具體數值大小爲EXACTLY模式,wrap_content則爲AT_MOST模式。

往下走,performMeasure方法中調用了DecorView的onMeasure方法,而DecorView繼承自FrameLayout,能夠看到FL的onMeasure方法中調用了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);
}

即測量子控件的大小,測量規則詳情可看getChildMeasureSpec方法,總結以下:

childLayoutParams\parentSpecMode EXACTLY AT_MOST UNSPECIFIED
dp EXACTLY/childSize EXACTLY/childSize EXCATLY/childSize
match_parent EXACTLY/parentSize AT_MOST/parentSize UNSPECIFIED/0
wrap_content AT_MOST/parentSize AT_MOST/parentSize UNSPECIFIED/0

回到onMeasure方法,測完子控件以後,ViewGroup會通過一些計算,得出自身大小:

// 加上padding
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

// 檢查是否小於最小寬度、最小高度
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

// 檢查Drawable的最小高度和寬度
final Drawable drawable = getForeground();
if (drawable != null) {
    maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
    maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}

setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
        resolveSizeAndState(maxHeight, heightMeasureSpec,
                childState << MEASURED_HEIGHT_STATE_SHIFT));

綜上,ViewGroup的測量須要先測量子View的大小,然後結合padding等屬性計算得出自身大小。

(3)View的測量流程
View.performMeasure()
-->onMeasure(int widthMeasureSpec, int heightMeasureSpec)
-->setMeasuredDimension(int measuredWidth, int measuredHeight)
-->setMeasuredDimensionRaw(int measuredWidth, int measuredHeight)

能夠看到setMeasuredDimensionRaw()方法:

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    // 存儲測量結果
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;

    // 設置測量完成的標誌位
    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

View不須要考慮子View的大小,根據內容測量得出自身大小便可。

另外,View中的onMeasure方法中調用到getDefaultSize方法:

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

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        // 最終測量的結果都是父容器的大小
        result = specSize;
        break;
    }
    return result;
}

這裏看到精確模式和最大模式,最終測量的結果都是父容器的大小,即佈局中的wrap_content、match_parent以及數值大小效果都同樣,這也就是自定義View必定要重寫onMeasure方法的緣由。

二、layout

佈局相對測量而言要簡單許多,從ViewRootImpl的performLayout方法出發,能夠看到其中調用了DecorView的layout方法:

// 實則爲DecorView的left, top, right, bottom四個信息
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

進入layout方法,發現l、t、r、b被傳遞到了setFrame方法中,並設置給了成員變量:

mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;

因此,佈局實際爲調用View的layout方法,設置自身的l、t、r、b值。另外,layout方法中往下走,能夠看到調用了onLayout方法,進入後發現爲空方法。於是查看FrameLayout的onLayout方法:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
    final int count = getChildCount();

    // 省略

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            // 省略

            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}

能夠看到,進行一系列計算後,調用了child的layout方法,對子控件進行佈局,同時子控件又會繼續往下對本身的子控件佈局,從而實現遍歷。

綜上,佈局實際爲調用layout方法設置View位置,ViewGroup則須要另外實現onLayout方法擺放子控件。

三、draw

(1)繪製過程入口
ViewRootImpl.performDraw()
-->ViewRootImpl.draw()
-->ViewRootImpl.drawSoftware()
-->View.draw()
(2)繪製步驟

進入到View的draw方法中,能夠看到如下一段註釋:

/*
 * 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)
 */

結合draw方法的源碼,繪製過程的關鍵步驟以下:

==> 繪製背景:drawBackground(canvas)

==> 繪製本身:onDraw(canvas)

==> 繪製子view:dispatchDraw(canvas)

==> 繪製滾動條、前景等裝飾:onDrawForeground(canvas)

原文出處:https://www.cnblogs.com/joahyau/p/11294970.html

相關文章
相關標籤/搜索