您的位置:首页 > 移动开发 > Android开发

Android进阶系列0—View的工作流程:measure,layout,draw小结

2016-08-05 19:49 543 查看
本文主要来自《Android开发艺术探索》和郭霖 《Android视图绘制流程完全解析,带你一步步深入了解View(二)》 两部分的综合理解。

本文只阐述流程,不讲述细节,大家可以结合上述两部分的细节看。如有问题,欢迎大家在评论区指出,谢谢!

View由ViewRoot完成三大流程。在ActivityThread中,当Activity对象被创建完毕后,DecorView会被添加到Window中,同时创建ViewRootImpl的对象,将其和DecorView关联。

View的绘制流程由ViewRoot的performTraversals()方法调用performMeasure,performLayout,performDraw完成顶级View(即DecorView)的measure,layout,draw。

measure

View的measure要分View和ViewGroup两种情况考虑,对于View我们可以给出通用的measure()方法,但是不同的ViewGroup(指ViewGroup的具体实现类,比如LinearLayout,RelativeLayout等)需求不一样,measure的过程不尽相同,所以ViewGroup给出了可以共用的方法,如measureChildren(),measureChild()等,留出了各ViewGroup实现类自行编写的抽象方法onMeasure()。

View的measure

View的对象调用measure()方法处理传入的MeasureSpec参数(这个参数比较好懂,就不解释了)

measure()方法内调用onMeasure()方法处理MeasureSpec参数

onMeasure()中再调用setMeasuredDimension()方法去设置View的具体宽高值

宽高值由getDefaultSize()方法将传入的MeasureSpec处理后得到(对于UNSPECIFIED这种情况,宽高值还与getSuggestedMinimumWidth等参数有关,由于这种SPEC_MODE很少用,这里不做讨论)

上述流程代码结构如下:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
@Override
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}

protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
setMeasuredDimension(getDefaultSize(...),...);
}


ViewGroup的measure

ViewGroup的measure与它自身的布局特性及内部的子布局有关。也就是说ViewGroup在measure自己的时候,需要先完成子布局的measure,然后再结合自己的特性(比如竖直,水平等)给出自身的measure值。所以,对于ViewGroup类来说,抽象出了几个通用的方法,完成子布局measure的:measureChildren(),measureChild(),getChildMeasureSpec()等。而特性相关的给出了一个抽象方法onDraw(),由各个ViewGroup的实现类自行重写。

/*
*遍历当前布局下的所有子视图,然后逐个调用measureChild()方法来测量相应子视图的大小
*/
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);
}
}
}
/*
*measueChild()中,调用getChildMeasureSpec()方法去计算子视图的MeasureSpec,并将MeasureSpec传入child.measure()对View设置
*/
protected void measureChild(View child,int parentWidthMeasureSpec,int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(...);
final int childHeightMeasureSpec = getChildMeasureSpec(...);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}


说完ViewGroup measure时的共有方法,再看看需要各自实现的onMeasure(),比如在LinearLayout中,onMeasure的实现大致如下:

/*
*按方向选择函数
*/
protected void onMeasure(int widthMeasureSpec,int heigthMeasureSpec){
if(mOrientation==VERTICAL){
measureVertical();
}else{
measureHorizontal();
}
}
/*
对水平LinearLayout布局来说,调用了*measureChildBeforeLayout()方法,并在函数末尾调用了setMeasuredDimension()设置LinearLayout的measure值
*/
void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
......
measureChildBeforeLayout();
......
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
......
}
/*
*measureChildWithMargins是在ViewGroup中定义的measure通用方法,和measureChild的不同之处在于多考虑了margin等属性对测量的影响
*/
void measureChildBeforeLayout(......) {
measureChildWithMargins(......);
}


至此,measure的过程就说完了。对于View来说,调用自身的measure()方法即可;对于ViewGroup,遍历调用所有子布局的measure方法后,在自定义的onMeasure()方法中得到ViewGroup的measure值。

layout

对于layout,View中给出了layout()方法用于设定View相对于父布局的位置。对于ViewGroup,设置完自己的位置后(ViewGroup本身相对于父布局的位置),怎也也得去布局子视图的位置吧。onLayout()方法就应运而出,用于确定父布局中子视图的位置。可以这么理解,onLayout()是控件留出来的编程接口方法,给View和ViewGroup的子类继承,当然,因为View不存在子布局,所以View的onLayout()方法为空方法,而在ViewGroup中,类似于measure时,不同的ViewGroup实现类的布局结构不同,onLayout()由各实现类自行重写。

还是参考LinearLayout的代码:

/*
*LinearLayout重写的onLayout方法
*/
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
/*
*遍历所有子布局并调用setChildFrame设定布局位置
*/
void layoutHorizontal(int left, int top, int right, int bottom) {
......
for (int i = 0; i < count; i++) {
int childIndex = start + dir * i;
final View child = getVirtualChildAt(childIndex);
......
setChildFrame(child, childLeft + getLocationOffset(child), childTop,
childWidth, childHeight);
......
}
}
/*
*调用child.layout设定子布局位置
*/
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}


这样,layout的过程也分析完了,总的来说过程就是,使用layout确定完自己的布局之后,就调用onLayout确定子布局的位置(View的onLayout方法就是空方法)。

draw

等到测量和布局都完了,就可以进行真正的视图绘制了。View的绘制主要有四步:

1. 绘制背景

2. 绘制自己(onDraw)

3. 绘制children(dispatchDraw)

4. 绘制装饰

每个视图的内容部分肯定都是不相同的,所以View的onDraw()是空方法,这部分的功能交给子类来去实现。

第四步的作用是对当前视图的所有子视图进行绘制。但如果当前的视图没有子视图,那么也就不需要进行绘制了。因此你会发现View中的dispatchDraw()方法又是一个空方法。

乌啦啦,View的工作流程就说完了,谢谢观看!有问题请留言,欢迎讨论~

方法论就这些,后面博主会放一个Github的demo链接,有兴趣的小伙伴可以瞅瞅。to be continued…
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: