本文章主要针对MeasureSpec和ViewRootImpl中的performMeasure(测量)、performLayout(布局)、performDraw(绘制)进行描述。
ViewRootImpl.java#performTraversals()
1.private void performTraversals() {
2. ...
3. //测量
4. performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
5. ...
6. //布局
7. performLayout(lp, desiredWindowWidth, desiredWindowHeight);
8. ...
9. //绘制
10. performDraw();
11. ...
12.}
在开始分析之前,我们需要了解一些概念,如:
View:是所有UI组件的基类,是Android平台中用户界面体现的基础单位。
ViewGroup:是容纳UI组件的容器,它本身也是View的子类。
ViewRootImpl:是View的绘制的辅助类,所有View的绘制都离不开ViewRootImpl。
MeasureSpec: View的内部类,主要就是View测量模式的工具类。
MeasureSpec提供打包和解包的方法:
可以将一组SpecMode和SpecSize通过makeMeasureSpec方法打包成MeasureSpec,也可以将一个MeasureSpec通过getMode和getSize进行解包获得对应的值。
MeasureSpec的作用:
在于Measure流程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后在onMeasure方法中根据这个MeasureSpec来确定View的测量宽高。
SpecMode测量模式有三种,含义如下:
1、AT_MOST:子View的最终大小是父View指定的SpecSize值,并且子View的大小不能大于这个值,即对应wrap_content这种模式。
常量值:-2147483648(0x80000000)。
2、EXACTLY:父View已经测量出子Viwe所需要的精确大小,这时候View的最终大小就是SpecSize所指定的值。对应于match_parent和精确数值这两种模式。
常数值:1073741824(0x40000000)。
3、UNSPECIFIED:父View不对子View有任何限制,子View需要多大就多大。
常数值:0(0x00000000)。
View.java#MeasureSpec()
1.public static class MeasureSpec {
2. /**进位大小为2的30次方(int的大小为32位,所以进位30位就是要使用int的最高位和第二高位也就是32和31位做标志位) */
3. private static final int MODE_SHIFT = 30;
4.
5. /** 运算遮罩,0x3为16进制,10进制为3,二进制为11。3向左进位30,就是11 00000000000(11后跟30个0) .
6. *(遮罩的作用是用1标注需要的值,0标注不要的值。因为1与任何数做与运算都得任何数,0与任何数做与运算都得0).
7. */
8. private static final int MODE_MASK = 0x3 << MODE_SHIFT;
9.
10. /**
11. * UNSPECIFIED 模式:
12. * 父View不对子View有任何限制,子View需要多大就多大
13. */
14. //0向左进位30,就是00 00000000000(00后跟30个0)
15. public static final int UNSPECIFIED = 0 << MODE_SHIFT;
16.
17. /**
18. * EXACTYLY 模式:
19. * 父View已经测量出子Viwe所需要的精确大小,这时候View的最终大小
20. * 就是SpecSize所指定的值。对应于match_parent= -1和精确数值这两种模式.
21. */
22. //1向左进位30,就是01 00000000000(01后跟30个0)
23. public static final int EXACTLY = 1 << MODE_SHIFT;
24.
25. /**
26. * AT_MOST 模式:
27. * 子View的最终大小是父View指定的SpecSize值,并且子View的大小不能大于这个值,
28. * 即对应wrap_content = -2这种模式
29. */
30. //2向左进位30,就是10 00000000000(10后跟30个0)
31. public static final int AT_MOST = 2 << MODE_SHIFT;
32.
33. /**
34. *将size和mode打包成一个32位的int型数值
35. *高2位表示SpecMode,测量模式,低30位表示SpecSize,某种测量模式下的规格大小
36. */
37. public static int makeMeasureSpec(int size, int mode) {
38. if (sUseBrokenMakeMeasureSpec) {
39. /*measureSpec = size + mode; (注意:二进制的加法,不是十进制的加法!)
40. 这里设计的目的就是使用一个32位的二进制数,32和31位代表了mode的值,后30位代表size的值
41. 例如size=100(4),mode=AT_MOST,则measureSpec=100+10000...00=10000..00100*/
42. return size + mode;
43. } else {
44. /*size &; ~MODE_MASK就是取size 的后30位,mode & MODE_MASK就是取mode的前两位,最后执行或运算,得出来的数字,前面2位包含代表mode,后面30位代表size*/
45. return (size & ~MODE_MASK) | (mode & MODE_MASK);
46. }
47. }
48.
49. /**将32位的MeasureSpec解包,返回SpecMode,测量模式*/
50. public static int getMode(int measureSpec) {
51. /*mode = measureSpec & MODE_MASK;
52. * MODE_MASK = 11 00000000000(11后跟30个0),原理是用MODE_MASK后30位的0替换掉measureSpec后30位中的1,再保留32和31位的mode值。
53. * 例如10 00..00100 & 11 00..00(11后跟30个0) = 10 00..00(AT_MOST),这样就得到了mode的值 */
54.
55. return (measureSpec & MODE_MASK);
56. }
57.
58. /**将32位的MeasureSpec解包,返回SpecSize,某种测量模式下的规格大小*/
59. public static int getSize(int measureSpec) {
60. /*将MODE_MASK取反,也就是变成了00 111111(00后跟30个1),将32,31替换成0也就是去掉mode,保留后30位的size*/
61. return (measureSpec & ~MODE_MASK);
62. }
63. //...
}
ViewRootImpl.java#performTraversals()
1.private void performTraversals() {
2. int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
3. int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
4.
5. //请求host(指的是DecorView)进行测量;
6. performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
7.}
看到这里有同学会问了,DecorView是顶层view了,没有父容器,那么它的MeasureSpec怎么来的呢?
如上code的getRootMeasureSpec就是lp.width/lp.height就是获取就是屏幕的尺寸,并把返回结果赋值childWidthMeasureSpec/childHeightMeasureSpec成员变量。
ViewRootImpl.java#getRootMeasureSpec()
1.private static int getRootMeasureSpec(int windowSize, int rootDimension) {
2. int measureSpec;
3. switch (rootDimension) {
4.
5. case ViewGroup.LayoutParams.MATCH_PARENT:
6. /*窗口无法调整大小,强制将根View设置成windowSize*/
7. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
8. break;
9. case ViewGroup.LayoutParams.WRAP_CONTENT:
10. /*窗口可以调整大小,设置最大size给根View*/
11. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
12. break;
13. default:
14. /*窗口要精确大小,强制将根View设置为 MeasureSpec.EXACTLY*/
15. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
16. break;
17. }
18. return measureSpec;
}
ViewRootImpl.java#performMeasure()
1.private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
2. Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
3. try {
4. //顶级View测量
5. mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
6. } finally {
7. Trace.traceEnd(Trace.TRACE_TAG_VIEW);
8. }
9.}
View.java#measure()
1.public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
2. // 判断View的layoutMode是否为LAYOUT_MODE_OPTICAL_BOUNDS(理解他是一个视觉边界)
3. boolean optical = isLayoutModeOptical(this);
4. // 子View是LAYOUT_MODE_OPTICAL_BOUNDS,父View不是LAYOUT_MODE_OPTICAL_BOUNDS的情况很少见,不需要去care.
5. if (optical != isLayoutModeOptical(mParent)) {
6. Insets insets = getOpticalInsets();
7. int oWidth = insets.left + insets.right;
8. int oHeight = insets.top + insets.bottom;
9. widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
10. heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
11. }
12.
13. // 生成View宽高的缓存key,并且如果缓存Map为null,则构建缓存Map.
14. long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
15. if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
16. // 若mPrivateFlags中包含PFLAG_FORCE_LAYOUT标记,则强制重新布局
17. // 比如调用View.requestLayout()会在mPrivateFlags中加入此标记
18. final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
19.
20. final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
21. || heightMeasureSpec != mOldHeightMeasureSpec;
22. final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
23. && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
24. final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
25. && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
26. final boolean needsLayout = specChanged
27. && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
28. // 判断是否为强制布局或者宽、高发生了变化
29. if (forceLayout || needsLayout) {
30. //清除PFLAG_MEASURED_DIMENSION_SET标记,表示该View还没有被测量.
31. mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
32. // 解析从右向左的布局
33. //一般对阿拉伯语、希伯来语等从右到左书写、布局的语言进行特殊处理
34. resolveRtlPropertiesIfNeeded();
35. //尝试从缓从中获取,若forceLayout为true或是缓存中不存在或是忽略缓存,则调用onMeasure()重新进行测量工作
36. int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
37. if (cacheIndex < 0 || sIgnoreMeasureCache) {
38. //View真正测量宽和高的地方
39. onMeasure(widthMeasureSpec, heightMeasureSpec);
40. mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
41. } else {
42. // // 缓存命中,获取缓存中的View宽和高,不必再测量
43. long value = mMeasureCache.valueAt(cacheIndex);
44. //long占8个字节,前4个字节为宽度,后4个字节为高度.
45. setMeasuredDimensionRaw((int) (value >> 32), (int) value);
46. mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
47. }
48.
49. // 无论是调用onMeasure还是使用缓存,都应该设置了PFLAG_MEASURED_DIMENSION_SET标志位.
50. // 没有设置,则说明测量过程出了问题,因此抛出异常.
51. // 并且,一般出现这种情况一般是子类重写onMeasure方法,但是最后没有调用setMeasuredDimension.
52. if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
53. throw new IllegalStateException("View with id " + getId() + ": "
54. + getClass().getName() + "#onMeasure() did not set the"
55. + " measured dimension by calling"
56. + " setMeasuredDimension()");
57. }
58.
59. mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
60. }
61.
62. mOldWidthMeasureSpec = widthMeasureSpec;
63. mOldHeightMeasureSpec = heightMeasureSpec;
64. // 记录View的宽和高,并将其存储到缓存Map里.
65. mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
66. (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
67. }
DecorView#onMeasure主要是进行了一些判断,这里就不展开来讲了,它最后会调用到super.onMeasure方法,即FrameLayout#onMeasure方法,下面我们主要说一下FrameLayout#onMeasure方法。
FrameLayout.java#onMeasure()
1. @Override
2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
3. //获取布局内子View数量
4. int count = getChildCount();
5.
6. final boolean measureMatchParentChildren =
7. MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
8. MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
9. mMatchParentChildren.clear();
10.
11. int maxHeight = 0;
12. int maxWidth = 0;
13. int childState = 0;
14. //遍历所有子View中可见的View,也就不为GONE的View;
15. for (int i = 0; i < count; i++) {
16. final View child = getChildAt(i);
17. if (mMeasureAllChildren || child.getVisibility() != GONE) {
18. // 测量子view
19. measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
20. // 获取子view的布局参数
21. final LayoutParams lp = (LayoutParams) child.getLayoutParams();
22. // 记录子view的最大宽度和高度
23. maxWidth = Math.max(maxWidth,
24. child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
25. maxHeight = Math.max(maxHeight,
26. child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
27. childState = combineMeasuredStates(childState, child.getMeasuredState());
28. // 记录所有跟父布局有着相同宽或高的子view
29. if (measureMatchParentChildren) {
30. if (lp.width == LayoutParams.MATCH_PARENT ||
31. lp.height == LayoutParams.MATCH_PARENT) {
32. mMatchParentChildren.add(child);
33. }
34. }
35. }
36. }
37.
38. // 子view的最大宽高计算出来后,还要加上父View自身的padding
39. maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
40. maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
41....
42. //保存测量结果
43. setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
44. resolveSizeAndState(maxHeight, heightMeasureSpec,
45. childState << MEASURED_HEIGHT_STATE_SHIFT));
46. //从子View中获取match_parent的个数
47. count = mMatchParentChildren.size();
48. if (count > 1) {
49. for (int i = 0; i < count; i++) {
50. final View child = mMatchParentChildren.get(i);
51. final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
52.
53. final int childWidthMeasureSpec;
54. // 如果子view的宽是MATCH_PARENT,那么宽度 = 父view的宽 - 父Padding - 子Margin
55. if (lp.width == LayoutParams.MATCH_PARENT) {
56. final int width = Math.max(0, getMeasuredWidth()
57. - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
58. - lp.leftMargin - lp.rightMargin);
59. childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
60. width, MeasureSpec.EXACTLY);
61. } else {
62. childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
63. getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
64. lp.leftMargin + lp.rightMargin,
65. lp.width);
66. }
67.
68....
69. //对于这部分的子View,重新执行measure
70. child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
71. }
72. }
73. }
注:这里的size,对于FrameLayout来说,是其最大子View的测量宽高,setMeasureDimension方法最终会调用setMeasureDimensionRaw来保存测量的宽高。
View#setMeasuredDimensionRaw()
1.private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
2. mMeasuredWidth = measuredWidth;
3. mMeasuredHeight = measuredHeight;
4. //增加标志位,表示该View被测量.
5. mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
6.}
ViewGroup.java#measureChild()
1.protected void measureChild(View child, int parentWidthMeasureSpec,
2. int parentHeightMeasureSpec) {
3. /**
4. * child用来描述当前要测量大小的子view,
5. *parentWidthMeasureSpec和parentHeightMeasureSpec用来描述当前子view可以获得的最大宽度和高度,
6. * widthUsed和heightUsed用来描述父窗口已经使用了的宽度和高度。
7. * */
8. final LayoutParams lp = child.getLayoutParams();
9.
10. final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
11. mPaddingLeft + mPaddingRight, lp.width);
12. final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
13. mPaddingTop + mPaddingBottom, lp.height);
14.
15. child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
16. }
这里的getChildMeasureSpec方法,就是把父容器的MeasureSpec以及自身的layoutParams属性传递进去来获取子View的MeasureSpec。
ViewGroup.java#getChildMeasureSpec()
1.public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
2. int specMode = MeasureSpec.getMode(spec);
3. int specSize = MeasureSpec.getSize(spec);
4. //size 表示子View可用空间:父容器尺寸减去padding
5. int size = Math.max(0, specSize - padding);
6.
7. int resultSize = 0;
8. int resultMode = 0;
9.
10. switch (specMode) {
11. // 父容器给子View确切的size,(具体数值或MATCH_PARENT)的情况下
12. case MeasureSpec.EXACTLY:
13. if (childDimension >= 0) {
14. resultSize = childDimension;
15. resultMode = MeasureSpec.EXACTLY;
16. } else if (childDimension == LayoutParams.MATCH_PARENT) {
17. // 子view想成为父容器的大小
18. resultSize = size;
19. resultMode = MeasureSpec.EXACTLY;
20. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
21. //子View确定自己的的size,但是不能超过父容器
22. resultSize = size;
23. resultMode = MeasureSpec.AT_MOST;
24. }
25. break;
26.
27. // 父容器对子View施加了最大的限制(即父容器大小赋值为WRAP_CONTENT)的情况下
28. case MeasureSpec.AT_MOST:
29. ...
30. break;
31.
32. // 父容器不限制子View大小,子View需要多大就多
33. case MeasureSpec.UNSPECIFIED:
34. ...
35. break;
36. }
37. //noinspection ResourceType
38. return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
39. }
注:对于不同类型的View,其onMeasure方法是不同的,但是对于不同的View,即使是自定义View,我们在重写onMeasure方法内,也一定会调用View#onMeasure方法,可参考如下示例图:
ViewRootImpl.java#performLayout()
1.private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
2. int desiredWindowHeight) {
3. mScrollMayChange = true;
4. mInLayout = true;
5. //这个host,就是DecorView
6. final View host = mView;
7. if (host == null) {
8. return;
9. }
10. if (DEBUG_ORIENTATION || sDebugLayout) {
11. Log.v(mTag, "Laying out " + host + " to (" +
12. host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
13. }
14.
15. Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
16. try {
17. //四个参数分别为 left,top,bottom,right.
18. host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
19. }
20.}
host.layout(0,0,host.getMeasuredWidth,host.getMeasuredHeight)它们分别代表了一个View的上下左右四个位置,显然,DecorView的左上位置为0,然后宽高为它的测量宽高。由于View的layout方法是final类型,子类不能重写,因此我们直接看View#layout方法即可:
View.java#layout()
1. public void layout(int l, int t, int r, int b) {
2. // // 当前视图的四个顶点
3. int oldL = mLeft;
4. int oldT = mTop;
5. int oldB = mBottom;
6. int oldR = mRight;
7. //isLayoutModeOptical(mParent);//判断该view布局模式是否有一些特殊的边界
8. //有特殊边界则调用setOpticalFrame(l, t, r, b)
9. //无特殊边界则调用setFrame(l, t, r, b)
10. boolean changed = isLayoutModeOptical(mParent) ?
11. setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
12. //如果视图的大小和位置发生变化,会调用onLayout()
13. if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
14. // 如果是单一View是没有子View的,所以onLayout()是一个空实现
15. //onLayout():确定该ViewGroup所有子View在父容器的位置,也可以理解为确定子view位置;
16. onLayout(changed, l, t, r, b);
17....
18.
19. }
}
View.java#setOpticalFrame()
1.private boolean setOpticalFrame(int left, int top, int right, int bottom) {
2. Insets parentInsets = mParent instanceof View ?
3. ((View) mParent).getOpticalInsets() : Insets.NONE;
4. Insets childInsets = getOpticalInsets();
5. //实际上是调用setFrame()
6. return setFrame(
7. left + parentInsets.left - childInsets.left,
8. top + parentInsets.top - childInsets.top,
9. right + parentInsets.left + childInsets.right,
10. bottom + parentInsets.top + childInsets.bottom);
11.}
Insets是不可变的,因此可以将其视为values。这里简单描述一下什么是insets边界,我这里用一段代码和图(13)来看一下Insets的四个值;
1.<?xml version="1.0" encoding="utf-8"?>
2.<inset xmlns:android="http://schemas.android.com/apk/res/android"
3. android:insetTop="50dp" android:insetLeft="50dp"
4. android:insetRight="50dp" android:insetBottom="50dp"
android:drawable="@color/colorAccent"/>
通过两张图我们理了解特殊边界,我们继续向下一看setFrame()方法,setFrame方法,会把四个位置信息传递进去,这个方法用于确定View的四个顶点的位置,即初始化mLeft,mRight,mTop,mBottom这四个值,在该方法中,会将新旧left、right、top、bottom进行对比,只要不完全相同就说明View的布局发生了变化,则将changed变量设置为true。然后比较View的新旧尺寸是否相同,如果尺寸发生了变化,并将其保存到变量sizeChanged中。如果尺寸发生了变化,那么sizeChanged的值为true
View.java#setFrame()
1. @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
2. protected boolean setFrame(int left, int top, int right, int bottom) {
3. boolean changed = false;
4.
5. if (DBG) {
6. Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
7. + right + "," + bottom + ")");
8. }
9.
10. if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
11. //将新旧left、right、top、bottom进行对比,只要不完全相对就说明View的布局发生了变化,
12. //则将changed变量设置为true
13. changed = true;
14.
15. //保存一下mPrivateFlags中的PFLAG_DRAWN标签信息
16. int drawn = mPrivateFlags & PFLAG_DRAWN;
17. //分别计算View的新旧尺寸
18. int oldWidth = mRight - mLeft;
19. int oldHeight = mBottom - mTop;
20. int newWidth = right - left;
21. int newHeight = bottom - top;
22. //控件的大小和位置有没有改变
23. boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
24.
25. //
26. invalidate(sizeChanged);
27. // 通过以下赋值语句记录下了视图的位置信息,即确定View的四个顶点
28. // 即确定了视图的位置
29. mLeft = left;
30. mTop = top;
31. mRight = right;
32. mBottom = bottom;
33. //mRenderNode.setLeftTopRightBottom()方法会调用RenderNode中原生方法的nSetLeftTopRightBottom()方法,
34. //该方法会根据left、top、right、bottom更新用于渲染的显示列表
35. mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
36. //向mPrivateFlags中增加标签PFLAG_HAS_BOUNDS,表示当前View具有了明确的边界范围
37. mPrivateFlags |= PFLAG_HAS_BOUNDS;
38.
39.
40. if (sizeChanged) {
41. //这里 sizeChange 方法内部调用了 onSizeChanged 方法。
42. //所以当控件的大小和位置改变的时候会回调 onSizeChanged 方法
43. sizeChange(newWidth, newHeight, oldWidth, oldHeight);
44. }
45....
46. return changed;
47. }
48.}
View.java#sizeChange()
1. private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {
2. onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
3....
4. rebuildOutline();
5. }
6.
7. protected void onSizeChanged(int w, int h, int oldw, int oldh) {
8. }
View.java#onLayout()
1.protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
2.}
注:对于单一View来说,由于在layout()中已经对自身View进行了位置计算,所以单一View的layout()就算已经完成了;
为什么呢?
因为只有viewGroup中才有子 view,如果自己定义view group 则必须实现这个方法,很好理解,你包含子view,测量出来大小了,你得告诉我在具体哪个位置显示,比如FrameLayout、LinearLayout、RelativeLayout等,他们的布局特性都是不一样的,需要各自根据自己的特性来进行制定确定子元素位置的规则;
ViewGroup.java#onLayout()
1./**
2. * @param changed 当前View的大小和位置改变了
3. * @param left 父View的左部位置
4. * @param top 父View的顶部位置
5. * @param right 父View的右部位置
6. * @param bottom 父View的底部位置
7. */
8.@Override
9.protected abstract void onLayout(boolean changed,
10. int l, int t, int r, int b);
FrameLayout.java#onLayout()
1.@Override
2.protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
3. //把父容器的位置参数传递进去
4. layoutChildren(left, top, right, bottom, false /* no force left gravity */);
5.}
FrameLayout.java#layoutChildren()
1. void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
2. final int count = getChildCount();
3. //以下四个值会影响到子View的布局参数
4. //parentLeft由父容器的padding和Foreground决定
5. final int parentLeft = getPaddingLeftWithForeground();
6. //parentRight由父容器的width和padding和Foreground决定
7. final int parentRight = right - left - getPaddingRightWithForeground();
8.
9....
10. for (int i = 0; i < count; i++) {
11. final View child = getChildAt(i);
12. if (child.getVisibility() != GONE) {
13. final LayoutParams lp = (LayoutParams) child.getLayoutParams();
14. //获取子View的测量宽高
15. final int width = child.getMeasuredWidth();
16. final int height = child.getMeasuredHeight();
17....
18. //当子View设置了水平方向的layout_gravity属性时,根据不同的属性设置不同的childLeft
19. //childLeft表示子View的 左上角坐标X值
20. switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
21. /* 水平居中,由于子View要在水平中间的位置显示,因此,要先计算出以下:
22. * (parentRight - parentLeft -width)/2 此时得出的是父容器减去子View宽度后的
23. * 剩余空间的一半,那么再加上parentLeft后,就是子View初始左上角横坐标(此时正好位于中间位置),
24. * 假如子View还受到margin约束,由于leftMargin使子View右偏而rightMargin使子View左偏,所以最后
25. * 是 +leftMargin -rightMargin .
26. */
27. case Gravity.CENTER_HORIZONTAL:
28. childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
29. lp.leftMargin - lp.rightMargin;
30. break;
31. //水平居右,子View左上角横坐标等于 parentRight 减去子View的测量宽度 减去 margin
32. case Gravity.RIGHT:
33. if (!forceLeftGravity) {
34. childLeft = parentRight - width - lp.rightMargin;
35. break;
36. }
37. //如果没设置水平方向的layout_gravity,那么它默认是水平居左
38. //水平居左,子View的左上角横坐标等于 parentLeft 加上子View的magin值
39. case Gravity.LEFT:
40. default:
41. childLeft = parentLeft + lp.leftMargin;
42. }
43. //当子View设置了竖直方向的layout_gravity时,根据不同的属性设置同的childTop
44. //childTop表示子View的 左上角坐标的Y值
45. //分析方法同上
46. switch (verticalGravity) {
47. case Gravity.TOP:
48. childTop = parentTop + lp.topMargin;
49. break;
50....
51. }
52. //对子元素进行布局,左上角坐标为(childLeft,childTop),右下角坐标为(childLeft+width,childTop+height)
53. child.layout(childLeft, childTop, childLeft + width, childTop + height);
54. }
55. }
56. }
View.java#setMeasuredDimensionRaw(),setFrame()
1. private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
2. mMeasuredWidth = measuredWidth;
3. mMeasuredHeight = measuredHeight;
4.
5. mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
6. }
7.
8.
9.
10. @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
11. protected boolean setFrame(int left, int top, int right, int bottom) {
12....
13. mLeft = left;
14. mTop = top;
15. mRight = right;
16. mBottom = bottom;
17....
18. }
所以说在自定义控件的时候在onLayout方法中一般采用getMeasuredWidth来获得控件的宽度,因为getMeasuredWidth在measure后就有了值,而getWidth在layout才有值。因此除了onLayout方法中采用getMeasuredWidth方法外,在其之外的其他地方一般采用getWidth方法来获取控件的宽度;
/**
1. * View最终 宽、高
2. */
3.@ViewDebug.ExportedProperty(category = "layout")
4.public final int getWidth() {
5. //View最终的宽 = 子View的右边界 - 子View的左边界
6. return mRight - mLeft;
7.}
8.@ViewDebug.ExportedProperty(category = "layout")
9.public final int getHeight() {
10. //View最终的高 = 子view的下边界 - 子view的上边界
11. return mBottom - mTop;
12.}
13.
14./**
15. View测量的 宽、高
16. */
17.public final int getMeasuredWidth() {
18. //Measured过程中返回的measured width
19. return mMeasuredWidth & MEASURED_SIZE_MASK;
20.}
21.public final int getMeasuredHeight() {
22. //Measured过程中返回的measured height
23. return mMeasuredHeight & MEASURED_SIZE_MASK;
24.}
这里有个疑问,怎样才能让getMeasuredWidth和getWidth方法的返回值不一样呢,其时在写的时候,一般这两个值都是相等的,为了区分这个值,可以通过下面这个案例,来证明一下;
MyViewGroup.java
1.public class MyViewGroup extends ViewGroup {
2.
3. public MyViewGroup(Context context) {
4. super(context);
5. }
6.
7. public MyViewGroup(Context context, AttributeSet attrs) {
8. super(context, attrs);
9. }
10.
11. @Override
12. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
13. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
14. View view = getChildAt(0);
15. /**
16. * 设置宽度值为100,MeasureSpec.EXACTLY精确测量模式
17. */
18. measureChild(view, MeasureSpec.EXACTLY + 100, MeasureSpec.EXACTLY + 100);
19. }
20.
21. @Override
22. protected void onLayout(boolean changed, int l, int t, int r, int b) {
23. View childView = getChildAt(0);
24. /**
25. * 设置子View的位置,左上右下
26. */
27. childView.layout(0, 0, 500, 500);
28. }
29.}
activity_main.xml
1.<?xml version="1.0" encoding="utf-8"?>
2.<com.example.application.MyViewGroup xmlns:android="http://schemas.android.com/apk/res/android"
3. xmlns:app="http://schemas.android.com/apk/res-auto"
4. xmlns:tools="http://schemas.android.com/tools"
5. android:layout_width="match_parent"
6. android:layout_height="match_parent"
7. tools:context=".Main2Activity">
8. <Button
9. android:id="@+id/bt_1"
10. android:layout_width="wrap_content"
11. android:layout_height="wrap_content">
12. </Button>
13.
14.</com.example.recyclerviewapplication.MyViewGroup>
MainActivity.java
1.public class MainActivity extends AppCompatActivity {
2.
3. private Button bt;
4.
5. @Override
6. protected void onCreate(Bundle savedInstanceState) {
7. super.onCreate(savedInstanceState);
8. setContentView(R.layout.activity_main);
9. bt=(Button)findViewById(R.id.bt_1);
10. }
11.
12. @Override
13. public void onWindowFocusChanged(boolean hasFocus) {
14. super.onWindowFocusChanged(hasFocus);
15. android.util.Log.d("MainActivity"," "+bt.getWidth()+","+bt.getHeight()+" ,"+bt.getMeasuredWidth()+" ,"+bt.getMeasuredHeight());
16. }
17.}
log输出:
9231 D MainActivity : 500,500,100,100
总结如下三点:
①getMeasuredWidth方法获得的值是setMeasuredDimension方法设置的值,它的值在measure方法运行后就会确定
②getWidth方法获得是layout方法中传递的四个参数中的mRight-mLeft,它的值是在layout方法运行后确定的
③一般情况下在onLayout方法中使用getMeasuredWidth方法,而在除onLayout方法之外的地方用getWidth方法。
ViewRootImpl.java#performDraw()
1. private void performDraw() {
2....
3. /**
4. * mFullRedrawNeeded 表示是否需要绘制当前窗口全部区域
5. * mReportNextDraw 表示是当前窗口区域是否绘制完成
6. * */
7. final boolean fullRedrawNeeded = mFullRedrawNeeded || mReportNextDraw;
8....
9. //实现分发draw的工作
10. boolean canUseAsync = draw(fullRedrawNeeded);
11....
12.}
ViewRootImpl.java#draw()
1. private boolean draw(boolean fullRedrawNeeded) {
2....
3. ///调用内部实现方法,来实现分发绘画的工作
4. scrollToRectOrFocus(null, false);
5. //如果界面发生了滚动,就分发滚动监听
6. if (mAttachInfo.mViewScrollChanged) {
7. mAttachInfo.mViewScrollChanged = false;
8. mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
9. }
10. //computeScrollOffset 判断是否要滑动动画。
11. //如果需要执行动画,则调用DeocView的onRootViewScrollYChanged,进行Y轴上的动画执行
12. boolean animating = mScroller != null && mScroller.computeScrollOffset();
13. final int curScrollY;
14....
15.
16. final float appScale = mAttachInfo.mApplicationScale;
17. final boolean scalingRequired = mAttachInfo.mScalingRequired;
18. //获取mDirty,该值表示需要重绘的区域
19. final Rect dirty = mDirty;
20....
21. //如果fullRedrawNeeded为真,怎dirty区域至为整个屏幕,表示整个view都需要绘制
22. //第一次绘制,需要绘制所有view
23. if (fullRedrawNeeded) {
24. dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
25. }
26....
27. //如果有注册TreeObserver下的监听,在调用onDraw之前会触发
28. mAttachInfo.mTreeObserver.dispatchOnDraw();
29....
30. if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
31. //是否开启硬件加速,如果是,View 的绘制流程都是一样的,区别就是 Canvas 不同
32. if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
33....
34. //开启了硬件加速,则执行该方法,内部最终还是会执行到view 的 draw 方法
35. mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
36. } ...
37. //未开启硬件加速,则执行该方法,执行drawSOftWare调用view的draw方法,整个绘画流程就跑起来了
38. if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
39. scalingRequired, dirty, surfaceInsets)) {
40. return false;
41. }
42. }
43....
44. return useAsyncReport;
45. }
ViewRootImpl.java#drawSoftware()
1.private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
2. boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
3. //锁定canvas区域,由dirty区域决定
4. //获取一块画布,这块画布会传递到各个onDraw方法中
5. canvas = mSurface.lockCanvas(dirty);
6...
7. //正式开始绘制
8. mView.draw(canvas);
9....
10. return true;
11. }
View.java#draw()
1.@CallSuper
2. public void draw(Canvas canvas) {
3. final int privateFlags = mPrivateFlags;
4. mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
5.
6. /*
7. * Draw traversal performs several drawing steps which must be executed
8. * in the appropriate order:绘制流程
9. * 绘制背景
10. * 1. Draw the background
11. * 如果需要,会保存画布已绘制的背景(可省略)
12. * 2. If necessary, save the canvas' layers to prepare for fading
13. * 绘制 View 的内容
14. * 3. Draw view's content
15. * 绘制子 View,子 View 的绘制也是按照这个流程进行
16. * 4. Draw children
17. * 如果需要,绘制边框
18. * 5. If necessary, draw the fading edges and restore layers
19. * 绘制装饰,如滚动条等
20. * 6. Draw decorations (scrollbars for instance)
21. * 如有必要,绘制默认的焦点突出显示
22. * 7. If necessary, draw the default focus highlight
23. */
24.
25. // Step 1, draw the background, if needed
26. //绘制背景
27. int saveCount;
28.
29. drawBackground(canvas);
30. // 通常情况下,会跳过第 2 步和第 5 步
31. // skip step 2 & 5 if possible (common case)
32. Fading Edge是View 设置边框渐变的效果
33. final int viewFlags = mViewFlags;
34. boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
35. boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
36. if (!verticalEdges && !horizontalEdges) {
37. //绘制 View 的内容,需要子类具体实现,View 的 onDraw 是一个空实现
38. //因为 View 并不是一个具体的 View ,不知道要绘制的内容,所以要绘制的内容留给具体的子类去具体实现
39. // Step 3, draw the content
40. onDraw(canvas);
41. //绘制内部包含的子 View,这个方法 View 也没有实现,具体的实现是在 ViewGroup 中.
42. //调用ViewGroup的dispatchDraw方法,让ViewGroup遍历并调用所有的onDraw方法,整个view绘画流程被激活
43. // Step 4, draw the children
44. dispatchDraw(canvas);
45.
46. drawAutofilledHighlight(canvas);
47.
48. // Overlay is part of the content and draws beneath Foreground
49. // 如果设置了 Overlay ,就调用并绘制 Overlay
50. if (mOverlay != null && !mOverlay.isEmpty()) {
51. mOverlay.getOverlayView().dispatchDraw(canvas);
52. }
53.
54. // 绘制前景色的drawble,绘制到canvas上
55. // 绘制scrollbars
56. // Step 6, draw decorations (foreground, scrollbars)
57. onDrawForeground(canvas);
58.
59. // Step 7, draw the default focus highlight
60. //绘制默认焦点高亮
61. drawDefaultFocusHighlight(canvas);
62.
63. if (isShowingLayoutBounds()) {
64. debugDrawFocus(canvas);
65. }
66.
67. // we're done...
68. return;
69. }
70. /*
71. * 后边是对于Fading Edge效果的设置,这次就不再分析了,有兴趣的朋友可以自己看一下这个效果;
72. *
73. */
View.java#drawBackground()
1.private void drawBackground(Canvas canvas) {
2. // 获取背景 drawable
3. final Drawable background = mBackground;
4. if (background == null) {
5. return;
6. }
7. // 根据在 layout 过程中获取的 View 的位置参数,来设置背景的边界
8. setBackgroundBounds();
9. ...
10. // 获取 mScrollX 和 mScrollY值
11. final int scrollX = mScrollX;
12. final int scrollY = mScrollY;
13. if ((scrollX | scrollY) == 0) {
14. background.draw(canvas);
15. } else {
16. // 如果 mScrollX 和 mScrollY 有值,则对 canvas 的坐标进行偏移
17. canvas.translate(scrollX, scrollY);
18. // 调用 Drawable 的 draw 方法绘制背景
19. background.draw(canvas);
20. canvas.translate(-scrollX, -scrollY);
21. }
22. }
View#onDraw()
1.protected void onDraw(Canvas canvas) {
2.}
View.java#dispatchDraw()
1.protected void dispatchDraw(Canvas canvas) {
2.
3.}
ViewGroup#dispatchDraw()
1. @Override
2. protected void dispatchDraw(Canvas canvas) {
3....
4. // 这里会对画布进行剪切,切掉Padding值
5. if (clipToPadding) {
6. clipSaveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
7. canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
8. mScrollX + mRight - mLeft - mPaddingRight,
9. mScrollY + mBottom - mTop - mPaddingBottom);
10. }
11.
12. ...
13. // 遍历子View
14. for (int i = 0; i < childrenCount; i++) {
15. while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
16. ...
17. }
18. }
19....
20. if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
21. //调用 drawChild 方法,进行绘制子view
22. more |= drawChild(canvas, child, drawingTime);
23. }
24. }
ViewGroup#drawChild()
1./**
2. *Draw 一个该view组的child。 此方法负责使canvas处于正确的状态。 这包括剪切,平移以使子view的滚动原点位于0、0,并应用任何动画转换。
3. * @param canvas 在其上绘制子项的画布
4. * @param child 画谁
5. * @param drawingTime draw 发生的时间
6. * @return 如果是 invalidate() 返回 true;
7. */
8.protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
9. return child.draw(canvas, this, drawingTime);
10.}
View.java#draw()
1. /**
2. * ViewGroup.drawChild()调用此方法以绘制每个子View。
3. * 这是View专门基于图层类型和硬件加速的渲染行为的地方。
4. */
5. boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
6. final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
7.
8. //是否支持硬件加速
9. boolean drawingWithRenderNode = mAttachInfo != null
10. && mAttachInfo.mHardwareAccelerated
11. && hardwareAcceleratedCanvas;
12....
13.
14. if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {
15. if (layerType != LAYER_TYPE_NONE) {
16. //未开启硬件加速
17. //调用 View 的 buildDrawingCache 方法
18. layerType = LAYER_TYPE_SOFTWARE;
19. buildDrawingCache(true);
20. }
21. cache = getDrawingCache(true);
22. }
23. if (drawingWithRenderNode) {
24. //延迟获取显示列表,直到获得动画驱动的alpha值为止,设置并传递给View
25. renderNode = updateDisplayListIfDirty();
26. if (!renderNode.hasDisplayList()) {
27. renderNode = null;
28. drawingWithRenderNode = false;
29. }
30. }
31.
32. int sx = 0;
33. int sy = 0;
34. if (!drawingWithRenderNode) {
35. //内部是一个空实现,用于我们在自定义滑动控件时,重写该方法,并设置mScrollX和mScrollY
36. computeScroll();
37. sx = mScrollX;
38. sy = mScrollY;
39. }
40....
41. //根据mScrollX和mScrollY移动画布的坐标系
42. if (offsetForScroll) {
43. canvas.translate(mLeft - sx, mTop - sy);
44. } ...
45. //设置画布透明度的操作,省略
46. ...
47.
48. if (!drawingWithDrawingCache) {
// 不用缓存,直接画
49. if (drawingWithRenderNode) {
// 是否硬件加速
50. mPrivateFlags &= ~PFLAG_DIRTY_MASK;
51. //开启硬件加速掉用drawRenderNode
52. ((RecordingCanvas) canvas).drawRenderNode(renderNode);
53. } else {
54. //是否已经绘制过一次了,如果没有,则会调用draw(canvas)方法,如果绘制了,则继续执行dispatchDraw,
55. if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
56. mPrivateFlags &= ~PFLAG_DIRTY_MASK;
57. dispatchDraw(canvas); 绘制子view的子view
58. } else {
59. draw(canvas);// 绘制子view自身
60. }
61. }
62. } else if (cache != null) {
// 有缓存的话,绘制bitmap,view的缓存是做为bitmap保存的.
63. mPrivateFlags &= ~PFLAG_DIRTY_MASK;
64. if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
65. ...
66. canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
67. } ...
68.
69.
70. return more;
71. }