前言
View繪制流程系列文章
View的繪制流程 - onMeasure()源碼分析
View的繪制流程 - onLayout()源碼分析
View的繪制流程 - onDraw()源碼分析
結論
View的繪制流程都是從ViewRootImpl中的requestLayout()方法開始進去的,performMeasure()、performLayout()、performDraw(),而如果代碼中又寫了這樣的代碼:addView()、setVisibility()等方法,意思就是會重新執行requestLayout(),意思就是會重新執行View的繪制流程,這個時候執行View的繪制流程時不會和第一次一樣去執行所有的邏輯,比如說你自己addView(),一次性添加了10個View,那么它有可能等你添加完畢之后才去執行 View的繪制流程的:
測量是從外往里遞歸,也就是說:
ViewRootImpl會把自己的測量模式傳遞給 -> DecorView,然后DecorView會把自己的測量模式傳遞給 activity_main中的LinearLayout ->
然后LinearLayout通過for循環把自己的測量模式傳遞給 TextView,然后就會調用 TextView的onMeasure()方法,然后根據傳遞過來的LinearLayout的測量模式來指定 TextView的寬高,測量完畢后通過childHeight = child.getMeasuredHeight();獲取到 子View的寬高,即就是獲取到3個TextView的寬高后,來計算父布局,即就是LinearLayout
自己的寬高 ,然后再把自己的寬高向外傳遞給DecorView ->
然后DecorView根據 LinearLayout傳回來的寬高,然后計算自己的寬高 , 把自己寬高計算好后,然后再把自己的寬高向外傳遞給 ViewRootImpl 。
onMeasure()源碼分析總結如下:
測量是從外往里遞歸的:
從外往里:
首先從最外層的 ViewRootImpl開始,它把它的 測量模式傳遞給 DecorView,然后DecorView把自己的測量模式 傳遞給 父布局LinearLayout,然后LinearLayout再把自己的測量模式 傳遞給 子View;
從里往外:
等子View計算出自己寬高后,然后把自己寬高傳遞給父布局LinearLayout,然后LinearLayout根據 子View的寬高,來計算自己的寬高,這里計算方式就是:
如果父布局是 LinearLayout,且是垂直方向,父布局高度就是累加子布局高度;
如果父布局是RelativeLayout,那么父布局高度就是指定 子孩子中最高的;
,然后LinearLayout把自己的高度傳遞給 它的父布局,就是這樣一路都把自己的高度傳遞給父布局,最后傳遞給 DecorView、傳遞給 ViewRootImpl,
onMeasure()源碼中就是這樣測量的,如下圖所示:
下邊進行分析,最下邊的結論可以不看,因為和上邊這個一樣,下邊僅用于分析流程。
1. 說明
這節課來看下View的繪制流程,我們由下邊的套路來一步一步引出并分析View的繪制流程 —— 根據一個小示例,如何能獲取mTextViewHeight高度,來引出setContentView到底做了什么、Activity的啟動流程、最后引出View的繪制流程(即就是分析onMeasure()、onLayout()、onDraw());
2. 代碼如下
public class MainActivity extends AppCompatActivity {
private TextView text_view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 下邊這個獲取不到view的高度,因為參數3是null,即就是父布局是null,說明你還沒有把activity_main添加到父布局中,所以不能獲取到寬高
View view = View.inflate(this, R.layout.activity_main, null);
// 這個可以獲取到寬高,因為 參數3ViewGroup表示父布局,下邊代碼就表示,你已經把activity_main布局添加到父布局中了,所以可以獲取到寬高
// View view = View.inflate(this, R.layout.activity_main, ViewGroup);
text_view = (TextView) findViewById(R.id.text_view);
Log.e("TAG" , "height1 -> " + text_view.getMeasuredHeight()) ; // 0
text_view.post(new Runnable() {
@Override
public void run() {
Log.e("TAG" , "height2 -> " + text_view.getMeasuredHeight()) ; // 高度:51
}
}) ;
}
@Override
protected void onResume() {
super.onResume();
Log.e("TAG" , "height3 -> " + text_view.getMeasuredHeight()) ; // 0
}
}
View view = View.inflate(this, R.layout.activity_main, null)為什么獲取不到高度?
參數3表示父布局,而這里的參數3是null,表示沒有把activity_main添加到父布局中,所以不能獲取到寬高;
View view = View.inflate(this, R.layout.activity_main, ViewGroup)為什么可以獲取到高度?
參數3表示父布局,這里的參數3是 ViewGroup,表示父布局,這里為了形象表示就直接把父布局寫成了ViewGroup,其實只要是父布局就行。這里就表示把activity_main添加到父布局中,所以可以獲取到高度;
分析其余3個mTextViewHeight的高度:
由以上可知:
03-19 21:29:23.491 18696-18696/? E/TAG: height1 -> 0
03-19 21:29:23.492 18696-18696/? E/TAG: height3 -> 0
03-19 21:29:23.591 18696-18696/? E/TAG: height2 -> 51
height1 = 0;height3 = 0 ;height2 = 51(高度)
分析原因:
我們需要知道,我們在onCreate()方法中只是調用了setContentView(),也需要知道setContentView()到底干了什么?
在PhoneWindow中,setContentView只是new DecorView()
之所以能夠拿到控件的寬高,是因為調用了onMeasure()方法,而在我們之前寫的那些自定義View效果的時候,其實都是在 onMeasure()方法中獲取到寬高后,都會重新調用setMeasuredDimension(width , height);
setContentView 只是創建DecorView,并且把我們的布局加載進DecorView,并沒有調用onMeasure()方法;
分析PhoneWindow的源碼如下:
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
只要installDecor()方法執行完,就會形成這樣一個局面:
onCreate()中為什么獲取不到 mTextViewHeight 高度?
因為在PhoneWindow中,setContentView()只是new DecorView(),然后把我們的布局加載到了DecorView(),其余什么都沒做,也并沒有調用onMeasure()方法,所以在onCreate()方法中不能獲取到TextView的寬高;
onResume()中為什么也獲取不到 mTextViewHeight 高度?
這個其實就涉及到Activity的啟動流程的分析,通過下邊對Activity啟動流程的分析,即就是分析 ActivityThread源碼,可以知道:
Activity的啟動流程是:
先調用handleLaunchActivity(),在這個方法中調用performLaunchActivity(),在performLaunchActivity()中會調用onCreate() ->
然后調用handleResumeActivity(),在這個方法中調用performResumeActivity() ->
然后調用Activity的onResume() ->
然后調用 wm.addView(decor , 1) ,這個時候才開始把我們的DecorView 加載到 WindowManager中,View的繪制流程在這個時候才剛剛開始,才開始onMeasure()(測量)、onLayout()(擺放)、onDraw()(繪制)draw()自己、draw()孩子;
所以說View的繪制流程是在onResume()方法之后才開始,所以說在onResume()方法中也是不能獲取 mTextViewHeight高度的,必須要等調用完onResume()之后,才可以獲取寬高的。
下邊的text_view.post為什么可以獲取到寬高?
text_view.post(new Runnable() {
@Override
public void run() {
Log.e("TAG" , "height2 -> " + text_view.getMeasuredHeight()) ; // 高度:51
}
}) ;
源碼分析:
View中源碼:
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
getRunQueue().post(action);
return true;
}
View中源碼:
public void post(Runnable action) {
postDelayed(action, 0);
}
public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
View中源碼:
/**
* @param info the {@link android.view.View.AttachInfo} to associated with
* this view
*/
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
if (mOverlay != null) {
mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
}
mWindowAttachCount++;
// We will need to evaluate the drawable state at least once.
mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
if (mFloatingTreeObserver != null) {
info.mTreeObserver.merge(mFloatingTreeObserver);
mFloatingTreeObserver = null;
}
// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
performCollectViewAttributes(mAttachInfo, visibility);
onAttachedToWindow();
}
HandlerActionQueue源碼:
public void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[i];
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
mActions = null;
mCount = 0;
}
}
這里只是把Runnable保存到Queue中,什么都沒干,run()方法會在dispatchAttachedToWindow()方法會在測量完畢然后調用executeActions()方法,即就是onMeasure()方法之后調用executeActions()方法,所以只要一調用text_view.post(new Runnable()) ,就馬上可以獲取寬高。
3:Activity的啟動流程?
這是ActivityThread源碼
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
mSomeActivitiesChanged = true;
if (r.profilerInfo != null) {
mProfiler.setProfiler(r.profilerInfo);
mProfiler.startProfiling();
}
// Make sure we are running with the most recent config.
handleConfigurationChanged(null, null);
if (localLOGV) Slog.v(
TAG, "Handling launch of " + r);
// Initialize before creating the activity
WindowManagerGlobal.initialize();
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
reportSizeConfigurations(r);
Bundle oldState = r.state;
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
} else {
}
}
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
return;
}
// TODO Push resumeArgs into the activity for consideration
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
final Activity a = r.activity; WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
if (!willBeVisible) {
try {
willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
a.getActivityToken());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
if (a.mVisibleFromClient && !a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
}
}
分析ActivityThread源碼可知:
先調用handleLaunchActivity(),在這個方法中調用performLaunchActivity(),在performLaunchActivity()中會調用onCreate() ->
然后調用handleResumeActivity(),在這個方法中調用performResumeActivity() ->
然后調用Activity的onResume() ->
然后調用 wm.addView(decor , 1) ,這個時候才開始把我們的DecorView 加載到 WindowManager中,View的繪制流程在這個時候才剛剛開始,也就是說在這個時候才開始onMeasure()(測量)、onLayout()(擺放)、onDraw()(繪制)draw()自己、draw()孩子;
所以說View的繪制流程是在 onResume()之后才開始,如果我們以后想要獲取控件的寬高的話,就必須等調用完onResume()之后,再去獲取寬高就可以。
自定義View的入口就是ViewRootImpl中的requestLayout()方法,所以先來看下ViewRootImpl的關系,如下圖所示:
在WindowManagerImpl源碼中:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
ViewRootImpl root;
View panelParentView = null;
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
}
throw e;
}
}
分析以上源碼可知:
wm.addView(decor , 1) ->
調用WindowManagerImpl.addView() ->
然后調用root.setView(view, wparams, panelParentView)方法 ->
調用requestLayout() -> 調用scheduleTraversals() ->
調用doTraversal() -> performTraversals() (網上的文章都是從這個方法開始講解的)
4. 開始View的繪制流程
1>:onMeasure()源碼分析:
ViewRootImpl源碼如下:
/**
* We have one child
*/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
mAttachInfo.mDisplayState = mDisplay.getState();
mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
}
}
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
performTraversals();
}
}
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals");
host.debug();
}
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(mTag,
"And hey let's measure once more: width=" + width
+ " height=" + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
} else
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
performDraw();
} else {
}
mIsInTraversal = false;
}
LinearLayout的onMeasure()源碼:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
LinearLayout的
void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}
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);
}
由上邊我們分析到 WindowManagerImpl的performTraversals()方法,這個時候就正式開始了View的繪制流程;
第一個調用performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); 這個方法用于給控件指定寬高 ->
調用mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); ->
調用onMeasure(widthMeasureSpec, heightMeasureSpec); 這個時候測量正式開始 ->
調用父布局,即就是LinearLayout.onMeasure()方法(因為這里是以LinearLayout包裹了3個TextView為例,當然你用RelativeLayout包裹,就調用RelativeLayout.onMeasure()測量方法也是可以的) ->
調用 LinearLayout.onMeasure()中的measureVertical(widthMeasureSpec, heightMeasureSpec) (這個是activity_main文件中最外層根布局中的LinearLayout) ->
measureChildWithMargins() ->
調用child.measure(childWidthMeasureSpec, childHeightMeasureSpec); (這個是最外層根布局中的子孩子的LinearLayout,如果有多個LinearLayout的子孩子,那么就會一直調用這個方法) ->
調用TextView的onMeasure()(這個就是子LinearLayout包裹的子孩子TextView)
在上邊涉及到2個測量模式
childWidthMeasureSpec, childHeightMeasureSpec
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);
}
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} 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;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = 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;
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
由以上源碼可知:
childWidthMeasureSpec, childHeightMeasureSpec這兩個測量模式是通過getChildMeasureSpec()方法去計算的,具體計算是:
在getChildMeasureSpec()中,先獲取自己的測量模式和大?。淳褪歉覆季郑?,判斷自己的測量模式是match_parent或者是一個固定的值,然后回去判斷子孩子的測量模式和大小,具體判斷方式如下:
如果自己測量模式(即就是父布局)是 EXACTLY,并且子孩子的大小是match_parent,就給子孩子的測量模式EXACTLY;
如果自己測量模式(即就是父布局)是 EXACTLY,并且子孩子的大小是wrap_content,就給子孩子的測量模式是 AT_MOST;
如果自己測量模式(即就是父布局)是 AT_MOST,即使子孩子大小是match_parent,就給子孩子的測量模式 AT_MOST;
如果自己測量模式(即就是父布局)是 AT_MOST,并且子孩子的大小是wrap_content,就給子孩子的測量模式 AT_MOST;
在最后會把獲取到的測量模式和大小,即就是resultSize, resultMode返回回去
即就是返回到了measureChildWithMargins()方法中,如下圖所示:
返回這個測量模式和大小后,這個時候我們都會調用 setMeasuredDimesion()方法,這個時候我們的布局,才真正的指定了寬度和高度
/**
* Email: 2185134304@qq.com
* Created by JackChen 2018/3/24 9:44
* Version 1.0
* Params:
* Description: 測量模式計算方式
*/
public class TextView extends View {
public TextView(Context context) {
super(context);
}
public TextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 指定寬高
// widthMeasureSpec = childWidthMeasureSpec
// heightMeasureSpec = childHeightMeasureSpec
// 我們之前講的
// wrap_content = AT_MOST
// match_parent 、fill_parent、100dp = Exactly
// 測量模式和大小是由父布局和它的孩子決定的,比方說:
// 父布局是包裹內容,就算子布局是match_parent,這個是時候的測量模式還是 AT_MOST
// 父布局是match_parent,就算子布局是match_parent,這個時候的測量模式是 EXACTLY
setMeasuredDimension();
}
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
而setMeasuredDimension();方法其實什么都沒干,就是在setMeasuredDimensionRaw()方法中給寬高賦值,在這個時候 mMeasuredWidth和mMeasureHeight才真正的有值
然后測量所有子孩子的寬高,源碼中是通過for循環,獲取所有子孩子,然后去調用child.measure(childWidthMeasureSpec, childHeightMeasureSpec)方法測量子View的高度,即就是TextView的高度,等測量出TextView的寬高,然后去測量自己的寬高(即就是父布局):
如果自己(即就是父布局)是LinearLayout并且是垂直方向,那么自己高度就是不斷的疊加子View的高度; childHeight = child.getMeasuredHeight();
如果自己(即就是父布局)是RelativeLayout,那么父布局的高度是,指定子孩子中最高的;
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:id="@+id/text_view" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:id="@+id/text_view2" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:id="@+id/text_view3" />
</LinearLayout>
總結:
測量是從外往里遞歸,也就是說:
ViewRootImpl會把自己的測量模式傳遞給 -> DecorView,然后DecorView會把自己的測量模式傳遞給 activity_main中的LinearLayout ->
然后LinearLayout通過for循環把自己的測量模式傳遞給 TextView,然后就會調用 TextView的onMeasure()方法,然后根據傳遞過來的LinearLayout的測量模式來指定 TextView的寬高,測量完畢后通過childHeight = child.getMeasuredHeight();獲取到 子View的寬高,即就是獲取到3個TextView的寬高后,來計算父布局,即就是LinearLayout
自己的寬高 ,然后再把自己的寬高向外傳遞給DecorView ->
然后DecorView根據 LinearLayout傳回來的寬高,然后計算自己的寬高 , 把自己寬高計算好后,然后再把自己的寬高向外傳遞給 ViewRootImpl 。
onMeasure()源碼分析總結如下:
測量是從外往里遞歸的:
從外往里:
首先從最外層的 ViewRootImpl開始,它把它的 測量模式傳遞給 DecorView,然后DecorView把自己的測量模式 傳遞給 父布局LinearLayout,然后LinearLayout再把自己的測量模式 傳遞給 子View;
從里往外:
等子View計算出自己寬高后,然后把自己寬高傳遞給父布局LinearLayout,然后LinearLayout根據 子View的寬高,來計算自己的寬高,這里計算方式就是:
如果父布局是 LinearLayout,且是垂直方向,父布局高度就是累加子布局高度;
如果父布局是RelativeLayout,那么父布局高度就是指定 子孩子中最高的;
,然后LinearLayout把自己的高度傳遞給 它的父布局,就是這樣一路都把自己的高度傳遞給父布局,最后傳遞給 DecorView、傳遞給 ViewRootImpl,
onMeasure()源碼中就是這樣測量的,如下圖所示:
代碼已上傳至github:
https://github.com/shuai999/View_day08_2