基本概念
ViewRoot
对应ViewRootImpl类,是连接WindowManager和DecorView的纽带,View的三大流程由ViewRoot的performTraversals开始,依次调用performMeasure,performLayout,performDraw完成顶级View的三大绘制流程。
DecorView
顶级View,内部是竖直方向的LinearLayout,包含标题栏和内容栏(FrameLayout)
Window
对应实现是PhoneWindow,通过WindowManager创建一个Window,Android中视图(Activity,Dialog,Toast)都是通过Window呈现的。其中创建Activity和Dialog的Window必须采用Activity的Context,因为需要应用的token只有Activity类型Context才有,系统级Window如Toast的Window则不需要。
三大流程
ViewRootImpl 在其创建过程中通过 requestLayout() 向主线程发送了一条触发遍历操作的消息,遍历操作是指 performTraversals() 方法,performTraversals() 会依次调用 performMeasure、performLayout、performDraw 三个方法即顺序执行measure、layout、draw这三个流程。measure负责确定View的测量宽/高,也就是该View需要占用屏幕的大小,确定完View需要占用的屏幕大小后,就会通过layout确定View的最终宽/高和四个顶点在手机界面上的位置,等通过measure和layout过程确定了View的宽高和要显示的位置后,就会执行draw绘制View的内容到手机屏幕上
测量Measure
View的measure过程是通过measure方法实现的,该方法为final,其内部调用了onMeasure方法,所以我们自定义测量需要重写onMeasure。ViewGroup的measure除了测量自身,还要对每个子元素测量,所以提供了一个measureChildren方法,该方法综合考虑了子元素的LayoutParams和MeasureSpec如下所示。MeasureSpec由SpecSize和SpecMode组成,SpecMode有UNSPECIFIED,EXACTLY,AT_MOST三种,我们一般需要对AT_MOST(即设置wrap_content时)进行处理(指定一个默认宽高)。DecorView的MeasureSpec由窗口尺寸和自身LayoutParams确定,普通View的MeasureSpec由父容器的MeasureSpec和自身LayoutParams确定。
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
布局Layout
performTraversals方法接下来就会执行layout(int l, int t, int r, int b)方法,该方法根据已经测量出的View大小,确定View的四四个顶点的位置,并通过setFrame设定View的四个顶点位置,然后再调用onLayout方法(这时位置已经确定了),该方法为空方法,可以重写来实现View的布局特性。若是ViewGroup则相应的需要重写并遍历所有子元素的进行布局。通过重写onLayout自定义布局。
绘制Draw
再performDraw方法中会调用draw方法,首先t创建一个Canvas对象,然后由顶层View调用Draw(),并传入该Canvas。绘制步骤:绘制背景,保存画布,绘制自己(调用onDraw),绘制子View,绘制View边缘色,绘制装饰物(滚动条)。我们可以重写onDraw达到绘制不规则图像效果
自定义View的注意点
- 尽量支持wrap_content,padding
- 尽量不要在View中使用Handler,因为View自身的post方法可以取代
- 要在View的onDetachedFromWindow(View的Activity退出或View被remove会调用)内停止动画和线程,否则容易发生内存泄漏
- 处理好滑动冲突
参考