首先来看一下requestLayout()方法是做什么的?
View#requestLayout():
/**
* Call this when something has changed which has invalidated the
* layout of this view. This will schedule a layout pass of the view
* tree. This should not be called while the view hierarchy is currently in a layout
* pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the
* end of the current layout pass (and then layout will run again) or after the current
* frame is drawn and the next layout occurs.
*
* Subclasses which override this method should call the superclass method to
* handle possible request-during-layout errors correctly.
*/
@CallSuper
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
// AttachInfo 当View依附到其父Window时,父Window提供给View的一组信息,一般不会为null
// mViewRequestingLayout 在布局期间调用requestLayout()时使用,用于跟踪哪个View发起了requestLayout()请求,默认值为null
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
ViewRootImpl viewRoot = getViewRootImpl();
// 判断当前这个View树是否在进行布局流程
if (viewRoot != null && viewRoot.isInLayout()) {
// 如果正在布局就调用requestLayoutoutDuringLayout(this)让这一次的布局延时进行
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
// mViewRequestingLayout 赋值为当前发起requestLayout()的View
mAttachInfo.mViewRequestingLayout = this;
}
// 设置mPrivateFlags的两个标志位,重要的两个标志位
// PFLAG_FORCE_LAYOUT,通过表面的意思可以知道这是一个布局标志位,就会执行View的mearsure()和layout()方法
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
// 重要代码,这里调用父类的requestLayout,一直往上循环...
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
// mViewRequestingLayout 清掉之前的赋值,重新置为null
mAttachInfo.mViewRequestingLayout = null;
}
}
方法的英文注释大致意思是: 当View发生改变使得这个View的布局无效的时候,调用该方法将会调整View树的布局。当View的层次结构正处于布局中时,不应调用该方法。如果View树正在进行布局,那么请求会在当前的布局流程完成时,或则在绘制流程完成且开始下一次布局之后执行。
注意: 重写此方法的子类应该调用父类的方法来正确处理请求布局期间可能的错误;
// 重要代码,这里调用父类的requestLayout,一直往上循环...
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
这个是最重要的一个判断,如果父容器不为空,并且父容器没有在LayoutRequest就调用父容器的requestLayout(),因为父容器是ViewGroup没有重写requestLayout(),但是ViewGroup的父类也是View就又会调用它父容器的requestLayout(),这样就会不断上传并且为父容器设置PFLAG_FORCE_LAYOUT和PFLAG_INVALIDATED两个标志位,最后到顶级最外层容器DecorView,这里DecorView的mParent是ViewRootImpl对象(为何DecorView的mParent是ViewRootImpl?答案可以参考上一篇关于invalidate()方法的源码分析),它也设置两个标志位,然后就调用ViewRootImpl#requestLayout()。
ViewRootImpl#requestLayout():
@Override
public void requestLayout() {
// mHandlingLayoutInLayoutrequest是一个boolean类型
// 在performLayout中被置为true,这里表示的意思就是当前并不处于Layout过程中,即当前为false
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
// 划重点:又走到这个熟悉的方法
scheduleTraversals();
}
}
ViewRootImpl#scheduleTraversals():
@UnsupportedAppUsage
void scheduleTraversals() {
// 注意这个标志位,多次调用 requestLayout,只有当这个标志位为 false 时才有效
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 通过postSyncBarrier()设置Handler消息的同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// Choreographer 通过 postCallback 提交一个任务,mTraversalRunnable是要执行的回调
// 有了同步屏障mTraversalRunnable就会被优先执行
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
Choreographer 通过 postCallback 提交一个任务,mTraversalRunnable是要执行的回调,有了同步屏障mTraversalRunnable就会被优先执行,至于为何有了同步屏障mTraversalRunnable就会被优先执行?可以查看分析Handler之同步屏障机制与Android的屏幕刷新机制在源码中的应用
ViewRootImpl#TraversalRunnable:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
// 在先前同步屏障的作用下,TraversalRunnable会优先执行
doTraversal();
}
}
ViewRootImpl#doTraversal():
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 移除通过postSyncBarrier()设置的Handler消息的同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// 走到这里,这里也是很多博客开始分析View的绘制流程时,选择的切入入口
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
performTraversals() 这个方法非常重要,方法非常多,简单讲我们需要关注以下几个方法:
ViewRootImpl#performTraversals() :
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
mIsInTraversal = true;
......省略代码
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
mForceNextWindowRelayout = false;
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
// 关注方法 1 performMeasure 测量方法
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;
......省略代码
// 由measureAgain判断是否需要再次测量
if (measureAgain) {
// 关注方法 1 performMeasure 测量方法
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
} else {
maybeHandleWindowMove(frame);
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
// 关注方法 2 performLayout 位置摆放方法
performLayout(lp, mWidth, mHeight);
......省略代码
}
......省略代码
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
// 关注方法 3 performDraw 绘制方法
performDraw();
} else {
......省略代码
}
mIsInTraversal = false;
}
关注方法 1:
ViewRootImpl#performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) :
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
// 调用View的measure方法,measure方法内部又会调用View的onMeasure方法对View进行测量
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
View#measure(int widthMeasureSpec, int heightMeasureSpec):
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
......省略代码
// requestLayout()方法里把mPrivateFlags标示位设置为 PFLAG_FORCE_LAYOUT,所以此时forceLayout值为true
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);
// 由forceLayout和needsLayout判断是否要执行onMeasure()方法
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
// View调用onMeasure方法测量自己
onMeasure(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 needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
// 设置mPrivateFlags标识位
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
......省略代码
}
该方法中,由于调用requestLayout()方法时,设置了标识位mPrivateFlags = PFLAG_FORCE_LAYOUT,最终这个View会根据mPrivateFlags来判断是否要执行View的onMeasure方法。
方法最后设置mPrivateFlags |= PFLAG_LAYOUT_REQUIRED,这个是View的onLayout方法需要用到的。
关注方法 2:
ViewRootImpl#performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight):
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
if (host == null) {
return;
}
try {
// host就是当前调用requestLayout()方法的View,也即调用了View的layout方法
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
......省略代码
if (validLayoutRequesters != null) {
......省略代码
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
mInLayout = true;
// host就是当前调用requestLayout()方法的View,也即调用了View的layout方法
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
......省略代码
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
View#layout(int l, int t, int r, int b):
public void layout(int l, int t, int r, int b) {
......省略代码
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
// View的measure方法里把mPrivateFlags标示位设置为 PFLAG_LAYOUT_REQUIRED,所以此时if的判断条件是true
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
// View调用onLayout方法调整自己的摆放位置
onLayout(changed, l, t, r, b);
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
// 设置mPrivateFlags标识位
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
......省略代码
}
......省略代码
关注方法 3:
ViewRootImpl#performDraw() :
private void performDraw() {
......省略代码
try {
// 调用View#draw(boolean fullRedrawNeeded)
boolean canUseAsync = draw(fullRedrawNeeded);
if (usingAsyncReport && !canUseAsync) {
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
usingAsyncReport = false;
}
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
View#draw(boolean fullRedrawNeeded):
private void draw(boolean fullRedrawNeeded) {
......省略代码
mAttachInfo.mDrawingTime =
mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
......省略代码
} else {
......省略代码
// 调用View#drawSoftware(Surface surface, ......)
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
......省略代码
}
View#drawSoftware(Surface surface, …):
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
final Canvas canvas;
try {
.....省略代码
// 意外找到了Canvas的赋值地点
canvas = mSurface.lockCanvas(dirty);
.....省略代码
} catch (Surface.OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
} catch (IllegalArgumentException e) {
mLayoutRequested = true; // ask wm for a new surface next time.
return false;
}
try {
......省略代码
try {
......省略代码
// 当前的View调用draw(Canvas canvas)进行View的绘制
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
......省略代码
}
} finally {
......省略代码
}
return true;
}
View#draw(Canvas canvas):
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
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;
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(canvas);
// Step 4, draw the children 绘制子View
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
......省略代码
canvas.restoreToCount(saveCount);
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);
if (debugDraw()) {
debugDrawFocus(canvas);
}
}
ViewRootImpl#performTraversals() 方法依次可能会调用了performMeasure,performLayout,performDraw。跟踪代码看下来执行requestLayout() 后,onMeasure(),onLayout(),onDraw()方法会依次执行,onMeasure()由于requestLayout()中把mPrivateFlags标示位设置为 PFLAG_FORCE_LAYOUT,使的判断条件为true,所以肯定会执行;onLayout()由于onMeasure()中把mPrivateFlags标示位设置为 PFLAG_LAYOUT_REQUIRED,使的判断条件为true,所以肯定也会执行;但是onDraw()就不一定会执行到,有兴趣的童鞋,可以跟踪查看一下TextView在setText之后是如何重新调整View布局的。
TextView#checkForRelayout():
private void checkForRelayout() {
// If we have a fixed width, we can just swap in a new text layout
// if the text height stays the same or if the view height is fixed.
if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
|| (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
&& (mHint == null || mHintLayout != null)
&& (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
......省略代码
if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
......省略代码
// We lose: the height has changed and we have a dynamic height.
// Request a new view layout using our new text layout.
requestLayout();
invalidate();
} else {
// Dynamic width, so we have no choice but to request a new
// view layout with a new text layout.
nullLayouts();
requestLayout();
invalidate();
}
}
该方法中,在调用了requestLayout()方法之后,紧跟着就调用了invalidate()方法;
我的理解是: requestLayout()方法肯定会走到onMeasure()和onLayout()这两个方法,而invalidate()方法肯定会执行onDraw()方法的,相当于加了一个双保险。
个人理解,如有大神指点讨论,还望不吝赐教!!!
至此,View#requestLayout() 方法的源码流程分析完毕;