Flow layout will be used from time to time in the project, such as search history, classification, hot words and other tags to display, can be designed as a flow layout display. Here I implemented a flow layout of search history from 0 to 1.

Demonstration effect:

Implementation steps:

1. Create FlowLayoutView, create data source, and add each subview. 2. Iterate over the child view in the onMeasure method, simply calculate the remaining width, use the collection to store several child views in the current row, and then set the final size according to the cumulative height of the child view. 3. In the onLayout method, iterate over each row, iterate over the child view of the row, and arrange layout to set the position of the child view in turn.Copy the code

Emphasis:

Introduce the concept of rows, each of which stores the child views it should place. Determine the remaining space in the row and the width of the subview to determine whether it can fit in that row or whether it needs to create the next row.

Main code:

/** * Description stream layout viewGroup */
public class FlowLayoutView extends ViewGroup {
    private List<Row> rows = new ArrayList<>();
    private int usedWidth;
    /** * The current line to operate */
    private Row curRow;
    private int verticalPadding = 30;
    private int horizontalPadding = 40;

    public FlowLayoutView(Context context) {
        super(context);
    }

    public FlowLayoutView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        restoreLine();  // For each relayout, the properties should be initialized to avoid the confusion of onMeasure repeated calls

        // The child view sets the width and height to the size of the parent view minus the padding
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        // Set the width and height of each subview, and assign each subview to its own row
        for (int i = 0; i < getChildCount(); i++) {
            View childView = getChildAt(i);

            // Set the subview to AT_MOST mode, i.e. the layout attribute is wrap_content
            int childWidthSpec = MeasureSpec.makeMeasureSpec(width, widthMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : widthMode);
            int childHeightSpec = MeasureSpec.makeMeasureSpec(height, heightMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : heightMode);
            childView.measure(childWidthSpec, childHeightSpec);

            if (curRow == null) {
                curRow = new Row();
            }

            // Use the current childView width and the remaining width to determine if it can fit into the current line
            if (childView.getMeasuredWidth() + horizontalPadding > width - usedWidth) {
                // Wrap the line first, then put it in
                nextLine();
            }

            usedWidth += childView.getMeasuredWidth() + horizontalPadding;
            curRow.addView(childView);
        }

        // Add the last row to rows
        rows.add(curRow);

        // Resets its own height based on the height of the subviews
        int finalHeight = 0;
        for (Row row : rows) {
            finalHeight += row.height + verticalPadding;
        }

        setMeasuredDimension(width, finalHeight);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        int top = 0;
        // Iterate over each row, laying out each row's child views
        for(Row row : rows) { row.layout(top); top = top + row.height + verticalPadding; }}/** * newline, we need to store the current row, and create a new row, the new row space set to 0 */
    private void nextLine(a) {
        rows.add(curRow);
        curRow = new Row();
        usedWidth = 0;
    }

    /** * OnMeasure needs to reset information every time */
    private void restoreLine(a) {
        rows.clear();
        curRow = new Row();
        usedWidth = 0;
    }

    /** * is used to record information about the child views placed on each row */
    class Row {
        /** * Subview */
        private List<View> childViews = new ArrayList<>();
        private int height;

        public void addView(View view) {
            childViews.add(view);
            height = view.getMeasuredHeight() > height ? view.getMeasuredHeight() : height;  // Take the height of the highest subview
        }

        public int getSize(a) {
            return childViews.size();
        }

        /** * Select * from * where ** ** **
        public void layout(int top) {
            int leftMargin = 0;
            for (int i = 0; i < childViews.size(); i++) { View view = childViews.get(i); view.layout(leftMargin, top, leftMargin + view.getMeasuredWidth(), top + view.getMeasuredHeight()); leftMargin = leftMargin + view.getMeasuredWidth() + horizontalPadding; }}}}Copy the code

MainActivity code:

public class MainActivity extends AppCompatActivity {
    private FlowLayoutView flowLayoutView;

    private String[] tagTextArray = new String[]{"Tmall Genie"."Rechargeable desk lamp"."Pajamas"."Watch"."Creative Glass"."Summer T-shirt Man."."Light mechanical Keyboard"."Principles of computers"."Notebooks for High Achievers"."Coca-Cola"."The treadmill"."Suitcase"."Bamboo toilet paper"."Hair dryer"."Facial cleanser"."The curtain"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        init();
    }

    private void init(a) {
        flowLayoutView = findViewById(R.id.flowlayout);

        TextView tvAddTag = findViewById(R.id.tv_addtag);
        tvAddTag.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.item_tagview, null);
                TextView tvContent = view.findViewById(R.id.tv_content);
                tvContent.setText(tagTextArray[(int) (Math.random()*tagTextArray.length)]); flowLayoutView.addView(view); }}); }}Copy the code

Demo