系列文章传送门 (持续更新中..) :android
自定义控件(一) Activity的构成(PhoneWindow、DecorView)canvas
在上一篇中咱们详细分析了 View 工做三大流程中最复杂的 measure 流程, 掌握了 measure 流程后, layout 和 draw 流程就相对比较简单些了。源码分析
layout
方法被父容器调用后它的位置将被肯定下来, 而后它在 onLayout
中遍历全部的子元素并调用它的 layout
方法对子元素进行摆放, 而在 layout 中 onLayout
方法又被调用, 如此反复直到布局完成。简单讲:layout 方法肯定 View 自己的位置, onLayout 肯定全部子元素的位置布局
在看源码以前,先提出一个问题, View 的 getWidth()
和 getMeasuredWidth()
有什么区别?post
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);
...
}
复制代码
layout 方法中, 先调用 setFrame()
给本身的四个顶点赋值, 就肯定了本身的位置。而后调用 onLayout
方法对子元素进行摆放优化
protected boolean setFrame(int left, int top, int right, int bottom) {
...
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
...
}
复制代码
看到没,mLeft、mTop 、mRight 、mBottom
就是这个 View 的四个顶点,当四个顶点的值呗肯定,View 的位置就摆放完了。 因为在 View 中 onLayout()
方法是空实现, ViewGroup 的 onLayout()
是抽象方法, 因此就挑一个 ViewGroup 经常使用的子类 FrameLayout 看一下 (其它都相似, 本身能够去看下):ui
#FrameLayout - onLayoutthis
protected void onLayout(boolean changed, int left, int top, int right, int bottom){
layoutChildren(left, top, right, bottom, false);
}
复制代码
onLayout
的参数直接传给 layoutChildren
,继续走:
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
...
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);
...
复制代码
很明显, 在 layoutChildren 遍历全部的子元素, 并调用其 layout 方法来摆放子元素,这样父容器在 layout 方法中完成本身的摆放后,经过 onLayout 方法去遍历调用子元素的 layout 方法,子元素又会经过 layout 方法肯定本身的位置,这样一层一层传递下去从而完成整个 View 树的 layout 过程。
width
和 height
, 其实就是这个 view 的测量宽/高。前面分析了 layout 中传入的参数会对 View 的四个顶点(mLeft、mTop 、mRight 、mBottom)
赋值来肯定位置,这里咱们来看一下 getWidth()
的返回值:public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
复制代码
结合咱们刚刚的源码分析不难看出来,mRight - mLeft 和 mBottom - mTop 的返回值不就分别是 view 的测量宽高么, 因此在系统 View 的默认实现中,以及开发中咱们能够直接认为 getWidth() = getMeasuredWidth(),只是赋值时间不一样, getHeight 和 getMeasuredHeight 同理, getMeasuredWidth()
是在 onMeasure()
方法中执行完成测量流程后并保存尺寸的时候被赋值,getWidth()
是在 layout
方法中肯定本身位置的时候被赋值。
固然也存在两种状况会出现不相等:一种是某些极端状况系统须要屡次执行measure流程,这时则除了最后一次measure,前几回的measure结果就可能存在不相等。另外一种则是在 onLayout() 中调用 layout 时, 对传入的四个顶点值作了一些运算处理, 则这两个值也是不相等的,以下
protected void onLayout(boolean changed, int left, int top, int right, int bottom){
...
child.layout(childLeft, childTop, childLeft + width + 100, childTop + height + 100);
...
}
// 或重写 layout 方法
public void layout(int l, int t, int r, int b) {
super.layout(l, t, r + 100, b + 100);
}
复制代码
绘制过程就是将 View 绘制到屏幕上, 它分为4步:
具体从 draw
方法中能够明了的看出来:
public void draw(Canvas canvas) {
...
drawBackground(canvas);
...
onDraw(canvas);
...
dispatchDraw(canvas);
...
onDrawForeground(canvas);
...
}
复制代码
绘制过程的传递是经过 dispatchDraw
来实现,dispatchDraw
中会遍历全部子元素的 draw
方法,如此反复下去直到绘制完成。
这是 View 的一个特殊方法,具体看源码:
/**
* If this view doesn't do any drawing on its own, set this flag to * allow further optimizations. By default, this flag is not set on * View, but could be set on some View subclasses such as ViewGroup. * * Typically, if you override {@link #onDraw(android.graphics.Canvas)} * you should clear this flag. * * @param willNotDraw whether or not this View draw on its own */ public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); } 复制代码
从注释大体能看出来,若是一个 View 不须要对本身进行任何绘制,设置这个标志位为 true 后,系统会进行相应的优化,即绕过 draw()
方法,换而直接执行 dispatchDraw()
,以此来简化绘制流程。默认状况下 View 没有设置这个标志位, 而 ViewGroup 默认会启动这个标志位。
dispatchDraw()
之外的任何一个绘制方法内绘制内容,你显示的关闭这个 WILL_NOT_DRAW 这个标志位:View.setWillNotDraw(false)
;以为有用的话,点个赞再走呗~