自定義流式佈局,能夠設置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);
}
複製代碼