一, 绘制流程源码路径
1,Activity 加载 ViewRootImpl
- ActivityThread.handleResumeActivity()
- --> WindowManagerImpl.addView(decorView, layoutParams)
- --> WindowManagerGlobal.addView()
2,ViewRootImpl 启动 View 树的遍历
- ViewRootImpl.setView(decorView, layoutParams, parentView)
- -->ViewRootImpl.requestLayout()
- -->scheduleTraversals()
- -->TraversalRunnable.run()
- -->doTraversal()
- -->performTraversals()(performMeasure,performLayout,performDraw)
二, View 绘制流程
1,measure
(1)MeasureSpec 是什么?
重写过 onMeasure() 方法都知道, 测量需要用到 MeasureSpec 类获取 View 的测量模式和大小, 那么这个类是怎样存储这两个信息呢?
留心观察的话会发现, onMeasure 方法的两个参数实际是 32 位 int 类型数据, 即:
00 000000 00000000 00000000 00000000
而其结构为 mode + size , 前 2 位为 mode, 而后 30 位为 size.
==> getMode() 方法 (measureSpec --> mode):
- private static final int MODE_SHIFT = 30;
- // 0x3 转换为二进制即为: 11
- // 左移 30 位后: 11000000 00000000 00000000 00000000
- private static final int MODE_MASK = 0x3 <<MODE_SHIFT;
- public static int getMode(int measureSpec) {
- // 与 MODE_MASK 按位与运算后, 即将低 30 位清零, 结果为 mode 左移 30 位后的值
- return (measureSpec & MODE_MASK);
- }
getSize() 方法同理.
==> makeMeasureSpec() 方法 (mode + size --> 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);
- }
- }
这里解释一下, 按位或左侧为 size 的高 2 位清零后的结果, 右侧为 mode 的低 30 位清零后的结果, 两者按位或运算的结果正好为高 2 位 mode, 低 30 位 size, 例:
- 01000000 00000000 00000000 00000000 |
- 00001000 00001011 11110101 10101101 =
- 01001000 00001011 11110101 10101101
二进制计算规则可参考: https://www.cnblogs.com/joahyau/p/6420619.html
==> 测量模式:
- 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;
UNSPECIFIED: 父容器不对 View 作任何限制, 系统内部使用.
EXACTLY: 精确模式, 父容器检测出 View 大小, 即为 SpecSize; 对应 LayoutParams 中的 match_parent 和指定大小的情况.
AT_MOST: 最大模式, 父容器指定可用大小, View 的大小不能超出这个值; 对应 wrap_content.
(2)ViewGroup 的测量流程
回到 ViewRootImpl 的 performMeasure 方法, 这里传入的参数为顶层 DecorView 的测量规格, 其测量方式为:
- private static int getRootMeasureSpec(int windowSize, int rootDimension) {
- int measureSpec;
- switch (rootDimension) {
- case ViewGroup.LayoutParams.MATCH_PARENT:
- measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
- break;
- case ViewGroup.LayoutParams.WRAP_CONTENT:
- measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
- break;
- default:
- measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
- break;
- }
- return measureSpec;
- }
match_parent 和具体数值大小为 EXACTLY 模式, wrap_content 则为 AT_MOST 模式.
往下走, performMeasure 方法中调用了 DecorView 的 onMeasure 方法, 而 DecorView 继承自 FrameLayout, 可以看到 FL 的 onMeasure 方法中调用了 measureChildWithMargins 方法, 并传入自身的测量规格:
- 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);
- }
即测量子控件的大小, 测量规则详情可看 getChildMeasureSpec 方法, 总结如下:
childLayoutParams\parentSpecMode | EXACTLY | AT_MOST | UNSPECIFIED |
---|---|---|---|
dp | EXACTLY/childSize | EXACTLY/childSize | EXCATLY/childSize |
match_parent | EXACTLY/parentSize | AT_MOST/parentSize | UNSPECIFIED/0 |
wrap_content | AT_MOST/parentSize | AT_MOST/parentSize | UNSPECIFIED/0 |
回到 onMeasure 方法, 测完子控件之后, ViewGroup 会经过一些计算, 得出自身大小:
- // 加上 padding
- maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
- maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
- // 检查是否小于最小宽度, 最小高度
- maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
- maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
- // 检查 Drawable 的最小高度和宽度
- final Drawable drawable = getForeground();
- if (drawable != null) {
- maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
- maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
- }
- setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
- resolveSizeAndState(maxHeight, heightMeasureSpec,
- childState << MEASURED_HEIGHT_STATE_SHIFT));
综上, ViewGroup 的测量需要先测量子 View 的大小, 而后结合 padding 等属性计算得出自身大小.
(3)View 的测量流程
- View.performMeasure()
- -->onMeasure(int widthMeasureSpec, int heightMeasureSpec)
- -->setMeasuredDimension(int measuredWidth, int measuredHeight)
- -->setMeasuredDimensionRaw(int measuredWidth, int measuredHeight)
可以看到 setMeasuredDimensionRaw() 方法:
- private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
- // 存储测量结果
- mMeasuredWidth = measuredWidth;
- mMeasuredHeight = measuredHeight;
- // 设置测量完成的标志位
- mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
- }
View 不需要考虑子 View 的大小, 根据内容测量得出自身大小即可.
另外, View 中的 onMeasure 方法中调用到 getDefaultSize 方法:
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
- getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
- }
- 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:
- case MeasureSpec.EXACTLY:
- // 最终测量的结果都是父容器的大小
- result = specSize;
- break;
- }
- return result;
- }
这里看到精确模式和最大模式, 最终测量的结果都是父容器的大小, 即布局中的 wrap_content,match_parent 以及数值大小效果都一样, 这也就是自定义 View 一定要重写 onMeasure 方法的原因.
2,layout
布局相对测量而言要简单许多, 从 ViewRootImpl 的 performLayout 方法出发, 可以看到其中调用了 DecorView 的 layout 方法:
- // 实则为 DecorView 的 left, top, right, bottom 四个信息
- host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
进入 layout 方法, 发现 l,t,r,b 被传递到了 setFrame 方法中, 并设置给了成员变量:
- mLeft = left;
- mTop = top;
- mRight = right;
- mBottom = bottom;
所以, 布局实际为调用 View 的 layout 方法, 设置自身的 l,t,r,b 值. 另外, layout 方法中往下走, 可以看到调用了 onLayout 方法, 进入后发现为空方法. 因而查看 FrameLayout 的 onLayout 方法:
- @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();
- // 省略
- for (int i = 0; i <count; i++) {
- final View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- // 省略
- child.layout(childLeft, childTop, childLeft + width, childTop + height);
- }
- }
- }
可以看到, 进行一系列计算后, 调用了 child 的 layout 方法, 对子控件进行布局, 同时子控件又会继续往下对自己的子控件布局, 从而实现遍历.
综上, 布局实际为调用 layout 方法设置 View 位置, ViewGroup 则需要另外实现 onLayout 方法摆放子控件.
3,draw
(1) 绘制过程入口
- ViewRootImpl.performDraw()
- -->ViewRootImpl.draw()
- -->ViewRootImpl.drawSoftware()
- -->View.draw()
(2) 绘制步骤
进入到 View 的 draw 方法中, 可以看到以下一段注释:
/*
* 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)
*/
结合 draw 方法的源码, 绘制过程的关键步骤如下:
==> 绘制背景: drawBackground(canvas)
==> 绘制自己: onDraw(canvas)
==> 绘制子 view:dispatchDraw(canvas)
==> 绘制滚动条, 前景等装饰: onDrawForeground(canvas)
来源: https://www.cnblogs.com/joahyau/p/11294970.html