日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android O: View的绘制流程(二):测量

發布時間:2025/3/15 Android 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android O: View的绘制流程(二):测量 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在前一篇博客Android O: View的繪制流程(一): 創建和加載中,?
我們分析了系統創建和加載View的過程,這部分內容完成了View繪制的前置工作。

本文開始分析View的測量的流程。


一、繪制流程的起點?
在分析View的測量的流程前,我們先來尋找一下界面繪制流程的起點。?
當Activity啟動時,會調用ActivityThread的handleLaunchActivity方法:

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {............//這部分代碼,會調用Activity的onCreate, 進而調用setContentView//完成上一篇博客描述的前置工作Activity a = performLaunchActivity(r, customIntent);if (a != null) {.............//重點關注該函數handleResumeActivity(r.token, false, r.isForward,!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);.............} else {.............} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

我們跟進一下handleResumeActivity函數:

final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {..........// 會回調Activity的onResume接口r = performResumeActivity(token, clearHide, reason);if (r != null) {final Activity a = r.activity;.........if (r.window == null && !a.mFinished && willBeVisible) {//之前已經創建出Activity對應的PhoneWindow和DecorView//將這些對象記錄到ActivityRecord中r.window = r.activity.getWindow();View decor = r.window.getDecorView();decor.setVisibility(View.INVISIBLE);//得到WindowManagerImplViewManager wm = a.getWindowManager();WindowManager.LayoutParams l = r.window.getAttributes();a.mDecor = decor;..........//如果Activity可見if (a.mVisibleFromClient) {if (!a.mWindowAdded) {a.mWindowAdded = true;//Activity的DecorView遞交給WindowManagerwm.addView(decor, l);} else {...........}}} else if (...) {.......}.......} else {.........} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

從上述代碼可以看出,解析完XML對應的View后,?
最終將DecorView遞交給WindowManager。

我們跟進一下WindowManagerImpl中的addView函數:

public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);//實際上定義于WindowManagerGlobal中mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);}
  • 1
  • 2
  • 3
  • 4
  • 5

繼續跟進WindowManagerGlobal中的代碼:

public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {............final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;//進一步調整wparams.............ViewRootImpl root;View panelParentView = null;synchronized (mLock) {..........//創建出View對應的ViewRootroot = new ViewRootImpl(view.getContext(), display);view.setLayoutParams(wparams);mViews.add(view);mRoots.add(root);mParams.add(wparams);try {//關聯View和ViewRootImplroot.setView(view, wparams, panelParentView);} catch (RuntimeException e) {...........}} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

至此我們知道了,WindowManager將DecorView和對應的ViewRootImpl關聯起來了。?
現在來一起看看ViewRootImpl的setView函數:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {synchronized (this) {if (mView == null) {mView = view;...........//初次布局開始requestLayout();...........}} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

容易看出ViewRootImpl與View關聯后,會調用requestLayout函數,?
該函數將開啟整個繪制流程。

眼見為實,我們來看看這個requestLayout函數:

public void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {checkThread();mLayoutRequested = true;//繼續跟進scheduleTraversals();}}void scheduleTraversals() {//mTraversalScheduled用于限制繪制的次數if (!mTraversalScheduled) {mTraversalScheduled = true;...........//將mTraversalRunnable加入執行隊列mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);...........}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

最后,我們來看看TraversalRunnable的實現:

final class TraversalRunnable implements Runnable {@Overridepublic void run() {doTraversal();}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

TraversalRunnable在執行時,會調用doTraversal函數,對應代碼如下:

void doTraversal() {if (mTraversalScheduled) {mTraversalScheduled = false;..........//開始繪制了performTraversals();..........} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

繪制的主要邏輯定義于ViewRootImpl的performTraversals中,?
該函數會遍歷整個視圖書,逐一繪制每個View。

performTraversals函數接近1000行左右且涉及較多瑣碎的細節,?
個人感覺沒有逐行解析的必要,因此我們主僅關注主要的邏輯。

實際上performTraversals的代碼流程可以大致分為三個階段,如下所示:

private void performTraversals() {.............// 測量階段int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);............performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);............// 布局階段performLayout(lp, mWidth, mHeight);............// 繪制階段performDraw(); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

總結一下上述整個代碼的調用流程,大致如下所示:?

二、Measure階段?
將performTraversals劃分為三個階段后,整體的邏輯就可以簡化為下圖:?

現在我們來看看其中Measure階段的代碼。

2.1 MeasureSpec?
在分析測量的代碼前,我們先要了解一下MeasureSpec的概念。?
MeasureSpec是定義于View.java中的內部類,表示一個32位的整形值。?
它的高2位表示測量模式SpecMode,低30位表示在相應模式下的測量尺寸SpecSize。

目前SpecMode的取值可以為以下三種:

/*** Measure specification mode: The parent has not imposed any constraint* on the child. It can be whatever size it wants.*/public static final int UNSPECIFIED = 0 << MODE_SHIFT;/*** Measure specification mode: The parent has determined an exact size* for the child. The child is going to be given those bounds regardless* of how big it wants to be.*/public static final int EXACTLY = 1 << MODE_SHIFT;/*** Measure specification mode: The child can be as large as it wants up* to the specified size.*/public static final int AT_MOST = 2 << MODE_SHIFT;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

如注釋所述:?
UNSPECIFIED?表示不指定測量模式,對應的場景是:?
父視圖沒有限制子試圖大小,子試圖可以是想要的任何尺寸。?
這種模式基本用不到。

EXACTLY?表示精確測量模式,對應的場景是:?
父視圖已經指定了子試圖的精確大小,此時測量值就是SpecSize的值。?
當視圖的layout_width或者layout_height指定為具體的數值,?
或指定為match_parent時,該模式生效。

AT_MOST?表示最大值模式,對應的場景是:?
當視圖的layout_width或layout_height指定為wrap_content時,?
子視圖的尺寸可以是不超過父視圖允許最大值的任何尺寸。

我們來看看前文代碼中的getRootMeasureSpec函數:

private static int getRootMeasureSpec(int windowSize, int rootDimension) {int measureSpec;switch (rootDimension) {case ViewGroup.LayoutParams.MATCH_PARENT:// Window can't resize. Force root view to be windowSize.measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);break;case ViewGroup.LayoutParams.WRAP_CONTENT:// Window can resize. Set max size for root view.measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);break;default:// Window wants to be an exact size. Force root view to be that size.measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);break;}return measureSpec; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

從代碼來看,根據LayoutParams的參數,getRootMeasureSpec會得到對應模式的MeasureSpec。?
其中主要用到的還是EXACTLY和AT_MOST模式。

對于DecorView而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同決定;?
對于普通的View,它的MeasureSpec由父視圖的MeasureSpec和自身的LayoutParams共同決定。

2.2 measure?
了解完MeasureSpec后,我們來看看performMeasure函數:

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {........//實際上調用的還是View的measure接口mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);........ }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

我們跟進View的measure函數:

//參數為父ViewGroup對當前View的約束信息 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {//當前View為ViewGroup且設置為視覺邊界布局模式時,才返回trueboolean optical = isLayoutModeOptical(this);//當前View與父容器的模式不同時,需要調整MeasureSpecif (optical != isLayoutModeOptical(mParent)) {............widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);}// Suppress sign extension for the low bytes// 計算key值, 用于判斷是否有緩存及作為存儲鍵值long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);//判斷是否需要強制重新布局//例如View調用requestLayout時,會在mPrivateFlags添加該標記final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;// 以下其實就是判斷是否需要重新布局// Optimize layout by avoiding an extra EXACTLY pass when the view is// already measured as the correct size. In API 23 and below, this// extra pass is required to make LinearLayout re-distribute weight.final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec|| heightMeasureSpec != mOldHeightMeasureSpec;final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);final boolean needsLayout = specChanged&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);//強制重新布局或需要重新布局if (forceLayout || needsLayout) {// first clears the measured dimension flagmPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;//嘗試解析RTL相關的屬性resolveRtlPropertiesIfNeeded();//沒有forceLayout時,嘗試從緩存獲取int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);//獲取緩存失敗,或忽略緩存時,才開始測量if (cacheIndex < 0 || sIgnoreMeasureCache) {// measure ourselves, this should set the measured dimension flag backonMeasure(widthMeasureSpec, heightMeasureSpec);mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;} else {//命中緩存,直接從緩存取結果long value = mMeasureCache.valueAt(cacheIndex);// Casting a long to int drops the high 32 bits, no mask neededsetMeasuredDimensionRaw((int) (value >> 32), (int) value);mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;}...........}mOldWidthMeasureSpec = widthMeasureSpec;mOldHeightMeasureSpec = heightMeasureSpec;//存入緩存mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

從上面的代碼來看,當需要(包括強制)重新布局且不使用(包括無緩存)緩存數據時,?
才會調用onMeasure進行View的測量工作。

上述代碼的整體流程,大致如下圖所示:?

2.3 ViewGroup的onMeasure?
onMeasure函數一般會被View的子類覆蓋,因此對于DecorView而言,?
實際調用的應該是FrameLayout的onMeasure方法。

我們來跟進一下FrameLayout的onMeasure方法:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//FrameLayout是ViewGroup的子類, 此處獲取子View的數量int count = getChildCount();//長或寬的SpecMode不為EXACTLY時, measureMatchParentChildren置為true//意味著ViewGroup的長或寬為wrap_contentfinal boolean measureMatchParentChildren =MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;mMatchParentChildren.clear();int maxHeight = 0;int maxWidth = 0;int childState = 0;//依次measure子Viewfor (int i = 0; i < count; i++) {final View child = getChildAt(i);//判斷能否measure該子Viewif (mMeasureAllChildren || child.getVisibility() != GONE) {//具體的測量函數measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);//不斷迭代出子View需要的最大寬度和最大高度final LayoutParams lp = (LayoutParams) child.getLayoutParams();maxWidth = Math.max(maxWidth,child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);maxHeight = Math.max(maxHeight,child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);//迭代childStatechildState = combineMeasuredStates(childState, child.getMeasuredState());if (measureMatchParentChildren) {if (lp.width == LayoutParams.MATCH_PARENT ||lp.height == LayoutParams.MATCH_PARENT) {//統計matchParentChildrenmMatchParentChildren.add(child);}}}}// Account for padding too// 最大寬度和高度需要疊加paddingmaxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();// Check against our minimum height and width// 需要判斷是否滿足設置的最小寬高的要求maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());// Check against our foreground's minimum height and width// 還需要滿足前景圖像的要求final Drawable drawable = getForeground();if (drawable != null) {maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());}//經過了以上一系列步驟后,我們就得到了ViewGroup的maxHeight和maxWidth的最終值//表示當前容器用這個尺寸就能夠正常顯示其所有子View//此處resolveSizeAndState根據數值、MeasureSpec和childState計算出最終的數值//然后用setMeasuredDimension保存到mMeasuredWidth與mMeasuredHeight成員變量 (定義于View.java)setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),resolveSizeAndState(maxHeight, heightMeasureSpec,childState << MEASURED_HEIGHT_STATE_SHIFT));//部分子View需要做最后的測量//當ViewGroup存在wrap_content的size(初始時,未明確定義大小)//且child View存在match_parent的size時(需要依賴父容器)//那么父容器計算完畢后,這類child view需要重新測量count = mMatchParentChildren.size();if (count > 1) {for (int i = 0; i < count; i++) {//根據父容器的參數生成新的約束條件............//重新測量child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84

從以上代碼的執行流程,我們可以看到,作為容器的ViewGroup,?
將通過measureChildWithMargins方法,對所有子View進行測量,?
然后才會計算自身的測量結果。

FrameLayout的onMeasure函數整體流程可以概括為下圖:?

2.4 measureChildWithMargins?
接下來,我們來看下ViewGroup的measureChildWithMargins方法:

protected void measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed,int parentHeightMeasureSpec, int heightUsed) {//獲取子View的LayoutParamsfinal 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);//調用子View的measurechild.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

從上述代碼可以看出,ViewGroup會利用getChildMeasureSpec函數計算出子View的約束條件,?
然后再調用子View的measure函數。

我們看看getChildMeasureSpec函數:

// 從measureChildWithMargins函數,可以看出: // spec為父View的MeasureSpec // padding為父View在相應方向的已用尺寸, 加上父View的padding和子View的margin // childDimension為子View的LayoutParams相應方向的值 public static int getChildMeasureSpec(int spec, int padding, int childDimension) {int specMode = MeasureSpec.getMode(spec);int specSize = MeasureSpec.getSize(spec);//得到父View在相應方向上的可用大小int size = Math.max(0, specSize - padding);//保存最終結果int resultSize = 0;int resultMode = 0;switch (specMode) {// Parent has imposed an exact size on uscase MeasureSpec.EXACTLY:// 表示子View的LayoutParams指定了具體大小值(xx dp)if (childDimension >= 0) {resultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size. So be it.// 子View為match_parentresultSize = size;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// Child wants to determine its own size. It can't be// bigger than us.// 子View為wrap_contentresultSize = size;resultMode = MeasureSpec.AT_MOST;}break;// Parent has imposed a maximum size on uscase MeasureSpec.AT_MOST:if (childDimension >= 0) {// Child wants a specific size... so be itresultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size, but our size is not fixed.// Constrain child to not be bigger than us.resultSize = size;resultMode = MeasureSpec.AT_MOST;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// Child wants to determine its own size. It can't be// bigger than us.resultSize = size;resultMode = MeasureSpec.AT_MOST;}break;case MeasureSpec.UNSPECIFIED://不關注................}//noinspection ResourceTypereturn MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

上面的方法展現了根據父View的MeasureSpec和子View的LayoutParams生成子View的MeasureSpec的過程,?
從代碼可以看出:?
當子View指定了具體的大小時,resultSize就是指定的size,resultMode為EXACTLY,?
父View對其沒有影響;?
當子View指定為MATCH_PARENT時,resultSize為父View可用的size,?
resultMode與父View一致;?
當子View指定為WRAP_CONTENT時,resultSize為父View可用的size,?
resultMode為AT_MOST。

從前文我們知道,獲取完子View的MeasureSpec后,?
measureChildWithMargins就會調用子View的measure方法。?
對于ViewGroup及其子類而言,將重新遞歸調用ViewGroup的onMeasure方法;?
對于View及其子類而言,將調用View的onMeasure方法。

由于measureChildWithMargins會遞歸調用ViewGroup的onMeasure方法,?
可以看出整個View的測量順序是先序遍歷的,但最終計算結果時是后序遍歷的,?
即子View測量完畢后,才能得到父View的size。

2.5 View的onMeasure?
現在我們跟進一下View的onMeasure方法:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}
  • 1
  • 2
  • 3
  • 4

從代碼可以看出,普通View只需要完成自身的測量工作即可。?
View以getDefaultSize方法的返回值來作為測量結果,通過setMeasuredDimension方法進行設置。

getDefaultSize的源碼如下:

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;// AT_MOST和EXACTLY這兩種情況都返回了SpecSize作為result// 自定義View直接繼承View類時,需要自己實現// 否則wrap_content就和match_parent效果一樣case MeasureSpec.AT_MOST:case MeasureSpec.EXACTLY:result = specSize;break;}return result;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

三、總結?
至此,我們大致了解了View的測量流程。?
個人覺得重點在于了解MeasureSpec對測量過程的影響,?
同時知道測量的順序是先序遍歷,計算最終結果是后序遍歷即可。

此外,當父容器的寬或高為wrap_content,其子View的寬或高為match_parent時,?
父容器得到最終的寬、高后,需要重新測量這部分子View。

下一篇博客,我們將繼續關注View繪制的布局流程。

版權聲明:轉載請注明:http://blog.csdn.net/gaugamela/article 與50位技術專家面對面20年技術見證,附贈技術全景圖

總結

以上是生活随笔為你收集整理的Android O: View的绘制流程(二):测量的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。