[Android] custom FlowLayout, supporting multiple Layout Optimization-android-flowlayout

Source: Internet
Author: User

[Android] custom FlowLayout, supporting multiple Layout Optimization-android-flowlayout
Preface

Flow layout and stream layout are common in mobile or front-end development, especially in multi-Label display. However, the Android official did not provide such a layout for developers, so many developers did the work themselves, and many custom FlowLayout appeared on github. Recently, I have also implemented such a FlowLayout, which I feel may be the best FlowLayout (cover your face). Here I will share it with you.
Project address: https://github.com/lankton/android-flowlayout

Display


The first figure showsAdd a child View to FlowLayout
The second figure showsCompress sub-views to make full use of space.
The third figure showsAdjust the interval between sub-views to align the left and right sides of each row

Use # basic stream Layout

Use FlowLayout in the layout file:


  

We can see that a custom parameter is provided.LineSpacingTo control the spacing between rows.

Compression
flowLayout.relayoutToCompress();

The compression method is to re-sort the child views to make them more reasonable to occupy space, which will be detailed later.

Alignment
flowLayout.relayoutToAlign();

Alignment does not change the layout of the sub-View or compress the sub-View.

Implementing stream layout rewrite the generateLayoutParams Method
@Override   protected LayoutParams generateLayoutParams(LayoutParams p) {       return new MarginLayoutParams(p);   }   @Override   public LayoutParams generateLayoutParams(AttributeSet attrs)   {       return new MarginLayoutParams(getContext(), attrs);   }

It is necessary to override the two reloads of this method. In this way, the LayoutParams of the element is MarginLayoutParam, which includes the margin attribute, which is exactly what we need.

Override onMeasure

There are two main purposes: the first is to measure the width and height of each sub-element, and the second is to set the FlowLayout measurement value based on the measured value of the sub-element.

@Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int mPaddingLeft = getPaddingLeft();        int mPaddingRight = getPaddingRight();        int mPaddingTop = getPaddingTop();        int mPaddingBottom = getPaddingBottom();        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        int lineUsed = mPaddingLeft + mPaddingRight;        int lineY = mPaddingTop;        int lineHeight = 0;        for (int i = 0; i < this.getChildCount(); i++) {            View child = this.getChildAt(i);            if (child.getVisibility() == GONE) {                continue;            }            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, lineY);            MarginLayoutParams mlp = (MarginLayoutParams) child.getLayoutParams();            int childWidth = child.getMeasuredWidth();            int childHeight = child.getMeasuredHeight();            int spaceWidth = mlp.leftMargin + childWidth + mlp.rightMargin;            int spaceHeight = mlp.topMargin + childHeight + mlp.bottomMargin;            if (lineUsed + spaceWidth > widthSize) {                //approach the limit of width and move to next line                lineY += lineHeight + lineSpacing;                lineUsed = mPaddingLeft + mPaddingRight;                lineHeight = 0;            }            if (spaceHeight > lineHeight) {                lineHeight = spaceHeight;            }            lineUsed += spaceWidth;        }        setMeasuredDimension(                widthSize,                heightMode == MeasureSpec.EXACTLY ? heightSize : lineY + lineHeight + mPaddingBottom        );    }

The Code logic is very simple, that is, to traverse the child element and calculate the cumulative length. If the length exceeds the width of a row, the cumulative length is cleared by 0, and the child element is placed in the next row. Why is this assumption? Because the process of actually placing child elements in FlowLayout is in the onLayout method.
Focus on the final setMeasuredDimension method. In the daily use of FlowLayout, our width is usually a fixed value or match_parent, which does not need to be changed based on the content. Therefore, the width value is directly widthSize, that is, the width obtained from the passed measured value.
The height is determined based on the MeasureSpec mode. EXACTLY means that the width of the measured value is the same as the width. Otherwise, it is wrap_content, and the height is determined by the height arranged by the child element.

Override onLayout
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) {     int mPaddingLeft = getPaddingLeft();     int mPaddingRight = getPaddingRight();     int mPaddingTop = getPaddingTop();     int lineX = mPaddingLeft;     int lineY = mPaddingTop;     int lineWidth = r - l;     usefulWidth = lineWidth - mPaddingLeft - mPaddingRight;     int lineUsed = mPaddingLeft + mPaddingRight;     int lineHeight = 0;     for (int i = 0; i < this.getChildCount(); i++) {         View child = this.getChildAt(i);         if (child.getVisibility() == GONE) {             continue;         }         MarginLayoutParams mlp = (MarginLayoutParams) child.getLayoutParams();         int childWidth = child.getMeasuredWidth();         int childHeight = child.getMeasuredHeight();         int spaceWidth = mlp.leftMargin + childWidth + mlp.rightMargin;         int spaceHeight = mlp.topMargin + childHeight + mlp.bottomMargin;         if (lineUsed + spaceWidth > lineWidth) {             //approach the limit of width and move to next line             lineY += lineHeight + lineSpacing;             lineUsed = mPaddingLeft + mPaddingRight;             lineX = mPaddingLeft;             lineHeight = 0;         }         child.layout(lineX + mlp.leftMargin, lineY + mlp.topMargin, lineX + mlp.leftMargin + childWidth, lineY + mlp.topMargin + childHeight);         if (spaceHeight > lineHeight) {             lineHeight = spaceHeight;         }         lineUsed += spaceWidth;         lineX += spaceWidth;     } }

This code is also easy to understand. You can determine whether to place child elements one by one in the row or by line feed. This step is the same as onMeasure. Basically, all FlowLayout will be rewritten, and I naturally have no special new ideas. I will not focus on these two parts. The following describes the implementation of two layout optimization methods.

Compression implementation

The question of how to implement compression is indeed a headache for me. Because I only have a general concept in my mind, that is, the effect of compression, and this fuzzy concept is difficult to convert into a specific mathematical model. Without a mathematical model, you cannot use code to solve this problem. You just wish to return to the university to re-learn algorithms .. However, there is a clear idea that solving this problem is actually re-sorting sub-elements.
Later, I decided to simplify my thinking and solve the problem by thinking like greedy algorithms, that is, solve the problem row by row, and strive to maximize the full capacity of each row.
1. Starting from the first line, select a part from the child element set so that the child element can occupy the row to the maximum extent;
2. Extract the selected parts from the collection and continue the first operation on the next row.
This idea has been established. How can we select a subset from the set to maximize the occupation of a row?
Known conditions:
1. Child Element Set
2. The width of each row
3. The width of each sub-element
At this time, I thought of the 01 backpack problem in my mind:
Known
1. Item Set
2. Total backpack capacity
3. value of each item
4. Volume of each item
Find the maximum value of a backpack containing items (and their solutions)
A friend may have doubts that the two are indeed similar, but isn't it still a condition? Well, yes .. HoweverIn the current situation, because we want to fill a row as much as possible, the width of each sub-element is not only limited, but also the value.
In this way, the problem is completely the same as that of the 01 backpack. Then you can useDynamic PlanningSolved the problem. I forgot about how to use dynamic planning to solve the backpack problem. I also checked the information on the Internet and reviewed it. So here I will not introduce it myself, nor post my own code (if you are interested, you can go to github to view it) and put a link. I feel that the explanation in this link is very helpful for me to review related knowledge points. If you are interested, you can also take a look at it ~
Knapsack Problem-"01 backpack" explanation and implementation (including solving specific items in the backpack)

Alignment

I first saw this feature on bilibili's ipad client, as shown below.

At that time, I thought it was pretty nice. I thought about how to do it for a while, but I didn't think about it for a moment... To achieve FlowLayout this time, we can easily implement this alignment style with our own ideas.

public void relayoutToAlign() {    int childCount = this.getChildCount();    if (0 == childCount) {        //no need to sort if flowlayout has no child view        return;    }    int count = 0;    for (int i = 0; i < childCount; i++) {        View v = getChildAt(i);        if (v instanceof BlankView) {            //BlankView is just to make childs look in alignment, we should ignore them when we relayout            continue;        }        count++;    }    View[] childs = new View[count];    int[] spaces = new int[count];    int n = 0;    for (int i = 0; i < childCount; i++) {        View v = getChildAt(i);        if (v instanceof BlankView) {            //BlankView is just to make childs look in alignment, we should ignore them when we relayout            continue;        }        childs[n] = v;        MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams();        int childWidth = v.getMeasuredWidth();        spaces[n] = mlp.leftMargin + childWidth + mlp.rightMargin;        n++;    }    int lineTotal = 0;    int start = 0;    this.removeAllViews();    for (int i = 0; i < count; i++) {        if (lineTotal + spaces[i] > usefulWidth) {            int blankWidth = usefulWidth - lineTotal;            int end = i - 1;            int blankCount = end - start;            if (blankCount > 0) {                int eachBlankWidth = blankWidth / blankCount;                MarginLayoutParams lp = new MarginLayoutParams(eachBlankWidth, 0);                for (int j = start; j < end; j++) {                    this.addView(childs[j]);                    BlankView blank = new BlankView(mContext);                    this.addView(blank, lp);                }                this.addView(childs[end]);                start = i;                i --;                lineTotal = 0;            }        } else {            lineTotal += spaces[i];        }    }    for (int i = start; i < count; i++) {        this.addView(childs[i]);    }}

The code is very long, but it is very simple to say. Obtains the list of child elements, starting from scratch, and determines which child elements are in the same row one by one. That is, start to end of each time. Then, calculate the number of sub-elements that are filled with a row, and set it to d. Then the padding between each two child elements is d/(end-start ). If the spacing is set, we certainly cannot change the nature of the child element. Then, you can add a blank view with a width of d/(end-start) between the two child elements.
The definition of this BlankView is as follows:

class BlankView extends View {    public BlankView(Context context) {        super(context);    }}

You see, nothing at all. So what does it mean to write a new class to inherit the View? In fact, we can see from the code alignment above.When traversing the child element of FlowLayout, you can use instance of blamkview to determine whether it is a child element that really needs to be processed and computed, or the supplemental View we added later..

Summary

Not all codes are posted, because all codes are posted on github ~ Here we will post the Project address:
Https://github.com/lankton/android-flowlayout

There are still many things to be optimized for this project. You are welcome to give your comments or suggestions and look forward to using them.
Thank you.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.