前言
flow layout, 流式布局, 这个概念在移动端或者前端开发中很常见,特别是在多标签的展示中, 往往起到了关键的作用。然而Android 官方, 并没有为开发者提供这样一个布局, 于是有很多开发者自己做了这样的工作,github上也出现了很多自定义FlowLayout。 最近, 我也实现了这样一个FlowLayout,自己感觉可能是当前最好用的FlowLayout了(捂脸),在这里做一下分享。
项目地址:https://github.com/2547095199/HuaYuan
展示
第一张图, 展示向FlowLayout中不断添加子View
第二张图, 展示压缩子View, 使他们尽可能充分利用空间
第三张图, 展示调整子View之间间隔, 使各行左右对齐
这张图,截断flowlayout到指定行数。--20160520更新。
话不多说,接下来看代码:
flawlayout页面代码:
package yuan.bwie.com.flowlayout; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import java.util.ArrayList; import java.util.List; /** * Created by CZ on 2017/11/30. */ public class FlowLayout extends ViewGroup { private Context mContext; private int usefulWidth; // the space of a line we can use(line's width minus the sum of left and right padding private int lineSpacing = 0; // the spacing between lines in flowlayout List<View> childList = new ArrayList(); List<Integer> lineNumList = new ArrayList(); public FlowLayout(Context context) { this(context, null); } public FlowLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mContext = context; TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.FlowLayout); lineSpacing = mTypedArray.getDimensionPixelSize( R.styleable.FlowLayout_lineSpacing, 0); mTypedArray.recycle(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int mPaddingLeft = getPaddingLeft(); int mPaddingRight = getPaddingRight(); int mPaddingTop = getPaddingTop(); int mPaddingBottom = getPaddingBottom(); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int lineUsed = mPaddingLeft + mPaddingRight; int lineY = mPaddingTop; int lineHeight = 0; for (int i = 0; i < this.getChildCount(); i++) { View child = this.getChildAt(i); if (child.getVisibility() == GONE) { continue; } int spaceWidth = 0; int spaceHeight = 0; LayoutParams childLp = child.getLayoutParams(); if (childLp instanceof MarginLayoutParams) { measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, lineY); MarginLayoutParams mlp = (MarginLayoutParams) childLp; spaceWidth = mlp.leftMargin + mlp.rightMargin; spaceHeight = mlp.topMargin + mlp.bottomMargin; } else { measureChild(child, widthMeasureSpec, heightMeasureSpec); } int childWidth = child.getMeasuredWidth(); int childHeight = child.getMeasuredHeight(); spaceWidth += childWidth; spaceHeight += childHeight; if (lineUsed + spaceWidth > widthSize) { //approach the limit of width and move to next line lineY += lineHeight + lineSpacing; lineUsed = mPaddingLeft + mPaddingRight; lineHeight = 0; } if (spaceHeight > lineHeight) { lineHeight = spaceHeight; } lineUsed += spaceWidth; } setMeasuredDimension( widthSize, heightMode == MeasureSpec.EXACTLY ? heightSize : lineY + lineHeight + mPaddingBottom ); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int mPaddingLeft = getPaddingLeft(); int mPaddingRight = getPaddingRight(); int mPaddingTop = getPaddingTop(); int lineX = mPaddingLeft; int lineY = mPaddingTop; int lineWidth = r - l; usefulWidth = lineWidth - mPaddingLeft - mPaddingRight; int lineUsed = mPaddingLeft + mPaddingRight; int lineHeight = 0; int lineNum = 0; lineNumList.clear(); for (int i = 0; i < this.getChildCount(); i++) { View child = this.getChildAt(i); if (child.getVisibility() == GONE) { continue; } int spaceWidth = 0; int spaceHeight = 0; int left = 0; int top = 0; int right = 0; int bottom = 0; int childWidth = child.getMeasuredWidth(); int childHeight = child.getMeasuredHeight(); LayoutParams childLp = child.getLayoutParams(); if (childLp instanceof MarginLayoutParams) { MarginLayoutParams mlp = (MarginLayoutParams) childLp; spaceWidth = mlp.leftMargin + mlp.rightMargin; spaceHeight = mlp.topMargin + mlp.bottomMargin; left = lineX + mlp.leftMargin; top = lineY + mlp.topMargin; right