背景
这几天在读三个常见布局(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版本源代码,所以有很多额外的东西(主要是硬件加速),有兴趣的可以自己下去看看。