项目请参考 LaybelLayout
在开发当中,我们或多或少的可能会用到一种类似于标签一样的布局,如下图所示。
在SDK自带的控件当中,我们很难实现这样一种效果。如果根据显示内容使用LinearLayout
去动态生成的化,又显得很繁琐,而且很难移植到别的项目里去。于是就实现了这么一个布局Layout.
大致思路
一,子控件的测量和摆放
首先我们是直接继承了ViewGroup
.众所周知,要自定义一个布局,必须要重写ViewGroup
的onLayout()
方法,在这个方法里面,对子控件进行布局的摆放。但是在摆放前,咱们得先给这些子控件进行一个测量,知道它们自己需要多大空间。所以在onMeasure()
方法里面,对这些子控件进行测量。因为要支持margin
外边界吧,所以ViewGroup
最原始的那个LayoutParams
就不能使用啦,咱们得用MarginLayoutParams
。下面给出测量思路:
1,先说说宽度问题,测出每个子控件的宽度,然后每个控件占的宽度是 自身宽度 + margin_left + margin_right.
2,每一行的高度就等于 自身高度 + margin_top + margin_bottom + line_padding × 2,其中 ‘line_padding’ 是我定义的一个属性
3,测量时候,如果发现有控件占领宽度加上LaybelLayout
自padding
大于了LaybelLayout
的宽度,则让它刚好等于 LaybelLayout宽度 - 左右padding
4,测量完成之后,遍历子控件,并且将它们的摆放信息存到ChildLayoutMsg
这个内部类里面,这样在待会onLayout()
里面只需要将信息提取出来,进行摆放就可以了。
5,记录摆放的信息,也就是记录每个子控件的left, top, right, bottom
.也是根据每一行的剩余宽度来判断,如果控件占的宽度加上父控件的padding大于了剩余宽度,则需要摆放到下一行二,LaybelLayout本身的宽高配置
因为要实现wrap_content
属性,这就需要得到本控件的最小宽高。需要知道最小宽度,则需要只知道LaybelLayout的父控件给LaybelLayout分配了多大空间。!= = 这就需要用MeasureSpec
把本控件的宽高的mode
取出,不知道mode干嘛用的请自行google
如果mode是EXACTLY
,也就是LaybelLayout的父控件给定了宽度,这种情况就用默认的super.onMeasure(widthMeasureSpec, heightMeasureSpec)
进行设置宽高就行了。现在主要应对不确定宽度的情况,也就是判断宽和高的mode不等于EXACTLY
,把最小宽高通过setMeasuredDimension()
设置进去。这里还有个问题,当最小宽高超过了LaybelLayout的父控件给的最大限度时,咋办呢,是不是又要去判断下?其实有个api帮我们实现了这个功能,我们只需要将最小宽高放进去,就会返回一个合适的宽高回来,就是使用resolveSize()
了。
下面来看看resolveSize()
都干了什么
public static int resolveSize(int size, int measureSpec) {
return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK;
}
发现它调用了resolveSizeAndState()
,于是跟进去看看
public static int resolveSizeAndState(int size, int measureSpec,
int childMeasuredState) {
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
final int result;
switch (specMode) {
case MeasureSpec.AT_MOST:
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
第三个参数先不用管,这里用宽度来说明,传入了最小宽度和测量宽度的Spec。记住,这个最小宽度是与LaybelLayout的测量宽度有关的,把每一行的宽度进行比较,取最大的那个宽度作为本控件的最小宽度。虽然在本控件里面,最小宽度的计算方式已经处理了这个问题,但是任然用这个函数来包裹一层。可以看到,如果是父控件给定了确定宽度EXACTLY
,则完全按照测量的宽度来作为本控件的宽度;如果父控件对本控件没有加以限制UNSPECIFIED
,表示我这个控件需要多大,就得多大,大小完全自己决定,于是返回我传入的size(最小宽度);如果父控件给定了个范围AT_MOST
,则判断下,测量值和传入的size哪个小,就返回哪个,这样的好处就是,绝对不会超出父控件的范围。
总结
上面讲得确实有点啰嗦,但是为了力求准确,也只好这样了。如有错误,请各位看客指出,在下感激不尽。具体源码和Demo,请前往 github