1. ViewRoot和DecorView
ViewRoot对应于ViewRootImpl类,它连接WindowManager和DecorView。View的绘制流程从ViewRoot的performTraversals开始,经过measure,layout和draw过程。
Measure决定了View 的宽和高,通过getMeasuredWidth和getMeasuredHeight来获得;
Layout决定了四个顶点的坐标和实际的View宽高,之后可调用getTop, getBottom, getLeft, getRight, getWidth和getHeight;
Draw决定了View的显示。
2. MeasureSpec
在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,得出View的测量宽高。
MeasureSpec代表一个32位的int值,高2位代表SpecMode,低30位代表SpecSize。
SpecMode取值为EXACTLY,对应LayoutParams的match_parent和具体数值两种方式,表示View的最终大小就是父容器SpecSize所指定的值;
AT_MOST,对应LayoutParams的wrap_content,表示父容器制定了一个SpecSize,View不能大于它。
子元素的MeasureSpec的创建与父容器的MeasureSpec和子元素本身的LayoutParams有关,此外还和View的margin以及padding有关。
如果View为固定宽高,则View的SpecMode都是EXACTLY并且大小为LayoutParams中的大小;
如果View为match_parent,则View的SpecMode与父容器相同,SpecSize也为parentSize;
如果View为wrap_content,则View的SpecMode都是AT_MOST,并且SpecSize为parentSize。
3. View的onMeasure
setMeasureDimension设置宽高的测量值,getDefaultSize返回measureSpec中的specSize,getSuggestedMinimumWidth如果View没有设置背景则返回android:minWidth,否则返回minWidth和背景最小宽度中最小的。
直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则默认的wrap_content就相当于match_parent。
重写onMeasure:在AT_MOST的情况下,将尺寸设置为自己内部算好的尺寸。
4. ViewGroup的measure
除了完成自己的measure,还会便利调用所有子元素的measure方法。measureChildren方法,对非GONE的子View执行measureChild。
measureChild:根据父节点的MeasureSpec,结合子节点的LayoutParams,创建子节点的MeasureSpec,传入child.measure方法。
某些极端情况下,需要多次measure才能确定最终测量宽高,所以应在onLayout中获取View 的测量宽高或者最终宽高。
以LinearLayout的measureVertical(widthMeasureSpec, heightMeasureSpec)为例,计算每个子元素的初步高度,再累加;
如果它的布局中高度采用的是match_parent或者具体数值,则高度即为specSize,如果采用了wrap_content则高度是累加值但是不能超过父容器的空间剩余;
5. 四种方式获取View的尺寸
View的measure过程和Activity的生命周期方法不是同步执行的,所以无法保证在onCreate, onStart, onResume时某个View已经测量完毕。
1)onWindowFocusChanged,View初始化完毕,宽高已经准备好,在Activity的窗口得到焦点和失去时均会被调用;
2)view.post(runnable),通过post将一个runnable投递到消息队列的队尾,然后等待Looper调用;
3)ViewTreeObserver的OnGlobalLayoutListener接口,view树的状态发生改变或者内部View可见性发生改变;
4)view.measure
int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30) - 1, MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec, heightMeasureSpec);
6. layout过程
layout的大致流程:首先通过setFrame来设定View的四个顶点位置,接着调用onLayout确定子元素的位置;
以LinerLayout的onLayout中的layoutVertical为例,遍历所有子元素并调用setChildFrame设置子元素的位置,top逐渐增大,setChildFrame内部又调用了child.layout;
测量宽高形成于View的measure过程,最终宽高形成于layout过程。
7. draw过程
1)绘制背景background.draw(canvas)
2)绘制自己onDraw(canvas)
3)绘制子元素dispatchDraw(canvas)
4)绘制装饰onDrawScrollBars(canvas)
8. 自定义View方式
1)继承View重写onDraw:实现一些不规则的图形,要自己支持wrap_content,并且padding也需要自己处理;
2)继承ViewGroup派生特殊的Layout:处理ViewGroup和子元素的测量和布局两个过程;
3)继承特定的View:例如TextView;
4)继承特定的ViewGroup:例如LinerLayout
9. 自定义View须知
1)支持wrap_content
2)直接继承View的控件,需要在draw方法中处理padding,直接继承ViewGroup的控件需要在onMeasure和onLayout中考虑padding和子元素的margin;
3)尽量不要在View中使用Handler,因为View本身支持post方法;
4)View中有线程或者动画:可以在onAttachedToWindow中开启,在onDetachedFromWindow时停止和关闭;
5)滑动嵌套时要解决滑动冲突;
10. 自定义属性
1)在values目录下创建attrs_xxxx.xml,声明一个自定义属性集合,在其中定义属性名和格式;
<resources>
<declare-styleable name="CircleView">
<attr name="circle_color" format="color" />
</declare-styleable>
</resources>
2)在View的构造方法中解析自定义属性的值
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);
a.recycle();
init();
}
3)在布局文件中使用自定义属性
xmlns:app="http://schemas.android.com/apk/res-auto"
app:circle_color="@color/light_green"
此文章转载http://blog.csdn.net/doom20082004/article/details/53487597