[Android]《Android开发艺术探索》学习笔记-View的工作原理

一、初识ViewRoot和DecorView

ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程都是通过ViewRoot来完成的。在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView对象建立关联,这个过程如下:

root = new ViewRootImpl(view.getContext(),display);
root.setView(view,wparams,panelParentView);

View的绘制流程是从ViewRoot的performTraversals方法开始的,它经过measure、layout、draw后才将View绘制出来,其中measure用来测View的宽高,layout用来确定View在父容器的位置,draw用来绘制View到界面上。

performTraversals会依次调用performMeasure、performLayout和performDraw,分别完成顶级View的measure、layout、draw。其中在performMeasure会调用measure方法,在measure又会调用onMeasure方法,onMeasure方法会对所有子元素进行measure过程,这样measure过程就传到子元素中了,接着子元素会继续重复measure过程,这样完成了一个View树的遍历。Measure完成后,getMeasuredWidth/getMeasureHeight方法来获取View测量后的宽/高;Layout过程决定了View的四个顶点的坐标和实际View的宽高,完成后可通过getTop、getBotton、getLeft和getRight拿到View的四个定点坐标。

DecorView是顶级View,里边包含一个LinearLayout,有上下两部分,上面是titlebar,下面是一个内容栏,id是content,所以Activity中setContentView就是加载View到内容栏。我们可以通过findById(android.R.id.content)来找到这个contentView。

二、理解MeasureSpec

1、MeasureSpec

MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,而SpecSize是指在某种测量模式下的规格大小。MeasureSpec将这两个值打包成int,也提供了相应的解包。SpecMode有三类:

UNSPECIFIED:父容器不对View有任何的限制,一般用于系统内部,表示一种测量的状态。

EXACTLY:父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值,对应于LayoutParams中的match_parent和具体的数值这两种模式。

AT_MOST:父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同的View的具体实现,对应于LayoutParams中的wrap_content这种模式。

2、MeasureSpec和LayoutParams的对应关系

对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同确定;对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,MeasureSpec一旦确定后,onMeasure就可以确定View的测量宽高。

当View采用固定宽/高时,不管父容器的MeasureSpec是什么,View的MeasureSpec都是EXACTLY模式,并且大小遵循我们设置的值;当View的宽/高是match_parent时,View的MeasureSpec都是EXACTLY模式并且其大小等于父容器的剩余空间;当View的宽/高是wrap_content时,View的MeasureSpec都是AT_MOST模式并且其大小不能超过父容器的剩余空间。

三、View的工作流程

1、measure过程

(1)View的measure过程

直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content(即specMode是AT_MOST模式)时的自身大小,否则在布局中使用wrap_content相当于使用match_parent。

(2)ViewGroup的measure过程

ViewGroup是一个抽象类,没有重写View的onMeasure方法,但是它提供了一个measureChildren方法。measureChildren方法的流程:取出子View的LayoutParams,然后通过getChildMeasureSpec方法来创建子元素的MeasureSpec,最后将MeasureSpec直接传递给View的measure方法来进行测量。

(3)获取View的宽高

-Activity/View#onWindowFocusChanged:View已经准备好,可以去获取宽高了。

-view.post(runnable):post一个消息到消息队列底部,等Looper到它时View也准备好了。

-ViewTreeObserver:使用ViewTreeObserver的众多回调可以完成这个功能。

-view.measure(int widthMeasureSpec,int heightMeasureSpec):

    -match_parent: 无法measure出具体的宽高,因为不知道父容器的剩余空间。

    -具体的数值(dp/px):

 int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
 int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
 view.measure(widthMeasureSpec,heightMeasureSpec);

-wrap_content:View的尺寸使用30位二进制表示,最大值30个1,在AT_MOST模式下,我们用View理论上能支持的最大值去构造MeasureSpec是合理的

 int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT_MOST);
 int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT_MOST);
 view.measure(widthMeasureSpec,heightMeasureSpec);

2、layout过程

layout的作用是ViewGroup用来确定子View的位置,当ViewGroup的位置被确定后,它会在onLayout中遍历所有的子View并调用其layout方法,在layout方法中,onLayout方法又会被调用。流程如下:

(1)setFrame确定View四个顶点的位置。

(2)onLayout确定子容器位置。

3、draw过程

(1)绘制背景(canvas)

(2)绘制自己(onDraw)

(3)绘制children(dispatchDraw)

(4)绘制装饰(onDrawScrollBars)

 

四、自定义View

1、自定义View的分类

(1)继承View重写onDraw方法:可用来实现一些不规则的效果,但需要注意的是需要自己支持wrap_content,处理padding。

(2)继承ViewGroup派生特殊的Layout:可用来实现自定义布局,要注意处理ViewGroup的测量、布局两个过程以及子元素的测量和布局。

(3)继承特定的View(比如TextView):扩展已有的某种View,不需要自己支持wrap_content、padding。

(4)继承特定的ViewGroup(比如LinearLayout):与2的区别在于不需要自己处理ViewGroup的测量、布局两个过程。

2、自定义View须知

(1)让View支持wrap_content

(2)如果有必要,让你的View支持padding

(3)尽量不要在View中使用Handler,没必要(view本身就有post方法)

(4)View中如果有线程或动画,需要及时停止,参考View#onDetachedFromWindow

(5)View带有滑动嵌套情形时,需要处理好滑动冲突

发表评论

电子邮件地址不会被公开。 必填项已用*标注