View的工作原理,也就是View的measure,layout,draw三个过程,为了巩固学习,写代码实现“等分布局”,下面就介绍“等分布局”的实现,不过之前,需要重新学习一下View的三个过程。
View大致工作流程
View的绘制流程,从RootView的performTraversals开始,经过measure,layout,draw三个过程才将View绘制而出。measure用于测量View的大小,layout用于定位View在父容器中的放置相对位置,draw方法则是将View绘制在屏幕上。
performTraversals依次调用performMeasure,performLayout和performDraw,他们就是完成三个过程,performMeasuer会调用measure方法,measure又调用onMeasure,onMeasuer对所有的子元素进行measure过程,子元素又调用onMeasure又测量所有的子元素,直到所有的View测量完。(layout和draw也是如此)
View的测量
View的测量分为两种,DecorView(顶级View)和普通View,DecorView的测量只能根据自己属性和窗口的大小决定,而普通的View则是根据自己的属性和父容器的大小决定(LayoutParams和父容器的MeasureSpec决定)。
MeasureSpec
在测量的过程中,MesureSpec决定了一个View的尺寸规格,它是View大小的一个决定性因数,系统就是根据View的MeasureSpec来决定一个View的测量大小。 它本身是一个32位int值,高2位代表SpecMode,低30位代表SpecSize。 SpecMode有3中模式:
(1) UNSPECIFIED : 父容器不对View有任何限制
(2) EXACTLY : View具有精确大小 (width=10dp height=10dp)
(3) AT_MOST : 不能超过父容器的大小
关于如何确定自己的MeasureSpec这里就不说了,主要看一下getChildMeasureSpec源码就行,有一个对应关系
Measure过程
一般来说,都是由ViewGroup开始,我们看ViewGroup方法没有measure方法,但是ViewGroup继承自View,看出来measure是final类型,不可以复写
1
2
3
4
5
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
调用onMeasure来最终确定自己的大小
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
...
如果View的没有设置背景就是 android:minWidth指定值,有背景就是他们之间的最大值,setMesauredDimension就是确定了自己测量值
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
.....
UNSPECIFIED模式就是 result值(无限制)
其他就是messureSpec的值
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
上面几个步骤就最终完成了自己的测量,但是ViewGroup还需要对于child进行测量,measureChildren方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
循环调用子child
...
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);
}
过渡到了child的measure方法,其中getChildMeasureSpec就是根据child的layoutparams和父类的MesaureSpec来决定子View的MeasureSpec,然后又去调用onMeasure()方法
layout过程
Layout的作用是确定自己的位置,layout中又会调用onLayout方法去调用自己子类的layout方法去确定子类View的方法,onlayout是抽象方法需要自己实现
1
2
3
4
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
child.layout(width*i,0,width*i+child.getMeasuredWidth(),child.getMeasuredHeight());
draw过程
Draw将View绘制到屏幕上面,绘制步骤
(1) 绘制背景 background.draw(canvas)
(2) 绘制自己 (onDraw)
(3) 绘制children(dispatchDraw)
(4) 绘制装饰(onDrawScrollBars)
如何实现等分布局呢??
首先要确定子View的大小,测量的时候确定View等分,在ViewGroup决定子View测量的是measurechild函数,默认match_parent和warp_content 子View的大小就是parentSzie我们修改一下传入等分的parentSpec,然后就是定位,修改onLayout方法,在里面传入等分坐标就能够实现等分
效果图如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class three_equal_layout extends ViewGroup {
public three_equal_layout(Context context) {
super(context);
}
public three_equal_layout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public three_equal_layout(Context context, AttributeSet attrs) {
super(context, attrs,0);
}
//修改measureChildren方法
//获取MeasureSpec 使用makeMeasureSpec方法
@Override
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
int count=getChildCount();
Log.e("Layout","子view的数量为"+count);
Log.e("Layout","测量的高度为"+MeasureSpec.getSize(heightMeasureSpec));
widthMeasureSpec=MeasureSpec.makeMeasureSpec((MeasureSpec.getSize(widthMeasureSpec))/count,MeasureSpec.getMode(widthMeasureSpec));
super.measureChildren(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec,heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count =getChildCount();
Log.e("Layout","子view的数量为"+count);
int width=getMeasuredWidth()/count;
for(int i=0;i<count;i++){
final View child=getChildAt(i);
child.layout(width*i,0,width*i+child.getMeasuredWidth(),child.getMeasuredHeight());
}
}
}