android自定义流式布局(仿淘宝历史记录标签)

Screenshot_2019-12-06-16-32-12.png

一 概述

当一行显示不了子view时自动换行,就和淘宝的历史记录使用的布局一样。

二 原理

绘制控件的流程图

image

如果是重写viewGroup 则测量自身尺寸的代码写在onMeasure里,布局子view位置的代码写在onLayout里。

重写onMeasure步骤:
1 测量所有子view的尺寸
2 遍历所有子view,计算每个子view在布局里的位置
3设置布局的尺寸

三 实现

1,在values 新建attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="FlowLayout">
        <attr name="maxLine" format="integer"/>
        <attr name="noRightMargin" format="boolean"/>
    </declare-styleable>
</resources>

2,新建FlowLayout类

public class FlowLayout extends ViewGroup {
    protected List<Rect> viewRects;//子view布局
    protected int allChildWidth = 0, allChildHeight = 0;//当前布局包括padding的最大宽高
    int maxLine;//最大行数
    boolean noRightMargin;//每行最右边子view是否需要右边距

    public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray ary = this.getContext().obtainStyledAttributes(attrs, R.styleable.FlowLayout);
        maxLine = ary.getInt(R.styleable.FlowLayout_maxLine, 0);
        noRightMargin = ary.getBoolean(R.styleable.FlowLayout_noRightMargin,false);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        for (int i = 0; i < viewRects.size(); i++) {
            View child = getChildAt(i);
            if (child == null) {
                return;
            }
            Rect rect = viewRects.get(i);
            child.layout(rect.left, rect.top, rect.right, rect.bottom);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        viewRects = new ArrayList<>();
        /*测量子view尺寸*/
        measureChildren(widthMeasureSpec, heightMeasureSpec);

        int measureWidth, measureHeight;

        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        //预布局
        compute(widthSize);
        //给定大小和match_parent
        if (widthMode == MeasureSpec.EXACTLY) {
            measureWidth = widthSize;
            //未规定大小
        } else {
            measureWidth = allChildWidth;
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            measureHeight = heightSize;
        } else {
            measureHeight = allChildHeight;
        }
        //设置布局宽高
        setMeasuredDimension(measureWidth, measureHeight);
    }

    private void compute(int widthSize) {
        boolean aRow = false;//是否换行
        int line = 1;
        int paddingLeft = getPaddingLeft();
        int maxWidth = widthSize - getPaddingRight();//最大宽度
        if (maxWidth <= 0) {
            return;
        }
        int nextRowWidth = paddingLeft;//预计下一步起点已经占宽度

        int nextColumnTopHeight = getPaddingTop();//预计下一步行顶部所占高度
        int upRowHeight = 0;//上一步最大行高

        MarginLayoutParams margin;
        int left,top,right,bottom;

        for (int i = 0; i < getChildCount(); i++) {

            View child = getChildAt(i);
            int meausredWidth = child.getMeasuredWidth();//view测量宽度
            int measuredHeight = child.getMeasuredHeight();//view测量高度

            margin = (MarginLayoutParams) child.getLayoutParams();//view边距,必须重写generateDefaultLayoutParams不然会报错
            int childMarginLeft = margin.leftMargin;
            int childMarginRight = margin.rightMargin;
            int childMarginTop = margin.topMargin;
            int childMarginBottom = margin.bottomMargin;
            int childWidth = childMarginLeft + meausredWidth + childMarginRight;//view实际宽度
            int childHeight = childMarginTop + measuredHeight + childMarginBottom;//view实际高度

            //预计本次可能需要布局在下一行
            if (nextRowWidth + childWidth > maxWidth) {
                //布局在本行
                if (noRightMargin && (nextRowWidth + childWidth - childMarginRight <= maxWidth)) {
                    left = nextRowWidth + childMarginLeft;
                    top = nextColumnTopHeight + childMarginTop;
                    right = nextRowWidth + childWidth - childMarginRight;
                    bottom = nextColumnTopHeight + childHeight - childMarginBottom;
                    viewRects.add(new Rect(left, top, right, bottom));

                    //重置数据
                    int s = Math.max(childHeight, upRowHeight);//比较高度
                    nextRowWidth = paddingLeft;
                    nextColumnTopHeight += s;
                    allChildHeight = nextColumnTopHeight + getPaddingBottom();
                    upRowHeight = 0;

                    aRow = true;
                    line++;
                    if (line == maxLine) {
                        break;
                    }
                } else if (!noRightMargin || (noRightMargin && (nextRowWidth + childWidth - childMarginRight > maxWidth))) {
                    //布局在下一行
                    if (line == maxLine) {
                        break;
                    }
                    left = paddingLeft + childMarginLeft;
                    top = nextColumnTopHeight + upRowHeight + childMarginTop;
                    right = paddingLeft + childWidth - childMarginRight;
                    bottom = nextColumnTopHeight + upRowHeight + childHeight - childMarginBottom;
                    viewRects.add(new Rect(left, top, right, bottom));

                    //重置数据
                    nextColumnTopHeight+= upRowHeight;
                    nextRowWidth = right + childMarginRight;
                    upRowHeight = childHeight;
                    aRow = true;
                    line++;
                    allChildHeight = nextColumnTopHeight + upRowHeight + getPaddingBottom();
                }

            } else {
                //在当前行
                left = nextRowWidth + childMarginLeft;
                top = nextColumnTopHeight + childMarginTop;
                right = nextRowWidth + childWidth - childMarginRight;
                bottom = nextColumnTopHeight + childHeight - childMarginBottom;
                viewRects.add(new Rect(left, top, right, bottom));

                //重置数据
                nextRowWidth += childWidth;
                upRowHeight = Math.max(childHeight, upRowHeight);
                allChildHeight = nextColumnTopHeight + upRowHeight + getPaddingBottom();
            }


        }

        if (aRow) {
            allChildWidth = widthSize;
        } else {
            allChildWidth = nextRowWidth;
        }
    }

    //inflater 的时候调用
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

    //addView的时候
    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        return new MarginLayoutParams(p);
    }

    //child没有设置LayoutParams的时候调用
    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

    //addView的时候
    @Override
    protected boolean checkLayoutParams(LayoutParams p) {
        return p instanceof MarginLayoutParams;
    }

}

3,在xml里

<com.wuzhiping.kitchens.selfView.FlowLayout
        app:maxLine="5"
        android:id="@+id/xun_rage"
        app:noRightMargin="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

4,添加子view

        flow1 = view.findViewById(R.id.xun_rage);
        int mg = 10;
        FlowLayout.MarginLayoutParams pa = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
        pa.setMargins(0,mg,mg*4,0);
        for (int i=0;i < 20; i ++){
            TextView item = new TextView(view.getContext());
            item.setText("万一");
            item.setBackgroundResource(R.drawable.xun_top_edit);
            item.setTextSize(TypedValue.COMPLEX_UNIT_DIP,18);
            item.setPadding(mg,mg,mg,mg);
            flow1.addView(item,pa);
        }

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容