前言
在第一篇View绘制源码浅析(一)布局的加载我们知道了setContentView()
完成了DecorView
的创建,并且将xml中的布局标签转换成了对应的View、属性转换成了对应的LayoutParams
然后添加到了id为content的布局上,也就是说完成了布局对象的创建并且和DecorView
关联上了。
那么第二篇将介绍View是如何显示出来的,主体流程可分为测量、布局、绘制这三步。
本文源码基于API27,接下来我们开始吧。
概述
绘制的开始是在Activity收到AMS的Resume事件,然后给DecorView
设置上ViewRootImpl
这个视图结构的顶部对象作为DecorView
的parent
,然后通过调用ViewRootImpl
的requestLayout()
触发测量、布局、绘制的流程。
对于ViewRootImpl
来说是一个包含了父布局功能的视图顶层对象(因为每个View都有parent属性指向父布局,而DecorView
已经是最外层的布局了是没有父布局的,所以指向的是ViewRootImpl
),不过需要注意它不是一个View。
Activity收到Resume事件后最终会走道ViewRootImpl
的setView()
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
requestLayout();//触发测量、布局、绘制
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);//在window上显示
...
}
而requestLayout()
最终会走到performTraversals()
这个方法贼鸡儿长,我们只看重点
private void performTraversals() {
...
measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);//会调到performMeasure(childWidthMeasureSpec, childHeightMeasureSpec)进行测量
performLayout(lp, mWidth, mHeight);//布局
performDraw();//绘制
...
}
这里也就引出了我们的重点方法
performMeasure()
测量performLayout()
布局performDraw()
绘制
接下来我们分别介绍这个过程。
测量
看测量前,我们得先了解一个概念measureSpec,它是一个32位的int值,是父布局的宽高约束和要测量的View的LayoutParams
宽高共同作用生成的,作为测量方法的形参传入指导View的测量。其中高2位用于存储测量的模式,低30位用于存储具体的数值。然后为了方便生成和取出这个32位的int值,提供了一个工具类MeasureSpec
。
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;//高两位的掩码
//下面是3种模式
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
//传入size和mode生成MeasureSpec
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
//拿到mode
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
//拿到size
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
模式一共是有三种,这里先简单介绍下
-
UNSPECIFIED
未指定的,大小根据size来决定 -
EXACTLY
大小有明确的值,比如width为32或者macth_parent都适用该模式 -
AT_MOST
对应上wrap_content这种情况,size为View可用的最大值
通过makeMeasureSpec()
可生成32位的measureSpec
,getMode()
和getSize()
可拿到mode和size。
准备工作做完了,接下来回到主线看测量方法measureHierarchy()
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;
...
if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);//获取根布局的MeasureSpec
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);//获取根布局的MeasureSpec
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//执行测量方法
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
...
return windowSizeMayChange;
}
前面不是说measureSpec是根据父布局的宽高约束和要测量的View的LayoutParams
宽高共同作用生成的。而这里稍有不同因为DecorView是最外层的布局了,没有父布局给他生成measureSpec参数所以用window去生成一个。
desiredWindowWidth和desiredWindowHeight是屏幕的宽高,lp.width和lp.height默认是match_parent
,接下来看下getRootMeasureSpec()
生成的啥。
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;
}
可以看到生成的measureSpec的size为屏幕的尺寸,mode为MeasureSpec.EXACTLY,。然后将measureSpec作为performMeasure()
的形参传入。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);//这里mView为DecorView,即调用DecorView.measure
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
这里mView为DecorView
为了简便我们直接看FrameLayout
的measure()
方法,而FrameLayout
并未重写也没法重写因为是final修饰的,所以最终走到View的measure()
。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;//拿到缓存的key
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);//缓存容器
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;//如果有PFLAG_FORCE_LAYOUT标记会强制测量
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;//和旧的MeasuerSpec相比有没有变化
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;//模式是不是EXACTLY
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);//看size有没有变化
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);//如果MeasuerSpec有变化 并且 sAlwaysRemeasureExactly 为true 或者 模式不是Exactly 或者 size有变化
if (forceLayout || needsLayout) {//强制测量或者需要测量
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);//如要强制测量就不用拿缓存了
if (cacheIndex < 0 || sIgnoreMeasureCache) {//没有缓存或者忽略缓存
onMeasure(widthMeasureSpec, heightMeasureSpec);//onMeasure执行测量逻辑
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;
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;//添加需要进行layout的标记
}
mOldWidthMeasureSpec = widthMeasureSpec;//缓存widthMeasureSpec
mOldHeightMeasureSpec = heightMeasureSpec;//缓存heightMeasureSpec
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL);
}
从代码中可以看出measure()
中进行了条件判断需要满足下列条件其中之一就会触发测量
mPrivateFlags
包含PFLAG_FORCE_LAYOUT
MeasureSpec
和旧的相比有变化并且(sAlwaysRemeasureExactly
为true一般可以忽略默认是false的,MeasureSpec
的mode不为Exactly,MeasureSpec
的size和之前测量的值相比有变化)三者满足其一
真正执行测量的是在onMeasure()
中,然后我们看到FrameLayout
的onMeasure()
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();//拿到子view数量
int maxHeight = 0;//最大高度
int maxWidth = 0;//最大宽度
int childState = 0;
for (int i = 0; i < count; i++) {//遍历子view
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {//子view不为Gone
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);//测量子View
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);//储存所有子View中最大的宽度
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);//储存所有子View中最大的高度
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();//最大宽度加上padding
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();//最大高度加上padding
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());//和minHeight属性比较取最大值
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());//和minWidth属性比较取最大值
final Drawable drawable = getForeground();//拿到前景图
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());//和前景图比较取最大高度
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());//和前景图比较取最大宽度
}
//到这里maxHeight和maxWidth所装的值是该FrameLayout显示所有内容需要的宽高
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));//设置测量的宽高
...
}
FrameLayout
是ViewGroup
所以他的测量是遍历所有子类调用measureChildWithMargins()
先完成他们的测量,然后拿到最大的高度和宽度,再加上padding并和前景、min属性做比较确保这个maxHeight和maxWidth拿到的值能显示下所有内容,最后通过resolveSizeAndState()
拿到最终的测量结果调用setMeasuredDimension()
设置给FrameLayout
。
这里我们先看他测量子类的measureChildWithMargins()
方法。
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();//拿到子View的lp
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);//根据父MeasureSpec和子view的lp属性共同生成子view的MeasureSpec
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);//根据父MeasureSpec和子view的lp属性共同生成子view的MeasureSpec
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);//触发View的measure方法
}
可以看到在getChildMeasureSpec
方法中传入了parentMeasureSpec和子view的lp共同生成的MeasureSpec也印证了我们前面说的。
measureSpec,它是一个32位的int值,是父布局的宽高约束和要测量的View的
LayoutParams
宽高共同作用生成的
接下来看他如何生成MeasureSpec
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);//拿到父布局的mode
int specSize = MeasureSpec.getSize(spec);//拿到父布局的size
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
case MeasureSpec.EXACTLY://父布局为EXACTLY
if (childDimension >= 0) {//如果子View有明确的值 直接用该值 mode为EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {//如果子view值为match_parent则 resultSize为父布局的size mode为EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {//如果子view为WRAP_CONTENT resultSize为父布局的size mode为AT_MOST告知size为最大可用值
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST://父布局为AT_MOST
if (childDimension >= 0) {//如果子View有明确的值 直接用该值 mode为EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {//如果子view值为match_parent则 resultSize为父布局的size mode为AT_MOST因为父布局大小不确定
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {//如果子view为WRAP_CONTENT resultSize为父布局的size mode为AT_MOST告知size为最大可用值
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
//父布局为UNSPECIFIED情况比较少这里就不分析了
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);//生成MeasureSpec
}
从代码中可以看到不同情况下生成的MeasureSpec会有差别,不过只要子view宽高有明确值的时候mode一定是Exactly,宽高为wrap_content的时候mode一定是AT_MOST。然后给子View生成完MeasureSpec后调用的child.measure(childWidthMeasureSpec, childHeightMeasureSpec)
这个方法我们前面看过,最终会调到View的onMeasure()
方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
并未做啥处理直接通过getDefaultSize()
获取具体的数值
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST://在mode为AT_MOST和EXACTLY的时候都是直接取的
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
可以看到在mode为AT_MOST和EXACTLY的时候默认取的是MeasureSpec的size,所以我们写自定义view的时候需要重写onMeasure()
方法在AT_MOST这种情况下进行测量,不然wrap_content在默认情况下和match_parent没区别,TextView等常用的View都重写了该方法可自行查看。
获取到具体的值后通过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;
}
将值存储到mMeasuredWidth
和mMeasuredHeight
成员变量,这里子view测量的流程就走完了。
我们在回过头去看下FrameLayout
还没说完的那部分resolveSizeAndState()
和setMeasuredDimension()
方法。
FrameLayout
是ViewGroup
所以他的测量是遍历所有子类调用measureChildWithMargins()
先完成他们的测量,然后拿到最大的高度和宽度,再加上padding并和前景、min属性做比较确保这个maxHeight和maxWidth拿到的值能显示下所有内容,最后通过resolveSizeAndState()
拿到最终的测量结果调用setMeasuredDimension()
设置给FrameLayout
。
这里我们先看resolveSizeAndState()
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) {
case MeasureSpec.AT_MOST://如果为Wrap_content则取size和specSize中最小值,即不能超过父布局的大小
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);
}
根据情况判断需要拿到的size大小,然后通过setMeasuredDimension()
设置,FrameLayout
并未重写该方法最后还是调的View的。
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;
}
这里测量流程就说完了,我们做一个简单的总结:
-
测量的最开始是在
Measure()
方法,这个方法是View中的final方法无法重写,当中进行了一些是否需要测量的判断,真正执行测量的是onMeasure()
需要各个子类重写。 -
整个View树的测量是通过递归的方式,父View去通知子View测量,当子View测量完成父View在进行自己的测量。
-
测量完成后就可以通过
getMeasuredHeight()
和getMeasuredWidth()
获取测量宽高。
布局
布局就是将测量的好的宽高设置到left、top、right、bottom属性当中,起点是从ViewRootImpl的performLayout()
开始。
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
...
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());//这里host既是DecorView
...
}
然后调到了DecorView
的layout()
,为了简便我们还是看FrameLayout
的layout()
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);//调用View的layout()
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
再看到View的layout()
public void layout(int l, int t, int r, int b) {
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);//一般都是走的setFrame()
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {//判断和之前有没有变化,或者有PFLAG_LAYOUT_REQUIRED标记
onLayout(changed, l, t, r, b);//执行布局
}
}
代码中可以看到通过setFrame()
设置位置并且判断有没有变化。如果有变化或者有PFLAG_LAYOUT_REQUIRED
标记则触发onLayout()
先看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;
mLeft = left;//存储位置值
mTop = top;
mRight = right;
mBottom = bottom;
...
}
return changed;
}
可以看到只要left、top、right、bottom有一个值发生了变化则返回值为true并存储位置值。
然后看到View的onLayout()
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
可以看到是空实现,因为作为view来说是不需要在处理子View的布局。
那么我们看到FrameLayout.onLayout()
是如何处理子View的布局的。
@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数量
final int parentLeft = getPaddingLeftWithForeground();//获取左内边距
final int parentRight = right - left - getPaddingRightWithForeground();//获取右内边距
final int parentTop = getPaddingTopWithForeground();//获取上内边距
final int parentBottom = bottom - top - getPaddingBottomWithForeground();//获取下内边距
//其实上面就是抛开padding,子view可以摆放的区域
for (int i = 0; i < count; i++) {//遍历子view
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {//不为gone
final LayoutParams lp = (LayoutParams) child.getLayoutParams();//拿到子view的lp
final int width = child.getMeasuredWidth();//拿到子view宽
final int height = child.getMeasuredHeight();//拿到子view高
int childLeft;
int childTop;
int gravity = lp.gravity;//获取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;
//根据gravity计算left的值 我们直接看最简单的default分支
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
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;//拿到内边距parentLeft+leftMargin即为view的left
}
//根据gravity计算top的值 我们直接看最简单的default分支
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;//拿到内边距parentTop+topMargin即为view的top
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);//计算出子view的right和bottom,调用view.layout通知他执行布局逻辑
}
}
}
可以看到FrameLayout
在onLayout
中遍历子View计算出他们的left、top、right、bottom,然后调用View.layout()
设置给他们位置值完成了子View的布局。
简单总结下:
- 布局的开始是在
ViewRootImpl.performLayout()
方法。 - 布局的过程就是
DecorView.layout()
中算出子View的位置然后通知子View.layout()
不断递归的过程。 View.layout()
此方法判断是否需要进行布局一般不重写它,如果需要布局它会调用onLayout()
,ViewGroup都会重写onLayout()
完成子View的布局,View不需要重写。- 布局完成后即可通过
getLeft()
、getTop()
,getRight()
,getBottom()
获取到对应的位置值。
绘制
绘制由ViewRootImpl.performDraw()
开始,最后会调用DecorView.draw()
,不过DecorView
并未重写draw()
最终还是调到View.draw()
public void draw(Canvas canvas) {
/*
* 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
if (!dirtyOpaque) onDraw(canvas);//绘制内容
// Step 4, draw the children
dispatchDraw(canvas);//让子View绘制
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.draw()
方法中进行了七步,注释已经很清楚了就不细述了。
我们重点要看的是:
onDraw()
绘制内容,一般View会重写该方法完成内容的绘制dispatchDraw()
分发绘制让子View执行绘制,在ViewGroup
中重写了
我们先看到ViewGroup.dispatchDraw()
@Override
protected void dispatchDraw(Canvas canvas) {
int clipSaveCount = 0;
final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
if (clipToPadding) {//根据布局得到的值裁剪画布
clipSaveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
mScrollX + mRight - mLeft - mPaddingRight,
mScrollY + mBottom - mTop - mPaddingBottom);
}
final ArrayList<View> preorderedList = usingRenderNodeProperties
? null : buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
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);//遍历执行子View绘制
}
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);
}
}
}
通过第二步布局得到的值裁剪画布,然后遍历调用drawChild()
使子View执行绘制
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);//最终会调到view.draw()
}
最终会调到View.draw()
,然后又会调用onDraw()
和dispatchDraw()
,像TextView这种都实现了onDraw()
完成了自己的绘制。
那么通过这种递归方式就完成了整体的绘制。
简单总结下绘制:
- 绘制的起点是在
ViewRootImpl.performDraw()
。 - 绘制的过程就是触发
View.draw()
方法然后其中会调用onDraw()
绘制自己的内容,dispatchDraw()
触发子View绘制。
至此,整个View的绘制流程我们就分析完了。若文中有叙述不清晰或是不准确的地方,希望大家能够指出,谢谢大家:)