背景
这几天在读三个常见布局(RelativeLayout、FrameLayout和LinearLayout)的测量布局绘制流程时,涉及到了view的绘制过程draw(),所以干脆把view的测量和布局流程也看完拉倒。
view的这些流程开始于ViewRootImpl的私有方法performTraversals(),这个方法也很长,所以我只选读了跟三大流程直接有关的部分
ViewRootImpl#performTraversals
测量
跟measure()直接相关的代码如下
if (!mStopped || mReportNextDraw) { boolean focusChangedDueToTouchMode = ensureTouchModeLocally( (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0); if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) { // mHeight/mWidth是窗口的高度宽度,host.getMeasuredHeight/Width()是根view(decorView)的高度宽度 int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); // 获取根view的尺寸大小 int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); .. // 日志 // Ask host how big it wants to be // 开始测量根view,此时view树的测量工作开始 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) { // 根据权重再次测量 .. // 日志 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); } layoutRequested = true; } }
其中先是调用了getRootMeasureSpec()方法获取根view的measureSpec,代码如下
private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { // 根据传进来的根view尺寸,进行不同的makeMeasureSpec()处理 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; }
以宽度为例,传进来的是lp.width,这个lp的初始化也在ViewRootImpl.performTraversals()里,代码如下
WindowManager.LayoutParams lp = mWindowAttributes;
mWindowAttributes属性则是在一开始就初始化了
final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
WindowManager.LayoutParams的构造方法如下
public LayoutParams() { super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); type = TYPE_APPLICATION; format = PixelFormat.OPAQUE; }
所以lp的宽高都是match_parent。关于makeMeasureSpec()方法,参见安卓开发学习之MeasureSpec一文
从getRootMeasureSpec()方法返回后,主要调用了performMeasure()方法,代码如下
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { if (mView == null) { return; } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
调用了View.measure()方法,代码如下
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { .. // optical模式,一般遇不到 // Suppress sign extension for the low bytes long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL; if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2); 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; // view尺寸是否发生变化 final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY; // 是否是精确模式 final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec); // view宽高是否发生变化 final boolean needsLayout = specChanged && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize); // 是否需要重新布局 if (forceLayout || needsLayout) { // first clears the measured dimension flag mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; resolveRtlPropertiesIfNeeded(); // 解析view展示方向,从左往右还是从右往左。此处略过 int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) { // 当前view没有缓存的话,重新测量 // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); // view真正的测量过程 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) { .. // 没有调用setMeasureDimension()方法,程序会抛出异常。方法调用与否是根据标志位判断的 } mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; } mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 | (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension // 更新缓存 }
可见,主要是调用了view.onMeasure()方法测量,这个方法是空方法,不同的子类去进行不同的实现,decorView调用的是父类FrameLayout的onMeasure(),其具体的实现参见安卓开发之FrameLayout测量流程源码阅读一文
布局
跟layout()相关的代码如下
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw); // 是否需要布局 boolean triggerGlobalLayoutListener = didLayout || mAttachInfo.mRecomputeGlobalAttributes; // 是否需要通知globalLayoutListener,一般是需要的 if (didLayout) { performLayout(lp, mWidth, mHeight); .. // 后续处理 }
triggerGlobalLayoutListener变量可以决定是否要通知globalLayoutListener,这个监听可以在布局完成后让监听者进行业务处理,主要是获取measuredWidth,不过进行完布局方面的业务处理后,一定要remove掉这个listener,否则会进入死循环
然后,调用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 { host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); mInLayout = false; .. // 处理requestLayout,正常的绘制用不到 } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } mInLayout = false; }
主要是调用了view.layout()方法,代码如下
public void layout(int l, int t, int r, int b) { if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { // 如果在View.measure()方法中是从缓存读来的尺寸信息 onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); // 布局前先测量一遍 mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; // 清空标志位 } int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; boolean changed = isLayoutModeOptical(mParent) ? // 一般来说是setFrame() 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; .. // 处理自动填写autoFill }
先是调用了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(sizeChanged); mLeft = left; mTop = top; mRight = right; mBottom = bottom; mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); // renderNode只有在硬件加速渲染时才会有关联 mPrivateFlags |= PFLAG_HAS_BOUNDS; if (sizeChanged) { sizeChange(newWidth, newHeight, oldWidth, oldHeight); // 调用onSizeChanged()方法,这个也要子类去实现,然后更新轮廓outLine } if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) { // mGhostView也跟硬件加速有关 .. // 硬件加速有关 } // Reset drawn bit to original value (invalidate turns it off) mPrivateFlags |= drawn; mBackgroundSizeChanged = true; mDefaultFocusHighlightSizeChanged = true; if (mForegroundInfo != null) { mForegroundInfo.mBoundsChanged = true; } notifySubtreeAccessibilityStateChangedIfNeeded(); // 处理回调 } return changed; }
可见setFrame()方法主要就是重新设置了四个端点
然后回到View.layout()方法,进入View.onLayout()方法
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }
空方法,子类去实现。decorView调用的是父类FrameLayout的onLayout(),阅读记录参见在安卓开发学习之FrameLayout的layout过程一文
绘制
回到ViewRootImpl.performTraversals(),其中跟draw()有关的代码如下
if (!cancelDraw && !newSurface) { // 默认情况下,判断会成立 .. // 处理transition动画 performDraw(); }
调用了performDraw()方法,代码如下
private void performDraw() { .. // 必要性判断 final boolean fullRedrawNeeded = mFullRedrawNeeded; mFullRedrawNeeded = false; mIsDrawing = true; Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw"); try { draw(fullRedrawNeeded); } finally { mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } .. // 处理硬件加速的情况 }
调用了draw()方法,代码如下
private void draw(boolean fullRedrawNeeded) { Surface surface = mSurface; .. // 合法性判断 .. // firstDraw()处理,一般不用 .. // 滚动到当前显示的窗口或焦点处,并处理滚动。此处略过 final Rect dirty = mDirty; .. // mSurfaceHolder不为null,直接返回(但只要不用SurfaceView,这个就不用管) if (fullRedrawNeeded) { mAttachInfo.mIgnoreDirtyState = true; dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); // dirty表示需要绘制的区域,这里绘制的区域要大于窗口区域 } .. // 日志,下发onDraw()监听 .. // 其他处理 mAttachInfo.mDrawingTime = mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS; if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) { if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) { .. // 硬件加速 } else { .. // 硬件加速初始化 if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; } } } if (animating) { mFullRedrawNeeded = true; scheduleTraversals(); // 此方法上来判断mTraversalScheduled是否为false,是false才执行后面的逻辑。但performTraversals()执行后,只要不执行 } // 但performTraversals()执行后,正常情况下mTraversalScheduled一直为true }
主要还是调用了drawSoftware()方法,代码如下
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { // Draw with software renderer. final Canvas canvas; .. // 设置canvas为dirty try { .. // 日志,画布绘制颜色(默认没有颜色) dirty.setEmpty(); mIsAnimating = false; mView.mPrivateFlags |= View.PFLAG_DRAWN; .. // 日志 try { .. // 画布的偏移、抖动等等 mView.draw(canvas); .. } finally { .. } } finally { .. } return true; }
主要是调用了decorView的draw(canvas)方法,其实也就是View.draw(canvas)方法,关于这个方法的阅读,请参见文章安卓开发学习之View的draw(canvas)方法
结语
View绘制的三大流程:measure、layout、draw基本就这样了,这些源码都是来自api-26版本源代码,所以有很多额外的东西(主要是硬件加速),有兴趣的可以自己下去看看。