FlowLayout: application of flow layout

Keywords: Android Java xml encoding

I. application

Flow layout means that the control is automatically added to the right according to the width of the ViewGroup. If there is not enough space left in the current row, it is automatically added to the next row. It is often used in search history and hot search interfaces.

Two, implementation

1.FlowLayout.java

Just override the onMeasure and onLayout functions.

  • onMeasure mainly measures the width and height of the child controls
  • onLayout is mainly to layout the child controls
public class FlowLayout extends ViewGroup {
    public FlowLayout(Context context) {
        super(context);
    }

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

    public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected LayoutParams generateLayoutParams(
            LayoutParams mLayoutParams) {
        return new MarginLayoutParams(mLayoutParams);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new MarginLayoutParams(LayoutParams.MATCH_PARENT,
                LayoutParams.MATCH_PARENT);
    }

    /**
     * Responsible for setting the measurement mode and size of the child control and setting its own width and height according to all the child controls
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // Get the measurement mode and size set for it by its parent container
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

        // Record width and height in the case of warp < content
        int width = 0;
        int height = 0;
        //Record the width of each line, and the width is continuously taken as the maximum width
        int lineWidth = 0;
        //The height of each line, accumulated to height
        int lineHeight = 0;

        int cCount = getChildCount();
        // Traverse each child element
        for (int i = 0; i < cCount; i++) {
            View child = getChildAt(i);
            // Measure the width and height of each child
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            // Get lp of child
            MarginLayoutParams lp = (MarginLayoutParams) child
                    .getLayoutParams();
            // The actual width occupied by the current subspace
            int childWidth = child.getMeasuredWidth() + lp.leftMargin
                    + lp.rightMargin;
            // The actual height occupied by the current subspace
            int childHeight = child.getMeasuredHeight() + lp.topMargin
                    + lp.bottomMargin;
            /**
             * If the current child is added, the maximum width will be exceeded. Then the current maximum width will be given to the width, and the height will be accumulated and a new line will be opened
             */
            if (lineWidth + childWidth > sizeWidth) {
                //Update width (maximum width)
                width = Math.max(lineWidth, width);
                //Start next line, update lineWidth
                lineWidth = childWidth;
                // Overlay current height,
                height += lineHeight;
                // Turn on to record the height of the next line
                lineHeight = childHeight;
            } else {
                //Add lineWidth without wrapping
                lineWidth += childWidth;
                //lineHeight takes the maximum height
                lineHeight = Math.max(lineHeight, childHeight);
            }
            // If it is the last, compare the maximum width of the current record with the current lineWidth
            if (i == cCount - 1) {
                //Prevent the width of the last one from being the maximum, and update the width to be the maximum
                width = Math.max(width, lineWidth);
                //Ensure that the final height is greater than the cumulative value of all lineheights
                height += lineHeight;
            }

        }
        //Create view
        //Then, according to the measured width and height of all childviews, the width and height of the ViewGroup when it is set to wrap content are obtained.
        //Finally, according to the mode, if it is measurespec.exact, the width and height passed in by the parent ViewGroup will be used directly, otherwise, it will be set to the width and height calculated by itself.
        setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ? sizeWidth
                : width, (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight
                : height);

    }

    /**
     * Store all views, record by line
     */
    private List<List<View>> mAllViews = new ArrayList<>();
    /**
     * Record the maximum height of each line
     */
    private List<Integer> mLineHeight = new ArrayList<>();

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mAllViews.clear();
        mLineHeight.clear();

        int width = getWidth();

        int lineWidth = 0;
        int lineHeight = 0;
        // Store all child views in each row
        List<View> lineViews = new ArrayList<>();
        int cCount = getChildCount();
        // Traverse all the children
        for (int i = 0; i < cCount; i++) {
            View child = getChildAt(i);
            MarginLayoutParams lp = (MarginLayoutParams) child
                    .getLayoutParams();
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();

            // If a new line is needed
            if (childWidth + lp.leftMargin + lp.rightMargin + lineWidth > width) {
                // Record all views in this row and the maximum height
                mLineHeight.add(lineHeight);
                // Save the child view of the current row, and then open a new ArrayList to save the child view of the next row
                mAllViews.add(lineViews);
                lineWidth = 0;// Reset row width
                lineViews = new ArrayList<>();
            }
            /**
             * If no line wrapping is required, accumulate
             */
            lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
            lineHeight = Math.max(lineHeight, childHeight + lp.topMargin
                    + lp.bottomMargin);
            lineViews.add(child);
        }
        // Record last line
        mLineHeight.add(lineHeight);
        mAllViews.add(lineViews);

        int left = 0;
        int top = 0;
        // Get the total number of rows
        int lineNums = mAllViews.size();
        for (int i = 0; i < lineNums; i++) {
            // All views of each line
            lineViews = mAllViews.get(i);
            // Maximum height of current row
            lineHeight = mLineHeight.get(i);

            // Traverse all views in the current row
            for (int j = 0; j < lineViews.size(); j++) {
                View child = lineViews.get(j);
                if (child.getVisibility() == View.GONE) {
                    continue;
                }
                MarginLayoutParams lp = (MarginLayoutParams) child
                        .getLayoutParams();

                //Calculate left,top,right,bottom of childView
                int lc = left + lp.leftMargin;
                int tc = top + lp.topMargin;
                int rc = lc + child.getMeasuredWidth();
                int bc = tc + child.getMeasuredHeight();

                child.layout(lc, tc, rc, bc);

                left += child.getMeasuredWidth() + lp.rightMargin
                        + lp.leftMargin;
            }
            left = 0;
            top += lineHeight;
        }
    }
}

2.history_layout.xml

Introduce the FlowLayout control into the layout.

<LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginEnd="29.42dp"
        android:layout_marginStart="29.42dp">

        <mysummary.myutils.FlowLayout
            android:id="@+id/FlowLayout"
            android:layout_width="wrap_content"
            android:layout_height="match_parent">
        </mysummary.myutils.FlowLayout>
</LinearLayout>

3.history_textview_item.xml

Format child controls

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/history_item"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginBottom="15dp"
    android:layout_marginRight="12.57dp"
    android:orientation="horizontal">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/search_history_text"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_marginLeft="6.13dp"
            android:layout_marginTop="-4dp"
            android:ellipsize="end"
            android:fontFamily="sec-roboto-condensed"
            android:singleLine="true"
            android:text="TEXT"
            android:textColor="@color/colorWhite100"
            android:textSize="15.94dp" />
    </LinearLayout>
</LinearLayout>

4.Activity.java

In java files, each record is displayed under FlowLayout according to the size of the read history.

for (int i = 0; i < mHistoryList.size(); i++) {
            // If root is not null and attachToRoot is set to false, all layout properties at the outermost layer of the layout file will be set
            // When the view is added to the parent view, these layout properties will take effect automatically.
            View view = LayoutInflater.from(this).inflate(R.layout.view_search_history_item, mFlowLayout, false);
            TextView mText = view.findViewById(R.id.search_history_text);

            final String text = mHistoryList.get(i);
            if (text != null) {
                mText.setText(text);
                //Add history click event
                //...

                mFlowLayout.addView(view);
            }
}

The above is the simple application and implementation of FlowLayout.

Posted by johnSTK on Sun, 05 Jan 2020 11:57:22 -0800