一、绘制流程源码路径

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)

最新文章

  1. GitHub的使用记录
  2. javascript 杂记
  3. Webservice简介
  4. Android &quot;adb devices no permissions&quot;
  5. UVa 437 The Tower of Babylon(经典动态规划)
  6. 如何用 Robotframework 来编写优秀的测试用例
  7. 九、UINavigationController切换视图 实例
  8. sqlserver表数据导出为insert into语句
  9. 已知局域网IP地址,如何查看mac
  10. 【poj2728】Desert King
  11. libimobiledevice安装步骤
  12. Oracle 常用的SQL语法和数据对象
  13. JSF教程(10)——生命周期之Update Model Values Phase
  14. 禁用iOS9 App Transport Security(ATS)特性时不起作用
  15. FoxOne---一个快速高效的BS框架--(2)
  16. Dojo实现Tabs页报错(一)
  17. Windows cmd
  18. linux 手动安装 oracle(转)
  19. 在java项目中使用webservice
  20. STL的容器算法迭代器的设计理念

热门文章

  1. 【jar包管理】Maven BOM
  2. JavaScript 事件(捕获和冒泡 兼容性写法)
  3. 吐槽:那些Java设计中不得不说的槽点
  4. 微服务-springboot多环境配置(开发生产测试环境切换)
  5. SCUT 130:对抗女巫的魔法碎片(贪心)
  6. MacBook强制清除gardle缓存
  7. echo-nginx-module的安装、配置、使用
  8. C++学习书籍推荐《C++ Primer 第四版》下载
  9. Bzoj 3874: [Ahoi2014&amp;Jsoi2014]宅男计划 三分+贪心
  10. 个人永久性免费-Excel催化剂功能第51波-聚光灯功能,长宽工作表不看错位使用