自定义流式布局,能够设置margin、padding。子view实现自动换行markdown
/**
* FlowLayout自适应容器实现
*/
public class FlowLayout extends ViewGroup {
public FlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* (1)什么时候换行
* 从效果图中能够看到,FlowLayout的布局是一行行的,若是当前行已经放不下下一个控件,那就把这个控件移到下一行显示。
* 因此咱们要有个变量来计算当前行已经占据的宽度,以判断剩下的空间是否还能容得下下一个控件。
* (2)、如何获得FlowLayout的宽度
* FlowLayout的宽度是全部行宽度的最大值,因此咱们要记录下每一行的所占据的宽度值,进而找到全部值中的最大值。
* (3)、如何获得FlowLayout的高度
* 很显然,FlowLayout的高度是每一行高度的总和,而每一行的高度则是取该行中全部控件高度的最大值。
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.d(FlowLayout.class.getSimpleName(), "onMeasure");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
//every line width
int lineWidth = 0;
//every line height
int lineHeight = 0;
//FlowLayout width
int width = 0;
//FlowLayout height
int height = 0;
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
int marginLeft = layoutParams.leftMargin;
int marginRight = layoutParams.rightMargin;
int marginTop = layoutParams.topMargin;
int marginBottom = layoutParams.bottomMargin;
measureChild(child, widthMeasureSpec, heightMeasureSpec);
int childWidth = child.getMeasuredWidth() + marginLeft + marginRight;
int childHeight = child.getMeasuredHeight() + marginTop + marginBottom;
if (lineWidth + childWidth + paddingRight + paddingLeft > measureWidth) {
//须要换行
width = Math.max(childWidth, lineWidth);
height += lineHeight;
//由于因为盛不下当前控件,而将此控件调到下一行,因此将此控件的高度和宽度初始化给lineHeight、lineWidth
lineWidth = childWidth;
lineHeight = childHeight;
} else {
// 不然累加值lineWidth,lineHeight取最大高度
lineWidth += childWidth;
lineHeight = Math.max(lineHeight, childHeight);
}
//添加最后一行
if (i == count - 1) {
width = Math.max(lineWidth, width);
height += lineHeight;
}
}
//增长内间距
height = height + paddingTop + paddingBottom;
width = width + paddingLeft + paddingRight;
setMeasuredDimension((measureWidthMode == MeasureSpec.EXACTLY) ? measureWidth : width, (measureHeightMode == MeasureSpec.EXACTLY) ? measureHeight : height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int count = getChildCount();
//累加当前行的行宽
int lineWidth = 0;
//当前行的行高
int lineHeight = 0;
//当前坐标的top坐标和left坐标
int top = paddingTop, left = paddingLeft;
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
int marginLeft = layoutParams.leftMargin;
int marginRight = layoutParams.rightMargin;
int marginTop = layoutParams.topMargin;
int marginBottom = layoutParams.bottomMargin;
int childWidth = child.getMeasuredWidth() + marginLeft + marginRight;
int childHeight = child.getMeasuredHeight() + marginTop + marginBottom;
if (childWidth + lineWidth + paddingRight + paddingLeft> getMeasuredWidth()) {
//若是换行,当前控件将跑到下一行,从最左边开始,因此left就是paddingLeft,而top则须要加上上一行的行高,才是这个控件的top点;
top += lineHeight;
left = paddingLeft;
//一样,从新初始化lineHeight和lineWidth
lineHeight = childHeight;
lineWidth = childWidth;
} else {
// 不然累加值lineWidth,lineHeight取最大高度
lineWidth += childWidth;
lineHeight = Math.max(lineHeight, childHeight);
}
int lp = left + marginLeft;
int tp = top + marginTop;
int rp = lp + child.getMeasuredWidth();
int bp = tp + child.getMeasuredHeight();
child.layout(lp, tp, rp, bp);
//将left置为下一子控件的起始点
left += childWidth;
}
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MarginLayoutParams(p);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
}
复制代码
其中须要重写 重写generateLayoutParams()函数,才能实现子View设置Marginide
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MarginLayoutParams(p);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
复制代码
原理以下: 首先,在container在初始化子控件时,会调用LayoutParams generateLayoutParams(LayoutParams p)来为子控件生成对应的布局属性,但默认只是生成layout_width和layout_height因此对应的布局参数,即在正常状况下的generateLayoutParams()函数生成的LayoutParams实例是不可以取到margin值的.函数
/**
*从指定的XML中获取对应的layout_width和layout_height值
*/
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
/*
*若是要使用默认的构造方法,就生成layout_width="wrap_content"、layout_height="wrap_content"对应的参数
*/
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
复制代码