Android View绘制的三大流程
安卓的视图是树形结构的:
基本概念
在介绍视图的三大流程之前,我们必须先介绍一些基本的概念,才能更好地理解这整个过程。
窗口的概念
窗口表示的是一个窗口的概念,它是站在WindowManagerService角度上的一个抽象的概念,安卓中所有的视图都是通过窗口来呈现的,不管是活动,对话还是面包,只要有浏览的地方就一定有窗口。
这里需要注意的是,这个抽象的窗口概念和PhoneWindow这个类并不是同一个东西,PhoneWindow表示的是手机屏幕的抽象,它充当活动和DecorView之间的媒介,就算没有PhoneWindow也是可以展示查看的。
抛开一切,仅站在WindowManagerService的角度上,安卓的界面就是由一个个窗口层叠展现的,而窗户又是一个抽象的概念,它并不是实际存在的,它是以景观的形式存在,这个视图就是DecorView。
关于窗口这方面的内容,我们这里先了解一个大概
DecorView的概念
DecorView是整个窗口界面的最顶层视图,视图的测量,布局,绘制,事件分发都是由DecorView往下遍历这个景观树.DecorView作为顶级景观,一般情况下它内部会包含一个竖直方向的LinearLayout中,在这个的LinearLayout里面有上下两个部分(具体情况和的Android的版本及主题有关),上面是【标题栏】,下面是【内容栏】。在活动中我们通过的setContentView所设置的布局文件其实就是被加载到【内容栏】中的,而内容栏的ID是内容,因此指定布局的方法叫setContent()。
的ViewRoot的概念
的ViewRoot对应于ViewRootImpl类,它是连接的WindowManager和DecorView的纽带,查看的三大流程均是通过的ViewRoot来完成的。在ActivityThread中,当活动对象被创建完之后,会讲DecorView添加到窗口中,同时会创建对应的ViewRootImpl,并将ViewRootImpl和DecorView建立关联,并保存到WindowManagerGlobal对象中。
- WindowManagerGlobal.java
- root = new ViewRootImpl(view .getContext(),display);
- root.setView(view ,wparams,panelParentView);
Java的
查看的绘制流程是从的ViewRoot的performTraversals方法开始的,它经过测量,布局和绘制三个过程才能最终将一个视图绘制出来,大致流程如下图:
测量测量
为了更好地理解查看的测量过程,我们还需要理解MeasureSpec,它是搜索的一个内部类,它表示对查看的测量规格.MeasureSpec代表一个32位INT值,高2位代表SpecMode(测量模式),低30位代表SpecSize(测量大小),我们可以看看它的具体实现:
- MeasureSpec.java
- 公共静态 类MeasureSpec {
- private static final int MODE_SHIFT = 30;
- private static final int MODE_MASK = 0x3 << MODE_SHIFT;
- / **
- *未知模式:
- *父查看不对子查看有任何限制,子查看需要多大就多大
- * /
- 公共静态 最终 诠释 Unknown = 0 << MODE_SHIFT;
- / **
- *精确模式:
- *父查看已经测量出子Viwe所需要的精确大小,这时候查看的最终大小
- *就是SpecSize所指定的值。对应于match_parent和精确数值这两种模式
- * /
- public static final int EXACTLY = 1 << MODE_SHIFT;
- / **
- * AT_MOST模式:
- *子查看的最终大小是父查看指定的SpecSize值,并且子查看的大小不能大于这个值,
- *即对应wrap_content这种模式
- * /
- public static final int AT_MOST = 2 << MODE_SHIFT;
- //将大小和模式打包成一个32位的INT 型数值
- //高2位表示SpecMode,测量模式,低30位表示SpecSize,某种测量模式下的规格大小
- public static int makeMeasureSpec(int size , int mode){
- if(sUseBrokenMakeMeasureSpec){
- 返回大小 +模式;
- } else {
- 返回 (size &〜MODE_MASK)| (mode&MODE_MASK);
- }
- }
- //将32位的MeasureSpec解包,返回SpecMode,测量模式
- public static int getMode(int measureSpec){
- return (measureSpec&MODE_MASK);
- }
- //将32位的MeasureSpec解包,返回SpecSize,某种测量模式下的规格大小
- public static int getSize(int measureSpec){
- return (measureSpec&〜MODE_MASK);
- }
- // ...
- }
Java的
MeasureSpec通过将SpecMode和SpecSize打包成一个INT值来避免过多的对象内存分配,并提供了打包和解包的方法。
SpecMode有三种类型,每一类都表示特殊的含义:
UNSPECIFIED
父容器不对视图有任何限制,要多大就给多大,这种情况一般用于系统内部,表示一种测量的状态;
究竟
父容器已经检测出查看所需的精确大小,这个时候搜索的最终打消就是SpecSize所指定的值。它对应于的LayoutParams中的match_parent和具体数值这两种模式。
最多
父容器指定了一个可用大小即SpecSize,查看的大小不能大于这个值,具体是什么值要看不同观的具体实现。它对应于的LayoutParams中WRAP_CONTENT。
查看的MeasureSpec是由父容器的MeasureSpec和自己的的LayoutParams决定的,但是对于DecorView来说有点不同,因为它没有父类。在ViewRootImpl中的measureHierarchy方法中有如下一段代码展示了DecorView的MeasureSpec的创建过程,其中desiredWindowWidth和desireWindowHeight是屏幕的尺寸大小:
ViewGroup中的措施
- childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth,lp.width);
- childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight,lp.height);
- performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);
Java的
再看看getRootMeasureSpec方法:
- private static int getRootMeasureSpec(int windowSize, int rootDimension){
- int measureSpec;
- 开关(rootDimension){
- case ViewGroup.LayoutParams.MATCH_PARENT:
- //窗口无法调整大小。 强制 根 视图为 windowSize。
- measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.EXACTLY);
- 打破;
- 案例 ViewGroup.LayoutParams.WRAP_CONTENT:
- //窗口可以调整大小 设置最大尺寸为 根 视图。
- measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.AT_MOST);
- 打破;
- 默认:
- //窗口要 到 成为一门精确 的尺寸。 力 根 视图到 是 尺寸。
- measureSpec = MeasureSpec.makeMeasureSpec(rootDimension,MeasureSpec.EXACTLY);
- 打破;
- }
- 返回 measureSpec;
- }
Java的
通过以上代码,DecorView的MeasureSpec的产生过程就很明确了,因为DecorView是FrameLyaout的子类,属于ViewGroup中,对于ViewGroup中来说,除了完成自己的测量过程外,还会遍历去调用所有子元素的测量方法,各个子元素再递归去执行这个过程。和查看不同的是,一个ViewGroup是一个抽象类,他没有重写查看的onMeasure方法,这里很好理解,因为每个具体的ViewGroup中实现类的功能是不同的,如何测量应该让它自己决定,比如LinearLayout中和RelativeLayout的。
因此在具体的一个ViewGroup中需要遍历去测量子查看,这里我们看看一个ViewGroup中提供的测量子视图的measureChildWithMargins方法:
- 保护无效measureChildWithMargins(查看 孩子,
- int parentWidthMeasureSpec, int widthUsed,
- int parentHeightMeasureSpec, int heightUsed){
- 最终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);
- }
Java的
上述方法会对子元素进行测量,在调用子元素的测量方法之前会先通过getChildMeasureSpec方法来得到子元素的MeasureSpec。从代码上看,子元素的MeasureSpec的创建与父容器的MeasureSpec和本身的的LayoutParams有关,此外和查看的保证金和父类的填充有关,现在看看getChildMeasureSpec的具体实现:
- ViewGroup.java
- public static int getChildMeasureSpec(int spec, int padding, int childDimension){
- int specMode = MeasureSpec.getMode(spec);
- int specSize = MeasureSpec.getSize(spec);
- int size = Math。max (0,specSize - padding);
- int resultSize = 0;
- int resultMode = 0;
- 开关(specMode){
- //父母对 我们 施加了一个确切的 大小
- case MeasureSpec.EXACTLY:
- if(childDimension> = 0){
- resultSize = childDimension;
- resultMode = MeasureSpec.EXACTLY;
- } else if(childDimension == LayoutParams.MATCH_PARENT){
- //孩子要 到 是我们的 大小。就这样吧。
- resultSize = size ;
- resultMode = MeasureSpec.EXACTLY;
- } else if(childDimension == LayoutParams.WRAP_CONTENT){
- //孩子想 以 确定自己的 大小。不可能
- //比我们大
- resultSize = size ;
- resultMode = MeasureSpec.AT_MOST;
- }
- 打破;
- //父母对 我们 施加了最大的 尺寸
- case MeasureSpec.AT_MOST:
- if(childDimension> = 0){
- //孩子想要一个特定的 尺寸...就这样吧
- resultSize = childDimension;
- resultMode = MeasureSpec.EXACTLY;
- } else if(childDimension == LayoutParams.MATCH_PARENT){
- //孩子要 到 是我们的 规模,但我们的 规模是不 固定的。
- //约束孩子 到不 比我们更大。
- resultSize = size ;
- resultMode = MeasureSpec.AT_MOST;
- } else if(childDimension == LayoutParams.WRAP_CONTENT){
- //孩子想 以 确定自己的 大小。不可能
- //比我们大
- resultSize = size ;
- resultMode = MeasureSpec.AT_MOST;
- }
- 打破;
- //家长问 到 看到我们想有多大 ,以 成为
- 案例 MeasureSpec.UNSPECIFIED:
- if(childDimension> = 0){
- //孩子想要一个特定的 尺寸...让他拥有它
- resultSize = childDimension;
- resultMode = MeasureSpec.EXACTLY;
- } else if(childDimension == LayoutParams.MATCH_PARENT){
- //孩子要 到 是我们的 规模......发现 了 它有多大应该
- // be
- resultSize = 查看.sUseZeroUnspecifiedMeasureSpec?0: 尺寸;
- resultMode = MeasureSpec.UNSPECIFIED;
- } else if(childDimension == LayoutParams.WRAP_CONTENT){
- //孩子想 以 确定自己的 大小....找到 了 如何
- //它应该是大的
- resultSize = 查看.sUseZeroUnspecifiedMeasureSpec?0: 尺寸;
- resultMode = MeasureSpec.UNSPECIFIED;
- }
- 打破;
- }
- //没有检查ResourceType
- 返回 MeasureSpec.makeMeasureSpec(resultSize,resultMode);
- }
Java的
上述代码根据父类的MeasureSpec和自身的的LayoutParams创建子元素的MeasureSpec,具体过程同学们自行分析,最终的创建规则如下表:
ViewGroup中在遍历完子视图后,需要根据子元素的测量结果来决定自己最终的测量大小,并调用setMeasuredDimension方法保存测量宽高值。
- setMeasuredDimension(resolveSizeAndState(maxWidth,widthMeasureSpec,childState),heightSizeAndState);
Java的
这里调用了resolveSizeAndState来确定最终的大小,主要是保证测量的大小不能超过父容器的最大剩余空间maxWidth,这里我们看看它里面的实现:
- public static int resolveSizeAndState(int size , int measureSpec, int childMeasuredState){
- final int specMode = MeasureSpec.getMode(measureSpec);
- final int specSize = MeasureSpec.getSize(measureSpec);
- 最终 INT 结果;
- 开关(specMode){
- case MeasureSpec.AT_MOST:
- if(specSize < size ){
- 结果= specSize | MEASURED_STATE_TOO_SMALL;
- } else {
- 结果= 大小;
- }
- 打破;
- case MeasureSpec.EXACTLY:
- 结果= specSize;
- 打破;
- 案例 MeasureSpec.UNSPECIFIED:
- 默认:
- 结果= 大小;
- }
- 返回 结果| (childMeasuredState&MEASURED_STATE_MASK);
- }
Java的
关于具体的ViewGroup的onMeasure过程这里不做分析,由于每种布局的测量方式不一样,不可能逐个分析,但在它们的onMeasure里面的步骤是有一定规律的:
1.根据各自的测量规则遍历儿童元素,调用getChildMeasureSpec方法得到孩子的measureSpec;
2.调用儿童的度量方法;
3.调用setMeasuredDimension确定最终的大小。
查看的措施
查看的测量过程由其测量方法来完成,测量方法是一个最终的类型的方法,这意味着子类不能重写此方法,在景观的措施方法里面会去调用onMeasure方法,我们这里只要看onMeasure的实现即可,如下:
- 查看.java
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
- setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec))
- getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec));
- }
Java的
代码很简单,我们继续看看getDefaultSize方法的实现:
- 查看.java
- public static int getDefaultSize(int size , int measureSpec){
- int result = size ;
- int specMode = MeasureSpec.getMode(measureSpec);
- int specSize = MeasureSpec.getSize(measureSpec);
- 开关(specMode){
- 案例 MeasureSpec.UNSPECIFIED:
- 结果= 大小;
- 打破;
- case MeasureSpec.AT_MOST:
- case MeasureSpec.EXACTLY:
- 结果= specSize;
- 打破;
- }
- 返回 结果;
- }
Java的
从上述代码可以得出,景观的宽/高由specSize决定,直接继承视图的自定义控件需要重写onMeasure方法并设置WRAP_CONTENT时的自身大小,否则在布局中使用WRAP_CONTENT就相当于使用match_parent。
上述就是查看的量度大致过程,在测量完成之后,通过getMeasuredWidth /高度方法就可以获得测量后的宽高,这个宽高一般情况下就等于查看的最终宽高了,因为搜索的布局布局的时候就是根据measureWidth /高度来设置宽高的,除非在布局中修改了测量值。
布局布局
布局的作用是一个ViewGroup用来确定子元素的位置,当ViewGroup中的位置被确定后,它在onLayout中会遍历所有的子元素并调用其布局方法。简单的来说就是,布局方法确定视图本身的位置,而onLayout方法则会确定所有子元素的位置。
先看看查看的布局方法:
- public void layout(int l, int t, int r, int b){
- if((mPrivateFlags3&PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT)!= 0){
- onMeasure(mOldWidthMeasureSpec,mOldHeightMeasureSpec);
- mPrivateFlags3&=〜PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
- }
- int oldL = mLeft;
- int oldT = mTop;
- int oldB = mBottom;
- int oldR = mRight;
- boolean changed = isLayoutModeOptical(mParent)?
- setOpticalFrame(l,t,r,b):setFrame(l,t,r,b);
- if(changed ||(mPrivateFlags&PFLAG_LAYOUT_REQUIRED)== PFLAG_LAYOUT_REQUIRED){
- onLayout(改变,l,t,r,b);
- if(shouldDrawRoundScrollbar()){
- if(mRoundScrollbarRenderer == null ){
- mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
- }
- } else {
- mRoundScrollbarRenderer = null ;
- }
- mPrivateFlags&=〜PFLAG_LAYOUT_REQUIRED;
- ListenerInfo li = mListenerInfo;
- if(li!= null && li.mOnLayoutChangeListeners!= null ){
- ArrayList <OnLayoutChangeListener> listenersCopy =
- (ArrayList的<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
- int numListeners = listenersCopy。size ();
- for (int i = 0; i <numListeners; ++ i){
- listenerCopy.get(i).onLayoutChange(this,l,t,r,b,oldL,oldT,oldR,oldB);
- }
- }
- }
- mPrivateFlags&=〜PFLAG_FORCE_LAYOUT;
- mPrivateFlags3 | = PFLAG3_IS_LAID_OUT;
- }
因微信字数限制,请点击原文链接查看完整内容
总结
到这里,查看的措施,布局,绘制三大流程就说完了,这里做一下总结:
- 如果是自定义的ViewGroup的话,需要重写onMeasure方法,在onMeasure方法里面遍历测量子元素,同理onLayout方法也是一样,最后实现的onDraw方法绘制自己;
- 如果自定义视图的话,则需要从写onMeasure方法,处理WRAP_CONTENT的情况,不需要处理onLayout,最后实现的onDraw方法绘制自己;