Android 知识体系中,有一个很重要的知识就是View/ViewGroup 绘制流程,但每次都是找找资料以为自己理解了,到最后还是存在很多问题,百思不得其解,下面记录自己的疑惑和问题解答。
先抛出自己的疑问:
- View/ViewGroup总体的绘制流程是怎么样的
- ViewGroup为什么不走onDraw
- getMeasureWidth跟getWidth是不是一样的
- requestLayout、invalidate、postInvalidate有什么不一样
绘制流程
ViewRootImpl的performTraversals( )
会触发View/ViewGroup测量绘制,至于ViewRootImpl跟window、view的关系暂时先不管。
一切的绘制都是从performTraversals开始。
performTraversals
// ViewRootImpl.java
private void performTraversals() {
final View host = mView;
WindowManager.LayoutParams lp = mWindowAttributes;
int desiredWindowWidth;
int desiredWindowHeight;
...
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
// 确认绘制需求
final Resources res = mView.getContext().getResources();
// 确认window的size是否有改变
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
...
// Activity处于停止
if (!mStopped || mReportNextDraw) {
// 判断是否需要重新测量
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged
|| updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
//执行测量操作
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
...
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
if (didLayout) {
// 执行布局
performLayout(lp, mWidth, mHeight);
}
...
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
// 执行绘制
performDraw();
}
}
performTraversals内部会调用 performMeasure、performLayout、performDraw,完成完整的测量、布局、绘制流程。
但并不表示每次都会走完三个流程,layoutRequested就表示是否 measure 和 layout
ViewRootImpl负责管理View链,最顶层的是DecorView,是一个FrameLayout,measure等流程都是从上而下进行的,也是从DecorView开始。
MeasureSpec
在开始讲measure流程之前,先看一个重要的概念MeasureSpec。
MeasureSpec是一个32位的int值,前两位是SpecMode(测量模式),
后三十位表示,某种模式下的SpecSize(尺寸大小)。
SpecMode:AT_MOST、EXACTLY、UNSPECIFIED;
UNSPECIFIED : 父容器对 子View 的尺寸不作限制,通常用于系统内部
EXACTLY : SpecSize 表示 View 的最终大小,因为父容器已经检测出 View 所需要的精确大小,它对应 LayoutParams 中的 match_parent 和具体的数值这两种模式
AT_MOST : SpecSize 表示父容器的可用大小,View 的大小不能大于这个值。它对应 LayoutParams 中的 wrap_content
在测量的过程中,都会转化成MeasureSpec,而MeasureSpec是由View自身的LayoutParam和父ViewGroup的MeasureSpec一起决定的。
View的MeasureSpec = View的LayoutParam + 父容器的MeasureSpec
下图就表示如何生成子View的MeasureSpec:
DecorView是顶层的View,那他的MeasureSpec是由窗口大小 + 自己的LayoutParams决定,看一下getRootMeasureSpec。
// ViewRootImpl.java
// DecorView的 measureSpec = 窗口大小 + 自身的LayoutParams
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
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;
}
measureHierarchy
measureHierarchy的作用是预测量,返回窗口是否发生了变化
当layoutRequested为true时,该方法便会被调用
// ViewRootImpl.java
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
int childWidthMeasureSpec;
int childHeightMeasureSpec;
// 表示窗口尺寸是否发生了变化
boolean windowSizeMayChange = false;
boolean goodMeasure = false;
// 若是WRAP_CONTENT,缩小布局参数
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
final DisplayMetrics packageMetrics = res.getDisplayMetrics();
res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
int baseSize = 0;
if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
baseSize = (int)mTmpValue.getDimension(packageMetrics);
}
if (baseSize != 0 && desiredWindowWidth > baseSize) {
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
goodMeasure = true;
} else {
baseSize = (baseSize+desiredWindowWidth)/2;
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
+ host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
goodMeasure = true;
}
}
}
}
if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
return windowSizeMayChange;
}
performMeasure
// ViewRootImpl.java
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
mView其实就是DecorView,是一个FrameLayout;
先看一下,ViewGroup、View、FrameLayout三个类的继承关系:
FrameLayout继承了ViewGroup,而ViewGroup继承了View;
viewGroup没有measure()
方法,那直接看View的measure()
// View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
if (forceLayout || needsLayout) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
虽然viewGroup也没有onMeasure()
,但DecorView是一个FrameLayout,FrameLayou实现了onMeasure()
// FrameLayout.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
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(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 获取最大的子View的宽度、高度
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
...
}
}
// 增加padding
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// 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());
}
// 设定最大的子View的width、height为FrameLayout的宽高
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
count = mMatchParentChildren.size();
if (count > 1) {
// 遍历子View
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
// 已知FrameLayout的MeasureSpec和子View的LayoutParmas 计算子View的MeasureSpec
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
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);
}
// 子View的测量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
由上面FrameLayout的测量流程:
- 预测量,遍历子view,找出子view中最大的宽高
- 设定FrameLayout的测量宽高
- 遍历子View,计算子View的MeasureSpec = FrameLayout的MeasureSpec + 子View的LayoutParam
- 测量子View
measureChildWithMargins的作用是测量子View,并加上Padding、Margin的参考
// ViewGroup.java
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);
}
其实ViewGroup也有一个跟measureChildWithMargins类似的方法,measureChild()
,也是同样是测量子View,不过不考虑margin
// ViewGroup.java
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
上面是ViewGroup(FrameLayout)的onMeasure()
方法,而普通View的onMeasure()
比较简单,根据MeasureSpec设定自身的测量宽高,测量宽高并不一定等于最后的宽高,还有经过layout最后一步。
// View.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)
);
}
setMeasuredDimension() 设定测量宽高
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
...
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
小结
performMeasure是一个自上而下的测量流程,从DecorView开始,最后分发测量子View。
performLayout
performLayout是Layout布局的开始,直接调用DecorView的layout()
//ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
layout()
的四个参数是相对于父布局的坐标:
l 表示左边缘距离父布局的左边缘
t 表示上边缘距离父布局的上边缘
r 表示右边缘距离父布局的左边缘
b 表示下边缘距离父布局的上边缘
// ViewGroup.java
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
// 调用View.layout()
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
View的layout()
内部调用了setFrame()
设定自身的最终的布局参数,并调用onLayout()
;
setFrame()分别设置到mLeft、mTop、mRight、和mBottom,这些位置是相对父容器的,表示子View在父容器内部的位置
View的onLayout()
是空实现,FrameLayout、LinearLayout或者自定义View都可以自定义实现。
//View.java
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);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
}
}
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
看一下FrameLayout的onLayout,内部直接调用了layoutChildren()
,顾名思义就是遍历子View,逐个布局
// FrameLayout.java
@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();
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
...
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
小结
performLayout也是一个自上而下的流程,从DecorView开始布局,分发到子View,最后确定布局。
performDraw
// ViewRootImpl.java
private void performDraw() {
boolean canUseAsync = draw(fullRedrawNeeded);
}
// ViewRootImpl.java
// fullRedrawNeeded是否需要全部重新绘制视图
private boolean draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
final Rect dirty = mDirty;// mDrity表示脏区域
if (fullRedrawNeeded) {
// 如果需要全部绘制
mAttachInfo.mIgnoreDirtyState = true;
// 设置脏区域为全区域
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
int xOffset = -mCanvasOffsetX;
int yOffset = -mCanvasOffsetY + curScrollY;
final WindowManager.LayoutParams params = mWindowAttributes;
final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
if (surfaceInsets != null) {
xOffset -= surfaceInsets.left;
yOffset -= surfaceInsets.top;
// Offset dirty rect for surface insets.
dirty.offset(surfaceInsets.left, surfaceInsets.right);
}
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
...
dirty.setEmpty();
} else {
...
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
}
return useAsyncReport;
}
脏区域
在ViewRootImpl中mDirty表示脏区域,每次只是绘制在脏区域中的位置,子View如果需要重绘invalidate()
,会标记脏区域,传输到ViewRootImpl保存至mDirty,最后ViewRootImpl执行performTraverals()
。
ViewRootImpl的draw()
调用了drawSoftware()
,最后调用了View.draw()
// ViewRootImpl.java
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
final Canvas canvas;
...
canvas = mSurface.lockCanvas(dirty);
...
try {
// 判断画布是否实心
if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
...
mView.mPrivateFlags |= View.PFLAG_DRAWN;
canvas.translate(-xoff, -yoff);
// DecorView的draw()
mView.draw(canvas);
} finally {
surface.unlockCanvasAndPost(canvas);
}
return true;
}
ViewGroup和FrameLayout都没有实现draw()
,直接看View的draw()
,这个是View绘制的重点部分
// View.java
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;
// 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
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
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);
return;
}
...
}
View的绘制流程:
- View中 onDraw是一个空实现,由各种类型的View自定义实现
- dispatchDraw()负责控制子View绘制的,在view是空实现,但是在ViewGroup中有具体的实现;这是可以理解的,毕竟只有ViewGroup才需要分发绘制这是可以理解的,毕竟只有ViewGroup才需要分发绘制
// ViewGroup.java
protected void dispatchDraw(Canvas canvas) {
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
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) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
...
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
可见 dispatchView内部负责遍历子View,分别调用子VIew的draw()
小结
performDraw()从DecorView出发,根据脏区域执行绘制:绘制背景、绘制自身内容、绘制子View、绘制装饰。
参考
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/app/6271.html