流式布局,一般在商城类的项目中用到会非常多比如
淘宝中,购物选择商品列表的时候,这个就是流式布局
创作起来也很简单,
只要你计算出宽度,和高度,如果超出屏幕宽度,则换行摆放即可
然后我就尝试着写了一下,果然还是可以的
效果图
核心方法主要是viewgroup的layout方法
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 为什么定义x,因为是从这个坐标开始出发
// 往右进行摆放
int x = (int) (this.getPaddingLeft() + leftMargin);
int paddingRight = this.getPaddingRight();
// 为什么定义y,因为从上往下,top等于y轴线
int y = (int) (this.getPaddingTop() + topMargin);
int sumWidth = r - l;
int childCount = this.getChildCount();
int childMaxHeight = 0;
for (int i = 0; i < childCount; i++) {
View view = this.getChildAt(i);
// 如果大于了。需要规整
if (sumWidth < x + view.getMeasuredWidth() + paddingRight) {
// 跨行
// 改变x轴起始点
x = (int) (getPaddingLeft() + leftMargin);
// 改变y轴起始点
y += childMaxHeight;
childMaxHeight = 0;
}
view.layout(x, y, x + view.getMeasuredWidth(), y + view.getMeasuredHeight());
// 改变横坐标,切记加入view的宽度.否则会出问题
x += view.getMeasuredWidth() + rightMargin;
// 取最大宽度,为下一步跨行做准备
childMaxHeight = (int) Math.max(childMaxHeight, view.getMeasuredHeight() + topMargin + bottomMargin);
}
}
其次就是测量方法了.测量方法需要测量出总大小来控制view的大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 获取子view的数量
int childCount = this.getChildCount();
// 获取到本view的宽度最大值
int maxWidth = MeasureSpec.getSize(widthMeasureSpec) - this.getPaddingLeft() - this.getPaddingRight();
// 需要测量view的宽度以及view的高度。
// 所有的合集
// 总高度
int sumHeight = 0;
// 一行的子类总高
int childMaxHeight = 0;
// 总宽度
int sumWidth = 0;
// 一行的子类总宽
int sumChildWidth = 0;
for (int i = 0; i < childCount; i++) {
View view = this.getChildAt(i);
measureChild(view, widthMeasureSpec, heightMeasureSpec);
// 如果有leftMargin的话,需要在测量的时候,加上这个
sumChildWidth = (int) (sumChildWidth + leftMargin + rightMargin);
// 如果小于两者相加,所以超了,需要计算高度
// 取高度最大值,也就是所有控件的最大值
// 加完之后要清除,否则下一行高度无法计算
if (maxWidth < (sumChildWidth + view.getMeasuredWidth())) {
// 跨行
sumHeight += childMaxHeight;
childMaxHeight = 0;
// 跟自己比较,获取最大值,优先取最大
sumWidth = Math.max(sumChildWidth, sumChildWidth);
sumChildWidth = 0;
}
// 判断子类高度最大值
childMaxHeight = (int) Math.max(childMaxHeight, view.getMeasuredHeight() + topMargin + bottomMargin);
// 取子类行总宽,需要判断父类的宽度
sumChildWidth += view.getMeasuredWidth();
}
// 因为最后一行可能没有超过,所以不会进入,则需要重新加一下最后一行
sumHeight += childMaxHeight;
sumWidth = Math.max(sumChildWidth, sumWidth);
setMeasuredDimension(measureWidth(widthMeasureSpec, sumWidth), measureHeight(heightMeasureSpec, sumHeight));
}
private int measureHeight(int heightMeasureSpec, int sumHeight) {
int result = 0;
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
//EXACTLY
//精确值模式,当控件的layout_width和layout_height属性指定为具体数值或match_parent时。
//AT_MOST
//最大值模式,当空间的宽高设置为wrap_content时。
//UNSPECIFIED
//未指定模式,View想多大就多大,通常在绘制自定义View时才会用。
// 如果为精确值模式,那么不用判断了,直接返回
if (mode == MeasureSpec.EXACTLY) {
result = size;
return result;
}
result = sumHeight + this.getPaddingTop() + this.getPaddingBottom();
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(size, result);
}
return result;
}
private int measureWidth(int widthMeasureSpec, int sumWidth) {
int result = 0;
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
// 如果为精确值模式,那么不用判断了,直接返回
if (mode == MeasureSpec.EXACTLY) {
result = size;
return result;
}
result = widthMeasureSpec + this.getPaddingLeft() + this.getPaddingRight();
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(size, result);
}
return result;
}
演示结果就更简单了
int[] colors = {
Color.DKGRAY,
Color.GRAY,
Color.LTGRAY,
Color.WHITE,
Color.RED,
Color.GREEN,
Color.BLUE,
Color.YELLOW,
Color.CYAN,
Color.MAGENTA,
Color.TRANSPARENT
};
FlowLayout viewById = (FlowLayout) findViewById(R.id.fl);
viewById.setItemMargin(10, 10, 10, 10);
for (int i = 0; i < 1000; i++) {
TextView textView = new TextView(MainActivity.this);
textView.setText("我是条目 " + i);
int i1 = new Random().nextInt(colors.length);
textView.setBackgroundColor(colors[i1]);
viewById.addView(textView);
}
详细可以移步我的github