上一篇中其实对测量讲解的比较清楚了,对布局没有说。不过对于 viewGroup
而言,测量完成了,也就意味着着,知道怎么布局了。
其实知道了怎么测量和布局就可以实现很多沙雕的控件了。比如流布局。当然了,如果想提供很方便的调用方式,还需要搞一些便捷的自定义属性才行。
这里展示一下,通过测量和布局实现的沙雕布局。
通过效果图可以看出来,这就是流布局了。而且带两种效果:一种就是普通的流布局,一行一行的排列;然后第二种是竖直排列的效果,是一列一列的排列。
当然了,这两种的代码差别其实不大,主要是 在测量和布局的时候,要计算好每个 item 应该占据的尺寸和位置。
大致说一下原理。
比如这种横向排列的流布局。在测量的时候,首先肯定也是测量每个 child
的宽高。然后需要判断,当前这个 child
是挨着上一个的后面继续排,还是换行。然后对于布局,也是一样的,如果不换行,那么当前这个 child
一定是排在上一个的后面,就要计算好当前的child
的l,t,r,p
(上下左右四个顶点)对应的位置距离。如果换行,也是要计算这4个值。只是计算的逻辑有区别。
核心逻辑,关于计算和判断是否需要换行的代码就是下面这几行:
if (lineWidth + itemW > ws - getPaddingLeft() - getPaddingRight()) {
// 换行 ,先记录换行之前的数据
width = Math.max(width, lineWidth); // 记录的是上一行的数据
height += lineHeight; // 记录的是上一行的数据
// 换行了
lineHeight = itemH;
lineWidth = itemW;
} else {
// 不换行
lineHeight = Math.max(lineHeight, itemH);
lineWidth += itemW;
}
if (i == count - 1) {
// 最后一个 view , 记录这一行的数据
width = Math.max(width, lineWidth); // 记录的是这一行的数据
height += lineHeight; // 记录这一行的数据
}
// width/height 是 将要设置给自己(当前自定义的这个ViewGroup)的宽度/高度
看起来代码很少,而且也容易理解。但是,我要说的是,就是这里的逻辑搞得我头大,我之前想了很久,代码写的比这里复杂多了,但是没有实现想要的效果。(就是可以继续挨着就挨着,否则换行的效果)
实在没有想到要怎么实现这个逻辑,然后查了一些资料,终于写成上面这样的代码了。(可以说上面的这几行代码并非原创。)
要特别注意当前计算出来的值,是当前行的数据,还是上一行的数据。一定要计算出当前行的。
然后要注意对
child
支持padding / margin
的设置支持。(也是在测量和布局的时候做的逻辑)
彩蛋来了:完整代码,star me
给 ViewGroup
设置 divider
, 很多系统的容器的控件都提供了设置分割线的属性或者方法。比如LinearLayout / ListView
等。辣么,这都是怎么实现的呢?
其实分割线并不是一条线,当然理论上可以是一条线。不过一般系统控件都是通过
Drawable
去实现的。
通过查看系统控件 LinearLayout
的源码可以大致知道其实现方式:
- 首先需要让
ViewGroup
能够触发onDraw
方法。[该方法在ViewGroup
中默认不触发]
setWillNotDraw(!isShowingDividers());
requestLayout();
- 然后在测量的时候,要把
divider
的宽高也计算进去。 - 在布局的时候,也是要让出
divider
的位置。 - 在 绘制的时候,在对应的位置上面进行绘制即可。
实现一个类似竖直排列的线性布局 的 分割线
@Override
protected void onDraw(Canvas canvas) {
int t = getPaddingTop();
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int l = lp.leftMargin + getPaddingLeft();
t = t + lp.topMargin;
int r = l + child.getMeasuredWidth();
int b = t + child.getMeasuredHeight();
// child.layout(l, t, r, b);
t += child.getMeasuredHeight() + lp.bottomMargin + dividerHeight;
if (i < getChildCount() - 1) {
mDivider.setBounds(getPaddingLeft(), b + lp.bottomMargin, getRight()-getPaddingRight(), b + lp.bottomMargin + dividerHeight);
mDivider.draw(canvas);
}
}
}
其实绘制的关键代码只有两行:
// LinearLayout.java 中的源码
void drawHorizontalDivider(Canvas canvas, int top) {
mDivider.setBounds(getPaddingLeft() + mDividerPadding, top,
getWidth() - getPaddingRight() - mDividerPadding, top + mDividerHeight);
mDivider.draw(canvas);
}