在调用requestLayout之后,最终调用到了performTraversals
《深入理解Android卷III》将performTraversals分为了四个过程:应用程序的预测量过程、WMS布局窗口阶段、最终测量阶段、最终布局控件树阶段、绘制阶段
// ViewRootImpl#performTraversals
private void performTraversals() {
// cache mView since it is used so much below...
// mView为DecorView对象
final View host = mView;
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals");
host.debug();
}
if (host == null || !mAdded)
return;
// 是否正在遍历view
mIsInTraversal = true;
// 是否需要绘制
mWillDrawSoon = true;
// 界面尺寸是否发生变化
boolean windowSizeMayChange = false;
// 是否是新界面
boolean newSurface = false;
// 界面是否发生变化(看着好像与上面的变量冲突了)
boolean surfaceChanged = false;
// DecorView的layoutParams
WindowManager.LayoutParams lp = mWindowAttributes;
// DecorView的宽度与高度
int desiredWindowWidth;
int desiredWindowHeight;
final int viewVisibility = getHostVisibility();
final boolean viewVisibilityChanged = !mFirst
&& (mViewVisibility != viewVisibility || mNewSurfaceNeeded
// Also check for possible double visibility update, which will make current
// viewVisibility value equal to mViewVisibility and we may miss it.
|| mAppVisibilityChanged);
mAppVisibilityChanged = false;
final boolean viewUserVisibilityChanged = !mFirst &&
((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));
WindowManager.LayoutParams params = null;
if (mWindowAttributesChanged) {
mWindowAttributesChanged = false;
surfaceChanged = true;
params = lp;
}
CompatibilityInfo compatibilityInfo =
mDisplay.getDisplayAdjustments().getCompatibilityInfo();
if (compatibilityInfo.supportsScreen() == mLastInCompatMode) {
params = lp;
mFullRedrawNeeded = true;
mLayoutRequested = true;
if (mLastInCompatMode) {
params.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
mLastInCompatMode = false;
} else {
params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
mLastInCompatMode = true;
}
}
mWindowAttributesChangesFlag = 0;
// mWinFrame用来Activity窗口的宽度和高度
Rect frame = mWinFrame;
// 如果是第一次执行界面的测量绘制工作的话
if (mFirst) {
// 是否全部绘制
mFullRedrawNeeded = true;
// 是否重新布局view的位置即layout
mLayoutRequested = true;
final Configuration config = mContext.getResources().getConfiguration();
// 判断界面是否有状态栏和输入键盘
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
// 如果是的话,那么就设置当前activity的宽度和高度为减去状态栏和键盘弹的
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
// 如果没有的话,就是整个屏幕的高度
desiredWindowWidth = dipToPx(config.screenWidthDp);
desiredWindowHeight = dipToPx(config.screenHeightDp);
}
// ....代码省略
} else {
// 如果不是第一次执行测量工作的话,就会从上一次执行完并保存到mWinFrame/frame中的宽高
// 度中进行获取
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
// mWidth也是上一次执行完测量并进行保存的值,但是与frame的不同之处下面再说
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
// 如果两值不同 则说明界面发生了变化,设置界面发生变化标识,
mFullRedrawNeeded = true;
mLayoutRequested = true;
windowSizeMayChange = true;
}
}
// .... 代码省略
// Execute enqueued actions on every traversal in case a detached view enqueued an action
// 这里执行RunQueue中的回调。当我们在子线程中是,可以通过View.post()切换到主线程
// 为什么可以切换到主线程呢,就是因为RunQueue,通过post将Runnable添加到RunQueue队列中。
// 然后RunQueue将Runnable发送到主线程的Handler。
getRunQueue().executeActions(mAttachInfo.mHandler);
boolean insetsChanged = false;
// 是否需要重新重新layout布局,mLayoutRequested在第一次进行绘制为true,非第一次
// 但是界面尺寸发生变化时也会为true,所以简单推测就是当前界面需不需要重新绘制。
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
// 这里就是预测量阶段,遍历整个view树,得到顶层DecorView的尺寸,主要是通过measureHierarchy()
final Resources res = mView.getContext().getResources();
// 如果界面需要重新绘制 并且是第一次绘制的话
if (mFirst) {
// make sure touch mode code executes by setting cached value
// to opposite of the added touch mode.
// 设置触摸模式
mAttachInfo.mInTouchMode = !mAddedTouchMode;
ensureTouchModeLocally(mAddedTouchMode);
} else {
if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
insetsChanged = true;
}
if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) {
insetsChanged = true;
}
if (!mPendingStableInsets.equals(mAttachInfo.mStableInsets)) {
insetsChanged = true;
}
if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
+ mAttachInfo.mVisibleInsets);
}
if (!mPendingOutsets.equals(mAttachInfo.mOutsets)) {
insetsChanged = true;
}
if (mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar) {
insetsChanged = true;
}
// 在非第一次绘制窗口 并且窗口的宽高是wrap_content时,则需要重新为窗口设置尺寸
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
windowSizeMayChange = true;
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
Configuration config = res.getConfiguration();
desiredWindowWidth = dipToPx(config.screenWidthDp);
desiredWindowHeight = dipToPx(config.screenHeightDp);
}
}
}
// 因为上面已经做过判断了 如果是第一次执行刷新,那么一定会去计算屏幕的尺寸,所以只有在
// 非第一次的时候会做测量,如果屏幕的layoutparams是MATCH_PARENT类型的,说明尺寸是
// 确定的,(其实WARP_CONTENT和MATCH_PARENT对于顶层view是一样的),而如果是WRAP_CONTENT类型的话,
// 网上说的是悬浮窗类型的,因为如果一个activity的顶层view是WRAP_CONTENT,也可以理解为
// 非全屏的,那么此Activity的会覆盖到上一个Activity上,并且上个Activity的界面会显示一部分出来
// 所以下面的测量也是简单的将顶层view的尺寸设置为全屏,具体多大,会在下面再根据子view测量
// Ask host how big it wants to be
// 无论第一次还是非第一次,但凡窗口发生了变化都会通过measureHierarchy对view树进行预测量,
// 这里的目的主要是测量DecorView的尺寸
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
// ...代码省略
if (layoutRequested) {
// Clear this now, so that if anything requests a layout in the
// rest of this function we will catch it and re-run a full
// layout pass.
mLayoutRequested = false;
}
// 这里是判断窗口是否需要重新测量,
boolean windowShouldResize = layoutRequested && windowSizeMayChange
&& ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
|| (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.width() < desiredWindowWidth && frame.width() != mWidth)
|| (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.height() < desiredWindowHeight && frame.height() != mHeight));
windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM;
// If the activity was just relaunched, it might have unfrozen the task bounds (while
// relaunching), so we need to force a call into window manager to pick up the latest
// bounds.
windowShouldResize |= mActivityRelaunched;
// Determine whether to compute insets.
// If there are no inset listeners remaining then we may still need to compute
// insets in case the old insets were non-empty and must be reset.
final boolean computesInternalInsets =
mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners()
|| mAttachInfo.mHasNonEmptyGivenInternalInsets;
boolean insetsPending = false;
int relayoutResult = 0;
boolean updatedConfiguration = false;
final int surfaceGenerationId = mSurface.getGenerationId();
final boolean isViewVisible = viewVisibility == View.VISIBLE;
final boolean windowRelayoutWasForced = mForceNextWindowRelayout;
// 标识一
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
mForceNextWindowRelayout = false;
// ...代码省略
try {
// .....
// 这里就是布局窗口阶段,通过WMS来计算窗口的大小,并最终保存到入参里面
// relayoutWindow是用来请求WindowManagerService服务计算Activity窗口大小的
// 计算完毕之后,Activity窗口的大小就会保存在ViewRootImpl类的成员变量mWinFrame中
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
// ....
final boolean overscanInsetsChanged = !mPendingOverscanInsets.equals(
mAttachInfo.mOverscanInsets);
contentInsetsChanged = !mPendingContentInsets.equals(
mAttachInfo.mContentInsets);
final boolean visibleInsetsChanged = !mPendingVisibleInsets.equals(
mAttachInfo.mVisibleInsets);
final boolean stableInsetsChanged = !mPendingStableInsets.equals(
mAttachInfo.mStableInsets);
final boolean outsetsChanged = !mPendingOutsets.equals(mAttachInfo.mOutsets);
final boolean surfaceSizeChanged = (relayoutResult
& WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED) != 0;
final boolean alwaysConsumeNavBarChanged =
mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar;
// .... 代码省略
// 如果Activity没有处于暂停状态
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
// WMS测量出来的宽高尺寸与上次测量的不一致,则说明界面发生了变化,需要重新绘制
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed! mWidth="
+ mWidth + " measuredWidth=" + host.getMeasuredWidth()
+ " mHeight=" + mHeight
+ " measuredHeight=" + host.getMeasuredHeight()
+ " coveredInsetsChanged=" + contentInsetsChanged);
// Ask host how big it wants to be
// 这里就是最终测量阶段
// 真正执行测量的地方
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
// 这里是进行重新测量的的地方
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(mTag,
"And hey let's measure once more: width=" + width
+ " height=" + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
} else {
// Not the first pass and no window/insets/visibility change but the window
// may have moved and we need check that and if so to update the left and right
// in the attach info. We translate only the window frame since on window move
// the window manager tells us only for the new frame but the insets are the
// same and we do not want to translate them more than once.
// 这里是对应 标识一的if,如果不需要重新绘制,这里会判断窗口是否发生了 移动,
// 如果移动了则会执行移动动画
maybeHandleWindowMove(frame);
}
// 这里判断是否需要重新布局view树
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
// 布局view树阶段
// 执行onLayout
performLayout(lp, mWidth, mHeight);
// By this point all views have been sized and positioned
// We can compute the transparent area
if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
// start out transparent
// TODO: AVOID THAT CALL BY CACHING THE RESULT?
host.getLocationInWindow(mTmpLocation);
mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
mTmpLocation[0] + host.mRight - host.mLeft,
mTmpLocation[1] + host.mBottom - host.mTop);
host.gatherTransparentRegion(mTransparentRegion);
if (mTranslator != null) {
mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
}
if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
mPreviousTransparentRegion.set(mTransparentRegion);
mFullRedrawNeeded = true;
// reconfigure window manager
try {
mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
} catch (RemoteException e) {
}
}
}
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals -- after setFrame");
host.debug();
}
}
// 这里判断是否取消绘制
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
// 绘制阶段
// 如果不取消 回调onDraw
performDraw();
} else {
if (isViewVisible) {
// Try again,这里是重新绘制
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
mIsInTraversal = false;
}
注意的地方就只有一点就是mWidth、mHeight和mWinFrame中的宽高值的不同,前者是应用程序主动向WMS请求绘制,然后绘制的结果保存到mWinFrame,并赋值给mWidth/mHeight,此时mWinFrame中的宽高值是与mWidth/mHeight一样的;而如果由于某些原因,WMS主动绘制计算,然后应用程序被动接收绘制结果并保存到mWinFrame,此时mWinFrame中的宽高度和mWidth/mHeight是不同的
performTraversals
是绘制流程的入口,从测量到布局最后再到绘制
// FrameLayout#onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
// 首先明确一点widthMeasureSpec/heightMeasureSpec是父view传递过来的
// 根据父view的宽高规格和子view的宽高规格共同确定的(下面会细说,影响的主要是规格)
// 而下面则是判断父view的宽高是否是不确定的,也就是宽高是否是wrap_content的
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
// 本方法的作用就是用来测量当前view的宽高的,并用maxHeight和maxWidth储存宽高,
// 而此方法是FrameLayout的方法,根据FrameLayout的特征,如果FrameLayout是wrap_content
// 类型的,那么FrameLayout的宽高则根据子view中最大的view的宽高来设定自己的宽高(可能还有padding和margin)
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
// 这里遍历子view
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 这里测量子view的宽高(measureChildWithMargins下面再细说)
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 根据测量的当前view 的宽高与上一次遍历测量的子view的宽高来取较大值
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
// 如果当前view(FrameLayot)的宽高是AS_MOST类型的,并且当前子view是的宽高是
// MATCH_PARENT 类型的,则将当前子view先保存到mMatchParentChildren中。
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// Account for padding too
// 为当前view已经测量出来的宽高再添加背景的padding
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
// 已经测量出来的宽高与设置的最小宽高对比,并取最大值。如果当前为当前view设置了图片背景,则
// 与背景的宽高对比,并取最大值。
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// 当前view的背景的宽高对比(如果当前背景不为空的话),并取最大值。
// 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());
}
// 将测量完的宽高通过setMeasuredDimension保存起来(resolveSizeAndState下面在细说)
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
// 上面说,如果当前view的宽高类型是AS_MOST类型的,并且ziview是MATCH_PARENT类型的,会
// 将此子view保存到mMatchParentChildren
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
// 获取当前view的内容区域宽度(如果ziview是MATCH_PARENT类型的,那么其宽高就是父
// view的内容宽高---对于FrameLayout来说)
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
// 为子view重新设置宽度MeasureSpec的规格和尺寸
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
// getChildMeasureSpec,根据当前view以及当前子view的规格,返回当前子view合适的规格和尺寸。下面会在细说
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
// 高度同宽度一样,这里不在多说
final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
// 这里重新测量ziview的宽高
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
上面的代码我都有写注释,细节就不在说了,onMeasure
本身的作用就是测量当前view的宽高,并将值进行保存,而如果当前view是ViewGroup类型的,那么就会先去遍历子view,先去测量子view的宽高,因为有的父view的宽高会收子view的影响,比如wrap_content类型的FrameLayout,然后通过子view的宽高最终会得到一个合适的当前父view的宽高。
上面有几个方法没有细说,
measureChildWithMargins
测量子view的宽高,涉及view树的遍历;
resolveSizeAndState
根据当前view的规格返回相应的尺寸(子view的状态暂且不考虑);
getChildMeasureSpec
根据当前view以及当前子view的规格,返回当前子view合适的规格和尺寸。
// measureChildWithMargins
// child:当前子view的对象;
// parentWidthMeasureSpec/parentHeightMeasureSpec:当前(父)view的宽高的规格和尺寸;
// widthUsed/heightUsed::这里为0,好像是权重有关,这里不再细究,按0处理
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
// 获取到子view的LayoutParams
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// 根据父view的规格尺寸、父view的padding和子view的marging、子view的规格尺寸,得到一个合适的规格尺寸
// getChildMeasureSpec这里也用到了,下面一同说
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测量子view的宽高,因为measure最终会调用
// onMeasure,所以最后又回到了onMeasure方法,但是此时的onMeasure是child的,如果child也是
// 一个ViewGroup类型的,也会开始进行遍历,最终调用的最小的View的onMeasure()
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
// View#onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
// View#getDefaultSize
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
// 获取到测量出来的当前view的规格尺寸
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
// 这个case可以忽略
case MeasureSpec.UNSPECIFIED:
result = size;
break;
// 下面两个感觉好敷衍,最终返回的就是传递过来的尺寸(如果没有重写onMeasure而是使用默认值的话)
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
// View#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);
}
// View#setMeasuredDimensionRaw
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
// 将最终尺寸赋值给mMeasuredWidth / mMeasuredHeight
// 而在上面的onMeasure方法里面遍历子view的时候,通过child.getMeasuredWidth()得到的测量值,
// 其实就是这两个属性值
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
所以measureChildWithMargins
会测量完整个View树中的view的宽高尺寸;然后我们来看前面出现两次的方法getChildMeasureSpec
,再说onMeasure开头的时候,onMeasure的参数是从父view传递过来,是通过父view以及子view的规格共同处理得到的 ,但是这个方法是ViewGroup独有的
// ViewGroup#getChildMeasureSpec
// spec是父view的规格尺寸;
// padding是父view的padding值+子view的margin值
// childDimension :子view的宽高尺寸
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// 首先获取到父view的规格及尺寸
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
// 父view的尺寸 - 相对间距 = 可以给子view的最大尺寸
int size = Math.max(0, specSize - padding);
// 最终尺寸和规格值
int resultSize = 0;
int resultMode = 0;
// 首先判断父view的规格
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
// 如果父view的规格是确定的,并且子view也指定了一个尺寸,则设置子view的尺寸值为指定的
// 尺寸值,规格为精确值规格,即 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.
// 如果父view的规格是确定的,虽然没有为子view指定特定的值,但是设置了子view尺寸为
// MATCH_PARENT,则为子view指定父view最大可以给的值,并设置子view的规格为MeasureSpec.EXACTLY
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.
// 如果父view的规格是确定的,不但没有为子view指定特定的值,还设置子view尺寸为
// WRAP_CONTENT,则为子view指定父view最大可以给的值,并设置子view的规格为MeasureSpec.AT_MOST
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.
// 如果父view的规格是不确定的,但是子view尺寸为MATCH_PARENT,
// 则为子view指定父view最大可以给的值,并设置子view的规格为MeasureSpec.EXACTLY
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.
// 如果父view的规格是不确定的,并且子view尺寸为WRAP_CONTENT,
// 则为子view指定父view最大可以给的值,并设置子view的规格为MeasureSpec.AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
// 这个不考虑
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
所以上面的方法是根据父view以及子view的规格,最终得出了子view的规格以及尺寸,小结一下就是:
1、如果为子view指定了一个精确值,则不管父view的规格,直接设置子view的尺寸为指定的值,规格为EXACTLY
2、
3、如果父view的规格不确定,
2、不管父view的规格到底确不确定,子view的尺寸始终是父view可以给子view的最大尺寸值
3、如果父view的规格是确定的,即EXACTLY,那么规格则分两种情况(父view规格确定,那么只需要判
断子view的规格即可):
1)如果子view的规格确定,即MATCH_PARENT,则设置MeasureSpec.EXACTLY
2)如果子view的规格不确定,即WRAP_CONTENT,则设置MeasureSpec.AT_MOST
4、如果父view的规格是不确定,那就不用再分情况了,子view的规格都是AT_MOST ,主要是因为父view的
尺寸不确定,子view也没办法去掉具体的尺寸值,所以先按照AT_MOST来设置。
(还记不记得在onMeasure中会将父view是AT_MOST情况下的子view是MATCH_PARENT的情况,都会保
存到一个集合中,等到整个view树测量完毕并且为父view设置了宽高尺寸之后,会在重新测量这些子view,
就是这个原因)
然后我们看resolveSizeAndState
// View#resolveSizeAndState
// size:预测量的当前view的最终尺寸
// measureSpec:当前view宽高的的规格尺寸
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
final int result;
switch (specMode) {
// 如果当前view的规格是AT_MOST即没有设置精确值的话
case MeasureSpec.AT_MOST:
// 其实两个值都可以用,相对来说使用比较小的值,为了在绘制时少更快一些
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
// 如果是确定值的话,则使用specSize
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
resolveSizeAndState
看完,感觉有一种前功尽弃感,为什么,前面写了好多的代码都是为了计算确定当前view的宽高,而这里简简单单的一句判断,可能就会不再需要前面计算出的宽高了,当然这也是没有办法,都是为了防止AT_MOST这种情况的出现。
我们来看布局的方法
ViewRootImpl#performLayout
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
if (host == null) {
return;
}
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
Log.v(mTag, "Laying out " + host + " to (" +
host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
// 调用DecorView的layout方法,其中左侧相对于父view左侧的距离为0,顶部
// 相对于父view顶部的距离为0
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
// requestLayout() was called during layout.
// If no layout-request flags are set on the requesting views, there is no problem.
// If some requests are still pending, then we need to clear those flags and do
// a full request/measure/layout pass to handle this situation.
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
// Set this flag to indicate that any further requests are happening during
// the second pass, which may result in posting those requests to the next
// frame instead
mHandlingLayoutInLayoutRequest = true;
// Process fresh layout requests, then measure and layout
int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during layout: running second layout pass");
view.requestLayout();
}
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
mInLayout = true;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mHandlingLayoutInLayoutRequest = false;
// Check the valid requests again, this time without checking/clearing the
// layout flags, since requests happening during the second pass get noop'd
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null) {
final ArrayList<View> finalRequesters = validLayoutRequesters;
// Post second-pass requests to the next frame
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during second layout pass: posting in next frame");
view.requestLayout();
}
}
});
}
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
我们接下来看layout布局
ViewRootImpl#performLayout -> ViewGroup#layout -> view#layout ->
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;
}
// 将原先的view的坐标进行保存
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_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
notifyEnterOrExitForAutoFillIfNeeded(true);
}
}
这里调用到了onLayout,onLayout的作用是为当前视图的子视图分配位置,所以,在view中的onLayout是一个空实现,而只有在ViewGroup类型的父视图中才会重写。关于setOpticalFrame和setOpticalFrame我们下面会再说
我们以DecorView为例,DecorView继承于FrameLayout
// 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();
// 因为我们是要为子view分配位置,但是位置是相对于父view的,所以要先拿到父view(当前view)的
// 位置信息
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
// 遍历子view并为其分配位置
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 获取到子view的测量宽高
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
// 根据子view的居中、靠左、靠右等为子view分配合适的位置
case Gravity.CENTER_HORIZONTAL:
// 如果是水平居中,
// 子view的左边位置 = 父view的左边距 +(父view的宽带 - 子view的宽度)/ 2 + 子view的左margin - 子view的margin(这里可以看出 margin和居中是会叠加的)
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
// 上面的计算就不在一一叙述了,这里调用了子view的layout,
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
上面我们知道,在onLayout里面会遍历子view并调用子view的layout(),如果子view是viewGroup类型的, 最终也是会走到这里,然后在继续向下遍历,直到找到最底层的view,最后又回到了View#layout();
在View#layout()中我们有两个方法还没有说到,setOpticalFrame()最终调用的也是setFrame(),因此我们只看setFrame()。
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
// 判断新位置与旧位置不是完全相同,则认为是发生了变化,需要重新刷新界面
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
// Remember our drawn bit
int drawn = mPrivateFlags & PFLAG_DRAWN;
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position
invalidate(sizeChanged);
// 将传递进来的新位置信息进行存储
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
mPrivateFlags |= PFLAG_HAS_BOUNDS;
// 如果布局发生变化,会回调onSizeChanged()
if (sizeChanged) {
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
// ....
}
return changed;
}
我们来看invalidata()
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
|| (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
|| (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
|| (fullInvalidate && isOpaque() != mLastIsOpaque)) {
// Propagate the damage rectangle to the parent view.
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
// 将坐标保存到damage中,并调用父view的invalidateChild
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}
}
}
// ViewGourp#invalidataChild
@Override
public final void invalidateChild(View child, final Rect dirty) {
final AttachInfo attachInfo = mAttachInfo;
ViewParent parent = this;
if (attachInfo != null) {
// .... 代码省略
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
parent = parent.invalidateChildInParent(location, dirty);
if (view != null) {
// Account for transform on current parent
Matrix m = view.getMatrix();
if (!m.isIdentity()) {
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
m.mapRect(boundingRect);
dirty.set((int) Math.floor(boundingRect.left),
(int) Math.floor(boundingRect.top),
(int) Math.ceil(boundingRect.right),
(int) Math.ceil(boundingRect.bottom));
}
}
} while (parent != null);
}
}
上面是一个do-while循环,直到找到最顶层的view,也就是DecorView,在上一章说过,DecorView的父view是ViewRootImpl,
// ViewGroup#invalidateChildInParent
@Override
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
// either DRAWN, or DRAWING_CACHE_VALID
if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE))
!= FLAG_OPTIMIZE_INVALIDATE) {
dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
location[CHILD_TOP_INDEX] - mScrollY);
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
}
final int left = mLeft;
final int top = mTop;
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {
dirty.setEmpty();
}
}
location[CHILD_LEFT_INDEX] = left;
location[CHILD_TOP_INDEX] = top;
} else {
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
} else {
// in case the dirty rect extends outside the bounds of this container
dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
}
location[CHILD_LEFT_INDEX] = mLeft;
location[CHILD_TOP_INDEX] = mTop;
mPrivateFlags &= ~PFLAG_DRAWN;
}
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
if (mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_INVALIDATED;
}
return mParent;
}
return null;
}
// ViewRootImpl#invalidateChildInParent
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);
if (dirty == null) {
invalidate();
return null;
} else if (dirty.isEmpty() && !mIsAnimating) {
return null;
}
if (mCurScrollY != 0 || mTranslator != null) {
mTempRect.set(dirty);
dirty = mTempRect;
if (mCurScrollY != 0) {
dirty.offset(0, -mCurScrollY);
}
if (mTranslator != null) {
mTranslator.translateRectInAppWindowToScreen(dirty);
}
if (mAttachInfo.mScalingRequired) {
dirty.inset(-1, -1);
}
}
// 这里最终将布局发生变化的区域作为重新绘制的区域,等待重新绘制
invalidateRectOnScreen(dirty);
return null;
}
最后我们来看绘制
// ViewRootImpl#performDraw:
private void performDraw() {
// ... 代码省略
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
// ViewRootImpl#draw()
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
final float appScale = mAttachInfo.mApplicationScale;
final boolean scalingRequired = mAttachInfo.mScalingRequired;
int resizeAlpha = 0;
// 获取到我们的绘制区域
final Rect dirty = mDirty;
if (mSurfaceHolder != null) {
// The app owns the surface, we won't draw.
dirty.setEmpty();
if (animating && mScroller != null) {
mScroller.abortAnimation();
}
return;
}
// 这里的判断是是否需要绘制整个屏幕,
if (fullRedrawNeeded) {
mAttachInfo.mIgnoreDirtyState = true;
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
// .... 代码省略
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
// ViewRootImpl#drawSoftware
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// .... 代码省略
try {
canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
attachInfo.mSetIgnoreDirtyState = false;
// 最终调用view.draw
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
if (!attachInfo.mSetIgnoreDirtyState) {
// Only clear the flag if it was not set during the mView.draw() call
attachInfo.mIgnoreDirtyState = false;
}
}
return true;
}
@CallSuper
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* 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
// 通过onDraw绘制主体
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children 绘制子view
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars) 绘制前景,比如滚动条
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
// ...代码省略
}
主要是两个地方,一个是绘制主体,一个是绘制子view
onDraw
在view里面并没有实现,具体的逻辑是根据view的派生类的自身情况具体实现的,比如TextView,这里我们不再多说;dispatchDraw只有ViewGroup的派生类中才会实现
@Override
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
// .... 代码省略
int clipSaveCount = 0;
final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
if (clipToPadding) {
// 这里去掉padding的区域,只绘制内容区域
clipSaveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
mScrollX + mRight - mLeft - mPaddingRight,
mScrollY + mBottom - mTop - mPaddingBottom);
}
// 遍历子view
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
// 绘制子view
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
if (clipToPadding) {
canvas.restoreToCount(clipSaveCount);
}
// .... 代码省略
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
// 开启硬件加速
boolean drawingWithRenderNode = mAttachInfo != null
&& mAttachInfo.mHardwareAccelerated
&& hardwareAcceleratedCanvas;
int sx = 0;
int sy = 0;
if (!drawingWithRenderNode) {
// 配合Scroller可以对滚动进行计算
computeScroll();
sx = mScrollX;
sy = mScrollY;
}
// ...代码省略
if (!drawingWithDrawingCache) {
if (drawingWithRenderNode) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
} else {
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
// 绘制自身
draw(canvas);
}
}
} else if (cache != null) {
}
return more;
}
绘制到这里就结束,小结一下
调用requestLayout之后,然后调用到了ViewRootImpl的performTraversals方法,在这个方法中,先是通过PerformMeasure
对整个view树进行了一次预测量,方便对比窗口是否发生了变化,需要重新测量绘制等操作,然后通过WMS对窗口进行测量,并返回测量结果,拿到测量结果之后就开始进行一系列的测量、布局、绘制的工作
网友评论