概述
View的整个过程是从ViewRoot开始的,实现类是ViewRootImpl类,在这个类中通过setView()
方法添加DecorView,之后以DecorView为rootView。在ViewRootImpl类中通过performTraversals()
方法,经过measure、layout、draw三个过程最终将View绘制出来。
Measure过程
MeasureSpec
首先提到MeasureSpec,这个类承担着MeasureMode和MeasureSize两个功能,将32位int的高两位表示MeasureMode,低30位代表MeasureSize,通过下面的代码我们可以看到MeasureSpec这个类的功能。
|
|
Measure过程
最开始的measure起点是从ViewRootImp
的performTraversals()
开始的,在方法内部调用
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
参数分别由Window大小和自身的LayoutParams决定。对于最外层的DecorView来说一般是
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
也就是说尺寸是窗口的大小,mode是EXACTLY的.在PerformMeasure中调用view.measure()
而在measure()
中调用onMeasure()
进行测量。
|
|
通过以上代码可以看到,在setMeasuredDimension()之后则可以通过getMeasuredWidth获取width,但不等于最后的width,在layout时会提到。在onMeasure中调用setMeasuredDimension,在其中调用了getSuggestedMininumWidth(),在里面看是否存在背景,然后取了View的minWidth和背景的minWidth的较大值传入,在getDefaultSize中发现,只有在UNSPECIFIED模式时才会用到。同时需要注意的是AT_MOST模式时的尺寸是measureSpec的specSize。而参数measureSpec是哪来的我们看看ViewGroup中的方法。
在ViewGroup中并没有重写onMeasure方法,那么子类一定会重写,以LinearLayout为例,
|
|
以measureVertical为例,在该方法中会对每个子View调用measureChildWithMargins
|
|
在getChildMeasureSpec()
中传入了父View的Spec和padding其中padding包括了父View的左右padding、子View的左右margin,以及父View在水平方向上用了的空间,最后一个参数是子View的layputParams,在getChildMeasureSpec()
中我们可以看到最重要的一个东西,就是父View是如何影响子View测量的一个过程:
- 父类是EXACTLY时:子类为固定值或者MATCH_PARENT时子View都是EXACTLY模式,size也都固定,一个是自身的size,一个是父View剩余的size,这很好理解。当子View是WRAP_CONTENT时,子View的模式是AT_MOST模式,大小是剩下的size,结合上面提到的在getDefaultSize()会发现,当子View并没有重写onMeasure方法并对WRAP_CONTENT情况进行处理时的size其实就是MATH_PARENT。
剩下的就不一一分析了,看图:
在这里可以注意到当子View是EXACTLY模式时,父View不管是什么,都是EXACTLY模式。
最后:在得到了子View的MeasureSpec后,就传进了子View的measure方法,完成父View到子View测量的一个传递过程。
最后总结一下measure的流程:最开始的measure过程是在ViewRootImp类中的performMeasure()中开始的,传入的measureSpec和其他View的不同,因为rootView并不存在父View(可以将其父View看成固定大小,且模式是EXACTLY的一个View),其自身的MeasureSpec是由Window大小和自身的LayoutParams决定得。之后在performMeasure中调用measure方法,在measure中调用onMeasure,当子View是ViewGroup时,会对每个子View调用MeasureChild()方法,在该方法中通过结合父View的MeasureSpec和子View的layoutParams得到子View的MeasureSpec,之后调用子View的Measure(),就这样一直解析View树,完成Measure过程.
Layout过程
layout过程用于ViewGroup确定子View的位置,也是在ViewRootImp的performTraversals()
中最先开始的,在该方法内对rootView调用performLayout(),在performLayout()中:
final View host = mView;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
而在View的layout方法中:
在layout方法中,调用setFrame设置mLeft、mRight、mTop、mBottom,之后该View在其父View中的位置基本也就确定了,如果变了则会同时在layout()中调用onSizeChanged()
,这个方法是空方法,我们可以自己重写。之后在调用onLayout()设置子View的layout,View的onlayout方法为空,而ViewGroup中的onLayout方法为抽象方法,所以参考LinearLayout()的源码
|
|
可以看到在LinearLayout中会对每个子View计算相应的位置,然后调用setChildFrame()
|
|
在setChildFrame()中调用child.layout()来设置子View的位置,可以看到child.layout方法包括width和height,这就是measure过程获取的,而在layout中会调用setFrame(),在其中设置left、top等如下:
|
|
而这时我们就可以通过getWith()
、getHeight()
获取View的宽高,因为:
所以我们我们总说getMeasuredWith几乎都等于getWidth,只有你重写chid.layout时会产生不同,例如下面的getWidth()就会比getMeasureWidth多100。
所以可以看出来layout是用于设置View自身的位置,而onLayout方法是用于设置子View的位置的。
Draw过程
Draw的整个流程:performTravels()
->performDraw()
->drawSoftware()
,在drawSoftware()
中通过Surface获取了Canvas,然后
mView.draw(canvas);
看下draw的源码:
|
|
从注释里我们可以看到draw()的过程分为6步:
- 首先绘制背景
- 如果需要的话保存当前画布的堆栈状态并在该画布上创建Layer用于绘制View在滑动时的边框渐变效果
- 调用onDraw()绘制内容
- 调用disparchDraw()绘制子View,在View类中不存在子View,所以该方法是个空方法,在ViewGroup中重写了该方法,该方法中的主要操作就是对每个子View调用draw()方法。
- 如果需要的话绘制渐变效果,然后restore图层
- 绘制View的滚动条
invalidate、postInvalidate和requestLayout的区别:
因为Android的UI操作并不是线程安全的,所以在更新View的时候需要在主线程,因此invalidate并不能在非UI线程中调用,在非UI线程调用时需要借助handler,比较麻烦,这时可以调用postInvalidate()实现更新View.同时invalidate、postInvalidate只会重新调用View的draw(),并且只绘制那些“需要重绘的”视图,即谁(View的话,只绘制该View ;ViewGroup,则绘制整个ViewGroup)。而requestLayout则会调用measure和layout过程,不会调用draw().
当View调用setVisiable()时,View的可视状态在INVISIBLE/ VISIBLE 转换为GONE状态时,会间接调用requestLayout() 和invalidate方法。