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.