您好,登錄后才能下訂單哦!
這篇文章主要介紹Android UI繪制流程的示例分析,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!
一、繪制流程源碼路徑
1、Activity加載ViewRootImpl
ActivityThread.handleResumeActivity() --> WindowManagerImpl.addView(decorView, layoutParams) --> WindowManagerGlobal.addView()
2、ViewRootImpl啟動(dòng)View樹(shù)的遍歷
ViewRootImpl.setView(decorView, layoutParams, parentView) -->ViewRootImpl.requestLayout() -->scheduleTraversals() -->TraversalRunnable.run() -->doTraversal() -->performTraversals()(performMeasure、performLayout、performDraw)
二、View繪制流程
1、measure
(1)MeasureSpec是什么?
重寫(xiě)過(guò)onMeasure()方法都知道,測(cè)量需要用到MeasureSpec類(lèi)獲取View的測(cè)量模式和大小,那么這個(gè)類(lèi)是怎樣存儲(chǔ)這兩個(gè)信息呢?
留心觀察的話會(huì)發(fā)現(xiàn),onMeasure方法的兩個(gè)參數(shù)實(shí)際是32位int類(lèi)型數(shù)據(jù),即:
00 000000 00000000 00000000 00000000
而其結(jié)構(gòu)為 mode + size ,前2位為mode,而后30位為size。
==> getMode()方法(measureSpec --> mode):
private static final int MODE_SHIFT = 30; // 0x3轉(zhuǎn)換為二進(jìn)制即為:11 // 左移30位后:11000000 00000000 00000000 00000000 private static final int MODE_MASK = 0x3 << MODE_SHIFT; public static int getMode(int measureSpec) { // 與MODE_MASK按位與運(yùn)算后,即將低30位清零,結(jié)果為mode左移30位后的值 return (measureSpec & MODE_MASK); }
getSize()方法同理。
==> 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); } }
這里解釋一下,按位或左側(cè)為size的高2位清零后的結(jié)果,右側(cè)為mode的低30位清零后的結(jié)果,兩者按位或運(yùn)算的結(jié)果正好為高2位mode、低30位size,例:
01000000 00000000 00000000 00000000 | 00001000 00001011 11110101 10101101 = 01001000 00001011 11110101 10101101
二進(jìn)制計(jì)算規(guī)則可參考:https://www.jb51.net/article/166892.htm
==> 測(cè)量模式:
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:父容器不對(duì)View作任何限制,系統(tǒng)內(nèi)部使用。
EXACTLY:精確模式,父容器檢測(cè)出View大小,即為SpecSize;對(duì)應(yīng)LayoutParams中的match_parent和指定大小的情況。
AT_MOST:最大模式,父容器指定可用大小,View的大小不能超出這個(gè)值;對(duì)應(yīng)wrap_content。
(2)ViewGroup的測(cè)量流程
回到ViewRootImpl的performMeasure方法,這里傳入的參數(shù)為頂層DecorView的測(cè)量規(guī)格,其測(cè)量方式為:
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和具體數(shù)值大小為EXACTLY模式,wrap_content則為AT_MOST模式。
往下走,performMeasure方法中調(diào)用了DecorView的onMeasure方法,而DecorView繼承自FrameLayout,可以看到FL的onMeasure方法中調(diào)用了measureChildWithMargins方法,并傳入自身的測(cè)量規(guī)格:
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); }
即測(cè)量子控件的大小,測(cè)量規(guī)則詳情可看getChildMeasureSpec方法,總結(jié)如下:
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方法,測(cè)完子控件之后,ViewGroup會(huì)經(jīng)過(guò)一些計(jì)算,得出自身大?。?/p>
// 加上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的測(cè)量需要先測(cè)量子View的大小,而后結(jié)合padding等屬性計(jì)算得出自身大小。
(3)View的測(cè)量流程
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) { // 存儲(chǔ)測(cè)量結(jié)果 mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; // 設(shè)置測(cè)量完成的標(biāo)志位 mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }
View不需要考慮子View的大小,根據(jù)內(nèi)容測(cè)量得出自身大小即可。
另外,View中的onMeasure方法中調(diào)用到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: // 最終測(cè)量的結(jié)果都是父容器的大小 result = specSize; break; } return result; }
這里看到精確模式和最大模式,最終測(cè)量的結(jié)果都是父容器的大小,即布局中的wrap_content、match_parent以及數(shù)值大小效果都一樣,這也就是自定義View一定要重寫(xiě)onMeasure方法的原因。
2、layout
布局相對(duì)測(cè)量而言要簡(jiǎn)單許多,從ViewRootImpl的performLayout方法出發(fā),可以看到其中調(diào)用了DecorView的layout方法:
// 實(shí)則為DecorView的left, top, right, bottom四個(gè)信息 host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
進(jìn)入layout方法,發(fā)現(xiàn)l、t、r、b被傳遞到了setFrame方法中,并設(shè)置給了成員變量:
mLeft = left; mTop = top; mRight = right; mBottom = bottom;
所以,布局實(shí)際為調(diào)用View的layout方法,設(shè)置自身的l、t、r、b值。另外,layout方法中往下走,可以看到調(diào)用了onLayout方法,進(jìn)入后發(fā)現(xiàn)為空方法。因而查看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); } } }
可以看到,進(jìn)行一系列計(jì)算后,調(diào)用了child的layout方法,對(duì)子控件進(jìn)行布局,同時(shí)子控件又會(huì)繼續(xù)往下對(duì)自己的子控件布局,從而實(shí)現(xiàn)遍歷。
綜上,布局實(shí)際為調(diào)用layout方法設(shè)置View位置,ViewGroup則需要另外實(shí)現(xiàn)onLayout方法擺放子控件。
3、draw
(1)繪制過(guò)程入口
ViewRootImpl.performDraw() -->ViewRootImpl.draw() -->ViewRootImpl.drawSoftware() -->View.draw()
(2)繪制步驟
進(jìn)入到View的draw方法中,可以看到以下一段注釋?zhuān)?/p>
/* * 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) */
結(jié)合draw方法的源碼,繪制過(guò)程的關(guān)鍵步驟如下:
==> 繪制背景:drawBackground(canvas)
==> 繪制自己:onDraw(canvas)
==> 繪制子view:dispatchDraw(canvas)
==> 繪制滾動(dòng)條、前景等裝飾:onDrawForeground(canvas)
Android是一種基于Linux內(nèi)核的自由及開(kāi)放源代碼的操作系統(tǒng),主要使用于移動(dòng)設(shè)備,如智能手機(jī)和平板電腦,由美國(guó)Google公司和開(kāi)放手機(jī)聯(lián)盟領(lǐng)導(dǎo)及開(kāi)發(fā)。
以上是“Android UI繪制流程的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。