一、view绘制流程
view树 递归
measure -> layout -> draw
ViewRootImpl是windowManager和DecorView的纽带
View的绘制是从ViewRootImpl的performTranslate方法开始的,经过measure、layout、draw三个过程才绘制出来的。
measure用来测量view的宽高,layout是用来确定view在父容器中的位置,draw负责将view画在屏幕上
MeasureSpec :
测量规格 是由父容器的MeasureSpec和view的LayoutParams确定的。
本身是一个32位的int值,包含mode和在mode(模式)下的size,其中高2位代表SpecMode 低30位代表SpecSize
SpecMode有三个值:
MeasureSpec.EXCTLY 精确值 对应match_parent和具体的数值
MeasureSpec.AT_MOST 可用大小 取决于view的具体实现 但最大不能超过父容器的大小 对应wrap_parent
MeasureSpec.UNSPECIFIED 不确定值,一般用于系统内部
二、measure
分为两部分view的测量和viewgroup的测量
1、viewgroup测量:
由于viewgroup是一个抽象类,所以其测量过程的OnMeasure方法需要各个子类去实现,比如LinearLayout、FrameLayout等。这里就以FrameLayout为例来介绍viewgroup绘制的源码分析:
<1> 在FrameLayout的onMeasure方法中会循环所有的子view调用measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0)方法
<2> 在measureChildWithMargins方法中,通过调用child.getLayoutParams()获取到子view的layoutParams。
然后使用传递过来的widthMeasureSpec(即父widthMeasureSpec)以及子view的layoutParams的数值作为参数调用getChildMeasureSpec方法得到子view的MeasureSpec
<3> 之后调用child.measure(childWidthMeasureSpec, childHeightMeasureSpec)方法来测量子view的大小
<4> child.measure方法是final类型,所以不能被复写 measure方法会调用view的onMeasure方法
<5> 在使用自定义view的时候就可以复写onMeasure方法,使用传递过来的WidthMeasureSpec来确定view的测量大小,最后调用setMeasureDimension设置view的大小
<6> view默认会在onMeasure方法中提供一个view的大小,使用getDefaultSize方法getDefaultSize方法有两个参数,其中size通过getSuggestedMinimumWidth方法获取,指的是通过设置android:minWidth的大小或者大小
在getDefaultSize方法的实现中可以看到:
view的MeasureMode如果等于AT_MOST或EXACTLY的时候view的大小就是specSize
view的MeasureMode如果等于UNSPECIFIED的话view的大小就等于size(即view建议的最小值)
<7> 在所有的子view测量完成后FrameLayout经过一些列的计算调用setMeasuredDimension完成自身的测量
这样整个view树就通过递归的方式测量完成了
2、view的测量:
view的测量就相对简单了,只需要在onMeasure方法来完成view的测量
注意:view的measure过程和activity的生命周期不是同步的,所以无法保证在activity的onCreate、onStart、onResume方法中通过调用view.getMeasureWidth()和view.getMeasureHeight()获取到view的宽高。可以在Activity中onWindowFocusChanged、View.post(runnable)等方法中获取
三、layout
layout是viewgroup来确定子元素的位置的
<1> 在performMeasure测量结束后,ViewRootImpl会调用performLayout方法调用mView.layout方法 这里的mView(mView是个ViewGroup)
<2> 进入ViewGroup的layout方法中会调用super.layout(l, t, r, b)方法(即view的layout方法)
<3> 在view的layout方法中会调用setFrame(l, t, r, b)给mLeft 、mTop、mRight、mBottom赋值,然后基本就能确定View自己在父视图的位置了,这几个值构成的矩形区域就是该View显示的位置,这里的具体位置都是相对与父视图的位置。
然后会调用onLayout方法,对于view来说是一个空方法不需要去实现,对于ViewGroup来说是abstract抽象方法,需要去重写确定子view的位置。
onLayout的实现与具体的布局有关,所以还是以FrameLayout为例来说
<4> 在FrameLayout的onLayout方法中会去遍历所有的子view,然后调用 child.layout(l, t, r, b) ,给子view 通过setFrame(l, t, r, b) 确定位置
getMeasureWidth和getWidth的区别:
getMeasureHeight是获取一个View的测量宽高,是在onMeasure方法中赋值的,代表view原始的大小,也就是这个view在XML文件中配置或者是代码中设置的大小。
getHeight是获取View的真实宽高,是在layout完后在setFrame()中赋值的,view最终显示的大小,这个大小有可能等于原始的大小也有可能不等于原始大小。
一般情况下这两个值时相同的,除非是在重写layout阶段改变left,right的值
四、draw
draw的作用是将view绘制到屏幕上。
performTraversals 方法的在调用完performLayout调用performDraw方法,调用mView.draw(canvas)方法
draw(Canvas canvas)方法中一般包括:
<1> drawBackground(canvas);绘制背景
<2> onDraw(canvas) 绘制自己
<3> dispatchDraw(canvas) 绘制children
<4> onDrawForeground(canvas);绘制装饰
<5> drawDefaultFocusHighlight(canvas)
dispatchDraw方法:
view的绘制是通过dispatchDraw来传递给子view的,在该方法中会遍历所有的子view调用draw方法。由于单一View无子View,故View 中默认为空实现。
ViewGroup中系统已经复写好对其子视图进行绘制我们不需要复写
invalidate()和requestLayout()方法的区别:
invalidate:调用时请求系统重新绘制会触发draw过程,当视图大小没有发生变化时就不会调用layout过程
requestLayout:当布局发生变化时需要去调用该方法,会去触发measure和layout过程,但不会去触发draw过程
进行View更新时,若仅View的显示内容发生改变且新显示内容不影响View的大小、位置,则只需调用invalidate方法;
若View宽高、位置发生改变且显示内容不变,只需调用requestLayout方法;
若两者均发生改变,则需调用两者,按照View的绘制流程,推荐先调用requestLayout方法再调用invalidate方法。