每个Activity都是这样的树结构,一个Activity里最外层包含PhoneWindow,而PhoneWindow里包含DecorView,然后DecorView里包含有TitleView和ContentView,对于ContentView,我们立马就会联想到setContentView()方法有木有,其实这个方法就是设置ContentView的布局,下面我来这几个作出解释:
PhoneWindows:它继承于Window类,是Window类的具体实现,即我们可以通过该类具体去绘制窗口。并且,该类内部包含了一个DecorView对象,该DectorView对象是所有应用窗口(Activity界面)的根View。 简而言之,PhoneWindow类是把一个FrameLayout类即DecorView对象进行一定的包装,将它作为应用窗口的根View,并提供一组通用的窗口操作接口。
DecorView:是PhoneWindow类的内部类(后面版本将DecorView拿出来了)。该类是一个FrameLayout的子类,并且是PhoneWindow的子类,该类就是对普通的FrameLayout进行功能的扩展,更确切点可以说是修饰(Decor的英文全称是Decoration,即“修饰”的意思),比如说添加TitleBar(标题栏),以及TitleBar上的滚动条等 。最重要的一点是,它是所有应用窗口的根View 。它主要有以下功能:
Dispatch ViewRoot分发来的key、touch、trackball等外部事件;
DecorView有一个直接的子View,我们称之为System Layout,这个View是从系统的Layout.xml中解析出的,它包含当前UI的风格,如是否带title、是否带ActionBar等。可以称这些属性为Window decorations。
作为PhoneWindow与ViewRoot之间的桥梁,ViewRoot通过DecorView设置窗口属性。可以这样获取 View:view = getWindow().getDecorView();
DecorView只有一个子元素为LinearLayout。代表整个Window界面,包含通知栏,标题栏,内容显示栏三块区域。DecorView里面TitleView:标题,可以设置requestWindowFeature(Window.FEATURE_NO_TITLE)取消掉,ContentView:是一个id为content的FrameLayout。我们平常在Activity使用的setContentView就是设置在这里,也就是在FrameLayout上;
View绘制大致可以分为三个流程,分别是measure(测量),layout(布局),draw(绘制),这三者的顺序就是measure(测量)->layout(布局)->draw(绘制)。
如果你认为View绘制的起点在setContentView方法中,那么你就错了,setContentView方法只不过是用来绑定DecorView中的ContentView的,也就是只是指定它的布局而已,那么setContentView究竟做了什么工作,View绘制的起点又在哪里呢?请点击阅读:深入理解Android之View的绘制流程
其实Measure的目的非常简单,就是测量View的宽和高。
ViewGroup.LayoutParams:我们常见的ViewGroup是各种布局等控件,像线性布局(LinearLayout),相对布局(RelativeLayout),约束布局(ConstraintLayout),网格布局(GridLayout)等等,而LayoutParams类就是指定View宽高等布局参数而被使用的。其实很简单,就对应着我们在布局文件中对应的为View设置各种宽高,如下所示:
MeasureSpec:MeasureSpec(View的内部类)测量规格为int型,值由高2位规格模式specMode和低30位具体尺寸specSize组成。其中SpecMode只有三种值:
注意对于一个ViewGroup或者View的宽高而言,都一一对应一个MeasureSpec。
父View都会传递一个父View自身的MeasureSpec给子View,子View再根据自身设置的宽高参数LayoutParams属性和父View的MeasureSpec再去计算出子View自身的MeasureSpec,从而决定子View最终的宽和高。具体的计算的逻辑请查看getChildMeasureSpec()方法。
下面我们来看看这些组合下的子View和父View之间宽高的关系,首先笔者将LayoutParams和
MeasureSpec组合起来分析最终子View的宽高已经父View的宽高,注意这里的LayoutParams指的是子View的宽高设置参数,而MeasureSpec是父View传递给子View的,因为LayoutParams有三种情况(不讨论fill _ parent,因为已经过时),而MeasureSpec也有三种,那么最终笔者将会分析这3*3 = 9种情况:
这9种情况看出的公共条件:
没错,笔者就是根据这三个条件来分析出子View的测量宽高和测量模式,也就是确定子View的MeasureSpec。
当父View的MeasureSpec的测量模式是EXACTLY,可以确定父View的大小是确切的。
情况1:子View设置的宽高参数LayoutParams是一个具体的值(比如200dp):
这种情况下最简单了,因为父View的测量模式是ExACTLY,那么父View的大小一定是确切的,由于子View的宽高参数是一个具体的值,那么最后子View计算的MeasureSpec的mode也是EXACTLY,size为子VIew和父View中的最小值,为什么不直接为子View的大小呢,这里要注意一种情况,代码如下:
可以看到这段布局代码中最外层是一个相对布局,而里面是一个线性布局,线性布局里面有个TextView,你会发现线性布局的宽度是匹配父控件,而高度是100dp,再看看TextView设置的宽高,宽度是匹配父控件,而高度却是200dp明显超过父布局线性布局100dp,那么最终TextView的最终大小会是200dp吗?你的答案呢,答案很显然不会吧,这是注意的地方。
情况2:子View设置的宽高参数LayoutParams是MATCH_PARENT:
因为父View的大小是确切的,虽然子View的宽高参数是MATCH_PARENT,也就是充满整个父View,因此最终子View的大小和父亲是一样的,所以最终子View的MeasureSpec的mode就是EXACTLY,size为父View的size一样的大小。
情况3:子View设置的宽高参数LayoutParams是WRAP_CONTENT:
因为父View的大小是确切的,而子View的大小是WRAP_CONTENT,也就是说子View的大小由子View的内容所决定,无论内容多大,最终子View还是不被允许超过父View,因为父View传递过来的是EXACTLY,这里可以确定子View的mode一定是AT_MOST,因为子View的大小是可大可小的,由子View的内容所决定,但是最大不会超过父View给它的大小,所以最终子View的的MeasureSpec的mode就是AT_MOST,size为父View的size一样的大小。这里还需要提出一点,这个内容的大小包括很多东西,比如TextView的内容大小,还包括内边距和外边距等等。
当父View的MeasureSpec的测量模式是AT_MOST,可以确定父View的大小是不确切且不超过一个具体值。
情况4:子View设置的宽高参数LayoutParams是一个具体的值:
因为子View的大小是个确切的值,可以明确确定子View的mode就是EXACTLY的,而且父View明确告诉子View的大小绝对不能超过父View传递过来的那个size,所以这里有个比较,如果子View设置的大小是超过父View给的那个size,子View最终的size就是父View给的那个size,而如果子View设置的大小小于父View的那个size,那么最终的size就是子View设置的大小,等于的情况笔者就不说了。
情况5:子View设置的宽高参数LayoutParams是MATCH_PARENT:
因为子View是MATCH_PARENT,意思是充满父View的,而父亲View是AT_MOST的,这个时候是不是觉得有点难以分析呢,其实很简单,这个时候子View的大小跟随父View大小变化而变化,但是大小也肯定不会超过父View传递给子View的那个size,所以子View的mode为AT_MOST,而size就是父View传递过来的那个size。
情况6:子View设置的宽高参数LayoutParams是WRAP_CONTENT:
因为子View是WRAP_CONTENT,子View的大小是由内容所决定的,但是父View给子View的测量模式是AT_MOST,所以最终子View的大小也肯定不会超过父View传递过来的size,因此最终子View的mode就是AT_MOST,size就是父View传递过来的那个size。
当父View的MeasureSpec的测量模式是UNSPECIFIED,就是说子View可以不受拘束,想多大就多大
情况7:子View设置的宽高参数LayoutParams是一个具体的值:
由于子View的大小是个具体的值,尽管父View告诉子View它的大小不受拘束,但是子View已经把大小值固定了,因此子View的mode就是EXACTLY,size就是子View设置的大小。
情况8:子View设置的宽高参数LayoutParams是MATCH_PARENT:
因为父View的测量模式是UNSPECIFIED,那么子View的大小是毫无拘束的,因此子View的mode也是UNSPECIFIED,这个时候计算子View自身的size是件毫无意义的事,因此源码中好像size设置为0了。
情况9:子View设置的宽高参数LayoutParams是WRAP_CONTENT:
因为父View的测量模式是UNSPECIFIED,那么子View的大小是毫无拘束的,因此子View的mode也是UNSPECIFIED,这个时候计算子View自身的size是件毫无意义的事,因此源码中好像size设置为0了。
到这里这9种情况已经分析完毕,你大概了解了这个过程吗?能给面试官流畅的分析出来吗?
View的Measure过程:
接下来解释解释这几个方法:
在自定义View时,我们常常会利用以下两个方法:
ViewGroup的Measure过程:
注意:在描述这个过程之前,笔者有必要说明一下为什么会单独描述View和ViewGroup的Measure过程呢?这是因为它们之间的Measure过程肯定存在区别的,肯定不能一概而论,因为View只用测量自身就行了,而ViewGroup则不是,它不仅仅要测量自身,而且还有测量它的子View的责任。
接下来解释解释这几个方法:
Layout的目的就是确认View&ViewGroup的位置。也就是计算View&ViewGroup的四个顶点的位置,left,top,right,bottom。
在上面我们已经总结完有关Measure过程的知识点,Measure过程实际上只是确定了View&ViewGroup的大小,而接下来就是确定位置了,也就是Layout过程,在描述Layout过程之前,笔者有必要说一下,关于Android中有关坐标系的知识点,因为与View&ViewGroup位置相关的知识点必须要包括对于Android屏幕坐标系的理解。 请点击这里了解Android屏幕坐标系知识
View的Layout过程:
ViewGroup的Layout过程:
Draw过程的目的绘制View&ViewGroup的视图。
View的Draw过程:
ViewGroup的Draw过程:
此篇博客借助了以下链接的内容,由于笔者只针对面试,所以你要想详细了解View的绘制机制,那么请你点击以下链接进行学习: