View的绘制流程从ViewRoot的performTraversals()方法开始,主要经历三个过程:measure、layout、draw。
一、measure过程
1、调用View的measure(int widthMeasureSpec, int heightMeasureSpec),方法接收两个参数,widthMeasureSpec和heightMeasureSpec,这两个值分别用于确定View的宽度和高度的规格和大小。widthMeasureSpec和heightMeasureSpec这两个值又是从哪里得到的呢?通常情况下,这两个值都是由父布局经过计算后传递给子View的,说明父布局会在一定程度上决定子View的大小。最外层的根布局,它的widthMeasureSpec和heightMeasureSpec由系统产生。注意这个方法是final的,子类无法继承。
2、measure方法中调用onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
通过setMeasuredDimension方法设置最后的实际测量大小。
3、当View为ViewGroup时,可以通过调用measureChildren()方法来遍历计算每个子View的测量大小。
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
// 子View通过父布局得到widthMeasureSpec两个参数
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
小结:View测量的大小是由父布局以及子View本身共同计算的;ViewGroup需要对子View的测量负责,提供widthMeasureSpec,heightMeasureSpec两个参数。
二、layout过程
1、调用View的layout(int l, int t, int r, int b)方法,来进行布局。
2、layout方法中调用onLayout方法,在View中它是一个空方法,而在ViewGroup中它是一个抽象方法,这也不难理解,因为子View的布局通过ViewGroup来操纵,本身不进行操作。不同ViewGroup中必须实现onLayout方法,对子View进行布局。与measure过程对比,measure通过将父布局将widthMeasureSpec和heightMeasureSpec传递给子View然后才开始测量大小。
假设我们继承一个ViewGroup,可对onLayout进行如下操作。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
// 测量子View大小
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 通过不同实现来达到对子View进行不同布局。
if (getChildCount() > 0) {
View childView = getChildAt(0);
childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight());
View childView1 = getChildAt(1);
childView1.layout(0, childView.getHeight(), childView1.getMeasuredWidth(), childView.getHeight() + childView1.getMeasuredHeight());
}
}
这里可以提一下,view.getWidth方法正是layout中right-left,同理getHeight则是b-t;而view.getMeasuredWidth则是measure过程中最后setMeasuredDimension后确定的,一般两者会相同,因为在ViewGroup的onLayout方法会遵守测量的大小。
小结:View本身不会进行布局操作,表现为onLayout方法为空;ViewGroup需要对子View的布局负责,在ViewGroup中onLayout方法对子View进行布局操作。
三、draw过程
调用draw方法,大致流程
// Step 1, draw the background, if needed
// skip step 2 & 5 if possible (common case)
// Step 3, draw the content onDraw(canvas);
// Step 4, draw the children dispatchDraw(canvas);
// Step 6, draw decorations (scrollbars)
在View类中,onDraw是个空方法,这是好理解的,我们继承View之后需要自己来实现这个View的绘制从而达到自己想要的效果,不同的系统View之间肯定有不同的onDraw实现。
dispatchDraw同样是个空方法,而ViewGroup的dispatchDraw方法中就会有具体的绘制代码,其实就是调用子View的draw方法。
小结:View自身onDraw方法为空方法,需要自定义实现;而ViewGroup则可以调用子View进行draw操作。
小结
应用程序的最外层是一个FrameLayout,所以我们的绘制流程应当从ViewGroup开始:
- measure操作,ViewGroup调用子View的measure方法将widthMeasureSpec和heightMeasureSpec两个值传给子View,子view测得实际大小。倘若子View也为ViewGroup则不断遍历下去;
- layout操作,ViewGroup在onLayout方法中调用子View的layout方法,实现对子View的布局。倘若子View也为ViewGroup则不断遍历下去;
- draw操作,ViewGroup在draw方法中调用子View的draw方法,onDraw方法产生绘制效果,所以View中是个空方法,由子类实现。倘若子View也为ViewGroup则不断dispatchDraw遍历下去。
其他
来看一下动态加载View中经常要用到的方法,public View inflate(int resource, ViewGroup root, boolean attachToRoot),
- 当root为null时,加载的View则无法得到root的属性,
通过以上分析我们可以得知,子View大小和布局是需要父布局来决定的,所以当root == null时layout_width和layout_height默认为wrap_content; - 当root不为null时,得到root的属性,正确设置的layout_width和layout_height;返回的是
加载的view
; - 当root不为null时,得到root的属性,正确设置的layout_width和layout_height;返回的是
root
。
关于View重绘的两个方法invalidate()、requestLayout()。
- invalidate()方法measure和layout流程是不会重新执行的,只有draw流程可以得到执行。
- requestLayout()则可以将整个绘制流程可以完完整整地重新走一遍。
最后:
继承自View需要考虑onDraw,
继承自ViewGroup需要考虑measureChildren为子View测量大小和onLayout来布局子View;
继承自TextView、LinearLayout等,考虑丰富实现;