自定义View
public class MyFlowLayout extends ViewGroup { /** * 存储行的集合,管理行 */ private List<Line> mLines = new ArrayList<>(); /** * 水平和竖直的间距 */ private float vertical_space; private float horizontal_space; // 当前行的指针 private Line mCurrentLine; // 行的最大宽度,除去边距的宽度 private int mMaxWidth; public MyFlowLayout(Context context) { this(context, null); } public MyFlowLayout(Context context, AttributeSet attrs) { super(context, attrs); // 获取自定义属性 TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.FlowLayout); horizontal_space = array.getDimension(R.styleable.FlowLayout_width_space,0); vertical_space = array.getDimension(R.styleable.FlowLayout_height_space,0); array.recycle(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 每次测量之前都先清空集合,不让会覆盖掉以前 mLines.clear(); mCurrentLine = null; // 获取总宽度 int width = MeasureSpec.getSize(widthMeasureSpec); // 计算最大的宽度 mMaxWidth = width - getPaddingLeft() - getPaddingRight(); // ******************** 测量孩子 ******************** // 遍历获取孩子 int childCount = this.getChildCount(); for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); // 测量孩子 measureChild(childView, widthMeasureSpec, heightMeasureSpec); // 测量完需要将孩子添加到管理行的孩子的集合中,将行添加到管理行的集合中 if (mCurrentLine == null) { // 初次添加第一个孩子的时候 mCurrentLine = new Line(mMaxWidth, horizontal_space); // 添加孩子 mCurrentLine.addView(childView); // 添加行 mLines.add(mCurrentLine); } else { // 行中有孩子的时候,判断时候能添加 if (mCurrentLine.canAddView(childView)) { // 继续往该行里添加 mCurrentLine.addView(childView); } else { // 添加到下一行 mCurrentLine = new Line(mMaxWidth, horizontal_space); mCurrentLine.addView(childView); mLines.add(mCurrentLine); } } } // ******************** 测量自己 ********************* // 测量自己只需要计算高度,宽度肯定会被填充满的 int height = getPaddingTop() + getPaddingBottom(); for (int i = 0; i < mLines.size(); i++) { // 所有行的高度 height += mLines.get(i).height; } // 所有竖直的间距 height += (mLines.size() - 1) * vertical_space; // 测量 setMeasuredDimension(width, height); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // 这里只负责高度的位置,具体的宽度和子孩子的位置让具体的行去管理 l = getPaddingLeft(); t = getPaddingTop(); for (int i = 0; i < mLines.size(); i++) { // 获取行 Line line = mLines.get(i); // 管理 line.layout(t, l); // 更新高度 t += line.height; if (i != mLines.size() - 1) { // 不是最后一条就添加间距 t += vertical_space; } } } /** * 内部类,行管理器,管理每一行的孩子 */ public class Line { // 定义一个行的集合来存放子View private List<View> views = new ArrayList<>(); // 行的最大宽度 private int maxWidth; // 行中已经使用的宽度 private int usedWidth; // 行的高度 private int height; // 孩子之间的距离 private float space; // 通过构造初始化最大宽度和边距 public Line(int maxWidth, float horizontalSpace) { this.maxWidth = maxWidth; this.space = horizontalSpace; } /** * 往集合里添加孩子 */ public void addView(View view) { int childWidth = view.getMeasuredWidth(); int childHeight = view.getMeasuredHeight(); // 更新行的使用宽度和高度 if (views.size() == 0) { // 集合里没有孩子的时候 if (childWidth > maxWidth) { usedWidth = maxWidth; height = childHeight; } else { usedWidth = childWidth; height = childHeight; } } else { usedWidth += childWidth + space; height = childHeight > height ? childHeight : height; } // 添加孩子到集合 views.add(view); } /** * 判断当前的行是否能添加孩子 * * @return */ public boolean canAddView(View view) { // 集合里没有数据可以添加 if (views.size() == 0) { return true; } // 最后一个孩子的宽度大于剩余宽度就不添加 if (view.getMeasuredWidth() > (maxWidth - usedWidth - space)) { return false; } // 默认可以添加 return true; } /** * 指定孩子显示的位置 * * @param t * @param l */ public void layout(int t, int l) { // 平分剩下的空间 int avg = (maxWidth - usedWidth) / views.size(); // 循环指定孩子位置 for (View view : views) { // 获取宽高 int measuredWidth = view.getMeasuredWidth(); int measuredHeight = view.getMeasuredHeight(); // 重新测量 view.measure(MeasureSpec.makeMeasureSpec(measuredWidth + avg, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY)); // 重新获取宽度值 measuredWidth = view.getMeasuredWidth(); int top = t; int left = l; int right = measuredWidth + left; int bottom = measuredHeight + top; // 指定位置 view.layout(left, top, right, bottom); // 更新数据 l += measuredWidth + space; } } } }
attrs布局
<resources> <declare-styleable name="FlowLayout"> <attr name="width_space" format="dimension"/> <attr name="height_space" format="dimension"/> </declare-styleable> </resources>
控件使用
<zhuozhuo.com.zhuo.widget.MyFlowLayout android:id="@+id/flow_layout" app:width_space="10dp" app:height_space="10dp" android:padding="5dp" android:layout_width="match_parent" android:layout_height="match_parent"></zhuozhuo.com.zhuo.widget.MyFlowLayout>
TextView textView = new TextView(this); //textView.setBackgroundColor(ContextCompat.getColor(this, R.color.main_color4)); textView.setTextColor(Color.WHITE); textView.setPadding(5,5,5,5); textView.setGravity(Gravity.CENTER); textView.setText("455556546"); textView.setTextSize(14); // 设置彩色背景 GradientDrawable normalDrawable = new GradientDrawable(); normalDrawable.setShape(GradientDrawable.RECTANGLE); int a = 255; int r = 150 + random.nextInt(100); int g = 150 + random.nextInt(100); int b = 150 + random.nextInt(100); normalDrawable.setColor(Color.argb(a, r, g, b)); // 设置按下的灰色背景 GradientDrawable pressedDrawable = new GradientDrawable(); pressedDrawable.setShape(GradientDrawable.RECTANGLE); pressedDrawable.setColor(Color.GRAY); // 背景选择器 StateListDrawable stateDrawable = new StateListDrawable(); stateDrawable.addState(new int[]{android.R.attr.state_pressed}, pressedDrawable); stateDrawable.addState(new int[]{}, normalDrawable); textView.setBackground(stateDrawable); flowLayout.addView(textView);