流布局的核心思想就是把每一行看做一个对象,给其动态添加子view,通过计算添加View的宽的总和与行宽比较,从而继续添加或者换行显示。
自定义FlowLayout:
<span style="font-size:14px;">package com.example.mydemo;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
/**
* 排行的布局
*
* @author wkk
*
*/
public class FlowLayout extends ViewGroup {
/**
* 横向控件间隔
*/
private static final int HORIZONTALSPECING = 26;
/**
* 竖向控件的间隔
*/
private static final int VERTICALSPECING = 26;
/**
* 行对象的集合
*/
private List<Line> lines = new ArrayList<FlowLayout.Line>();
/**
* 当前行
*/
private Line currentLine;
/**
* 当前行使用的宽度
*/
private int useWidth;
public FlowLayout(Context context) {
super(context);
}
public FlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* 测量的时候调用
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 每次重新测量的时候重置
lines.clear();
currentLine = null;
useWidth = 0;
// 获取当前 控件 的 模式 +size
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
int heightSize = MeasureSpec.getSize(heightMeasureSpec) - getPaddingBottom() - getPaddingTop();
int childWidthMode;
int childHeightMode;
if (widthMode == MeasureSpec.EXACTLY) {
// 如果当前控件是精确值
childWidthMode = MeasureSpec.AT_MOST;
} else {
// 其他的模式 就和父控件 保持一直
childWidthMode = widthMode;
}
if (heightMode == MeasureSpec.EXACTLY) {
childHeightMode = MeasureSpec.AT_MOST;
} else {
childHeightMode = heightMode;
}
// 重新做测量规范
int childWidthMeasureSpec =
MeasureSpec.makeMeasureSpec(childWidthMode, widthSize + getPaddingLeft()
+ getPaddingRight());
int childHeightMeasureSpec =
MeasureSpec.makeMeasureSpec(childHeightMode, heightSize + getPaddingBottom()
+ getPaddingTop());
// 重新生成行
currentLine = new Line();
// 获取子view 的数量
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
// 获取v子iew
View child = getChildAt(i);
// 测量子view
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
// 获取子view 实际的 宽度
int childWidth = child.getMeasuredWidth();
// 获取子view 的实际高度
int childHeight = child.getMeasuredHeight();
useWidth += childWidth;
// 如果加上当前子view 小于父控件宽度
if (useWidth <= widthSize) {
currentLine.addChild(child);// 添加当前ziview
useWidth += HORIZONTALSPECING;
// 加上空格后 变得放不下去了
if (useWidth > widthSize) {
// 换行
newLine();
}
} else {
// 换行
newLine();
// TODO 一定要加上
// 换行后的第一个元素的宽度要重新加上
useWidth += childWidth;
// currentLine 是新的一行
currentLine.addChild(child);
}
}
// 添加最后一行
if (!lines.contains(currentLine)) {
lines.add(currentLine);
}
// 父控件的总高度
int totalHeight = 0;
// 计算父控件的高度
for (int i = 0; i < lines.size(); i++) {
Line line = lines.get(i);
totalHeight += line.getHeight();
totalHeight += VERTICALSPECING;
}
totalHeight += getPaddingBottom();
totalHeight += getPaddingTop();
/**
* When overriding this method, you * <em>must</em> call {@link #setMeasuredDimension(int, int)}
* to store the measured width and height of this view.
*/
// resolveSize 参数1 实际大小 参数2:测量规则 谁大用谁
setMeasuredDimension(widthSize + getPaddingLeft() + getPaddingRight(),
resolveSize(totalHeight, heightMeasureSpec));
}
/**
* 换行操作
*/
private void newLine() {
lines.add(currentLine);
currentLine = new Line();
useWidth = 0;
}
/**
* 放置子view
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
l += getPaddingLeft();
t += getPaddingTop();
// 先放 行
for (int i = 0; i < lines.size(); i++) {
Line line = lines.get(i);
line.layout(l, t);
// 重新计算下一行的 坐标
t += line.getHeight();
t += VERTICALSPECING;
}
}
/**
* 将每一行看做一个对象 以方便设置其宽高
*/
private class Line {
/**
* 每一行子view的集合
*/
private List<View> children = new ArrayList<View>();
/**
* 行高
*/
private int height;
/**
* 每行view的总宽
*/
private int realWidth = 0;
/**
* 给行添加子View
*
* @param child
*/
public void addChild(View child) {
// 添加到行子view集合中
children.add(child);
// 拿到最高的高度作为行高
if (child.getMeasuredHeight() > height) {
height = child.getMeasuredHeight();
}
// 每个子view的宽度相加
realWidth += child.getMeasuredWidth();
}
/**
* 获取每行高度
*
* @return
*/
public int getHeight() {
return height;
}
/**
* 放置行的位置
*
* @param l
* @param t
*/
public void layout(int l, int t) {
// 每一行空白的长度和
int ramaindWidth =
getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - realWidth - HORIZONTALSPECING
* (children.size() - 1);
// 将空白的长度平均作为每个view的间距
int ramind = 0;
if (children.size() > 0) {
ramind = ramaindWidth / children.size();
}
// 通过遍历设置子view的位置
for (int i = 0; i < children.size(); i++) {
View child = children.get(i);
child.layout(l, t, l + child.getMeasuredWidth() + ramind, t + child.getMeasuredHeight());
l += child.getMeasuredWidth() + ramind + HORIZONTALSPECING;
}
}
}
}</span>
XML文件直接使用:
<span style="font-size:14px;"> <com.example.mydemo.FlowLayout
android:id="@+id/fl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp" >
</com.example.mydemo.FlowLayout></span>
Activity中使用:
<span style="font-size:14px;">package com.example.mydemo;
import java.util.ArrayList;
import java.util.Random;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.TextView;
import android.widget.Toast;
public class FlowActivity extends Activity {
private ArrayList<String> datas;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_flow);
fillData();
addView();
}
private void fillData() {
datas = new ArrayList<>();
for (int i = 0; i < 20; i++) {
datas.add("控件" + i);
}
}
/**
* 自设假数据
*/
private void addView() {
FlowLayout layout = (FlowLayout) findViewById(R.id.fl);
// 设置方向
TextView tv;
int backBg = 0xffcecece;
Random random = new Random();
for (int i = 0; i < datas.size(); i++) {
String text = datas.get(i);
if (i % 2 == 0) {
text += "hahahahaha";
}
tv = new TextView(this);
tv.setText(text);
tv.setTextColor(Color.WHITE);
tv.setPadding(7, 4, 7, 4);
// 为区分加的颜色 实际中可不需要
int red = random.nextInt(200) + 22;
int green = random.nextInt(200) + 22;
int blue = random.nextInt(200) + 22;
int color = Color.rgb(red, green, blue);
tv.setBackgroundDrawable(DrawableUtils.createSelector(DrawableUtils.createShapre(color),
DrawableUtils.createShapre(backBg)));
final int index = i;
tv.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(FlowActivity.this, datas.get(index), Toast.LENGTH_SHORT).show();
}
});
layout.addView(tv, new LayoutParams(LayoutParams.WRAP_CONTENT, -2));
}
}
}</span>
基本实现功能,有点粗糙,先行记录,后续优化。