这篇博客主要是介绍自定义流式布局-FlowLayout
其实在github上面有好多这样的优秀的实现。
但是还发博客一方面是自己的学习,还有一方面希望把原理给大家简单说一下,共同进步共同学习吧!
通过继承ViewGroup实现,博客参考了 鸿洋大神-FlowLayout
核心代码在onMeasure和onLayout里面
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 得到宽高的测量值和测量模式
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
// wrap_content 测量模式下的当前布局宽度和高度
int width = 0;
int height = 0;
int lineWidth = 0; // 每一行的宽度
int lineHeight = 0; // 每一行的高度
// 重置索引
index = 0;
// 清空数据
views.clear();
lineViews.clear();
lineHeights.clear();
// 遍历子view
for (int i = 0; i < getChildCount(); i++) {
// 得到当前索引下的子view
View childView = getChildAt(i);
// View.GONE 该模式下子view不可见不占据空间不需要计算
if (childView.getVisibility() == View.GONE) {
continue;
}
// 测量子view
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
// 得到子view占据的宽高
int childWidth = childView.getMeasuredWidth();
int childHeight = childView.getMeasuredHeight();
// 判断是否换行
if (lineWidth + childWidth > widthSize - getPaddingLeft() - getPaddingRight()) {
// 换行
lineWidth = lineWidth - weightLevelSpace; // 减去最后多算的一个控件间距
width = Math.max(lineWidth, childWidth); // 设置布局宽为最大的行宽 (换行之前的所有行)
lineWidth = childWidth + weightLevelSpace; // 重置行宽(当前行)
height += lineHeight; //叠加行高(上一行)
views.put(index++, lineViews); // 添加上一行的view
lineViews = new ArrayList<>(); // 新建一个集合存放每一行的子view 不可以用clear
lineViews.add(childView); // 添加当前行的第一个view
lineHeights.add(lineHeight); //添加上一行的行高
lineHeight = childHeight; // 设置当前的行高为第一个子view的高度
} else {
// 不换行
lineWidth += (childWidth + weightLevelSpace); // 叠加(控件间距)得到当前的行宽 控件间距
lineHeight = Math.max(lineHeight, childHeight); // 当前行最大子view高度设置为当前的行高
lineViews.add(childView); // 添加view
}
// 处理最后一个控件的时候尺寸的计算
if (i == getChildCount() - 1) {
lineWidth -= weightLevelSpace;
width = Math.max(lineWidth, childWidth); // 设置布局宽为最大的行宽
height += lineHeight; //叠加行高(当前行)
/*
* 思考
* 如果最后一个子view需要换行,这height 叠加了上一行的行高,width计算的是上一行之前所有行
* 没有对当前行进行计算
* 如果最后一个子view不需要换行,height 没有叠加当前行行高,width没有计算当前行的行宽
* 所以需要处理最后一个子view
* */
}
}
// 添加当前行
views.put(index, lineViews); // 添加当前行的view
lineHeights.add(lineHeight); // 添加当前行的行高
height += (index) * lineSpace; // 添加行间距
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ?
widthSize : width + getPaddingRight() + getPaddingLeft()
, heightMode == MeasureSpec.EXACTLY ?
heightSize + (index) * lineSpace : height + getPaddingBottom() + getPaddingTop());
}
简单的代码分析
遍历子view,计算是否需要换行。这个地方主要是关于最后一个控件的计算一定不要遗漏思路看代码注释
还有就是关于控件之间的间距的计算(每行的控件间距个数是每行的控件个数-1)。
在最后一个控件的地方,需要注意就是如果没有换行,需要减去一个控件间距(在不换行的判断里面添加了控件间距,但是他后面没有控件不需要,所以减去)
(换行的时候,在重置行宽的时候,也添加了控件间距)
行间距,这个地方有俩个地方需要注意1.行间距个数=行数-1 2.需要在测量高度和计算高度里面都考虑到行间距
还有一个地方就是在测量的时候,将每一行的子view和对应的行号(索引)从0开始添加到了一个map集合中,将每一行的行高添加到了List集合
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 设置布局的起始位置
int left = getPaddingLeft();
int top = getPaddingTop();
// 遍历行
for (int i = 0; i <= index; i++) {
// 遍历每行
for (int j = 0; j < views.get(i).size(); j++) {
// 得到当前索引下的子view
View childView = views.get(i).get(j);
// View.GONE 该模式下的子view不需要布局
if (childView.getVisibility() == View.GONE) {
continue;
}
// 布局
int cl = left;
int cr = cl + childView.getMeasuredWidth();
int cb = top + childView.getMeasuredHeight();
childView.layout(cl, top, cr, cb);
// 设置下一个子view的起始位置 当前的子view的左边的位置+控件之间的间距
left = cr + weightLevelSpace;
}
// 换行时 重置布局位置
left = getPaddingLeft();
top += (lineHeights.get(i) + lineSpace);
}
}
简单代码分析
在布局的时候主要每一个子view的位置的确定
注意的地方就是不要忘记了计算控件间距还有行间距
最后附上源代码下载地址
git 地址 最新代码会在git上面更新