public class FlowLayout extends ViewGroup {
/**
* 所有子控件的容器
*/
private List> lineList = new ArrayList<>();
/**
* 每行的高度
*/
private List lineHeightList = new ArrayList<>();
/**
* 防止多次测量
*/
private boolean measureFlag = true;
public FlowLayout(Context context) {
super(context);
}
public FlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
/**
* 在被调用这个方法之前 它的父容器 已经把它的测量模式改成了当前控件的测量模式
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取到父容器 给我们的参考值
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//获取到自己的测量模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//记录当前控件里面的子控件的总高宽
int childMaxWidth = 0;
int childCountHeight = 0;
//防止多次测量
if (measureFlag) {
measureFlag = false;
} else {
//当前一行,控件中的子控件的总宽度
int lineCountWidth = 0;
//当前一行,最高子控件的高度
int lineMaxHeight = 0;
//记录每个子控件的宽高
int childWidth, childHeight;
//创建一行空容器
List viewList = new ArrayList<>();
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
//先测量子控件
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();
//计算当前子控件的实际宽高
childWidth = childView.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
childHeight = childView.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;
//如果当前行的宽度,加上这个view的宽度,大于容器的宽度时,就换行
if (lineCountWidth + childWidth + getPaddingLeft() + getPaddingRight() > widthSize) {
//需要换行的情况
//每次进入到这里的时候 只是保存了上一行的信息,并没有保存当前行的信息
//拿到所有行中,最大的宽,去决定EXACTLY时的最大宽度
childMaxWidth = Math.max(childMaxWidth, lineCountWidth);
childCountHeight += lineMaxHeight;
//把行高记录到集合中
lineHeightList.add(lineMaxHeight);
//把这一行的数据放进总容器
lineList.add(viewList);
//把一行的容器,重新创建一个,虽然会频繁创建,但如果调用clear,会导致lineList的栈也会被清掉
viewList = new ArrayList<>();
//把每行的高宽初始化
lineCountWidth = childWidth;
lineMaxHeight = childHeight;
viewList.add(childView);
} else {
//不需要换行的情况
lineCountWidth += childWidth;
//取当前行的所有子控件最大的高度
lineMaxHeight = Math.max(lineMaxHeight, childHeight);
viewList.add(childView);
}
//这样做的原因是 之前的if else中 不会把最后一行的高度加进listLineHeight
// 最后一行要特殊对待 不管最后一个item是不是最后一行的第一个item
if (i == (childCount - 1)) {
//保存当前行信息
childMaxWidth = Math.max(childMaxWidth, lineCountWidth + getPaddingLeft() + getPaddingRight());
childCountHeight += lineMaxHeight;
lineHeightList.add(lineMaxHeight);
lineList.add(viewList);
}
}
}
//设置控件最终大小
int measureWidth = (widthMode == MeasureSpec.EXACTLY ? widthSize : childMaxWidth + getPaddingLeft() + getPaddingRight());
int measureHeight = (heightMode == MeasureSpec.EXACTLY ? heightSize : childCountHeight + getPaddingTop() + getPaddingBottom());
setMeasuredDimension(measureWidth, measureHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//摆放子控件的位置
int left, top, right, bottom;
//保存上一个控件的边距
int countLeft = 0;
//保存上一行的高度的边距
int countTop = 0;
//遍历所有行
for (List views : lineList) {
//遍历每一行的控件
for (View view : views) {
MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
left = countLeft + layoutParams.leftMargin;
top = countTop + layoutParams.topMargin;
right = left + view.getMeasuredWidth();
bottom = top + view.getMeasuredHeight();
view.layout(left + getPaddingLeft(), top + getPaddingTop(), right + getPaddingRight(), bottom + getPaddingBottom());
//记录当前控件的实际宽度坐标,让下一个控件知道X轴的起点在哪
countLeft += view.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
}
int i = lineList.indexOf(views);
//换行时从最左边开始
countLeft = 0;
//换行时累加高度
countTop += lineHeightList.get(i);
}
//布局结束后清空记录的list
lineList.clear();
lineHeightList.clear();
}
}