How can I do something like a FlowLayout in Android?

后端 未结 9 1297
情歌与酒
情歌与酒 2020-11-22 08:11

How can I do something like a FlowLayout in Android?

相关标签:
9条回答
  • 2020-11-22 08:30

    Here is the custom class where you can achive layout like following with adding dynamicaly view (Also called FlowLayout).

    import android.content.Context;
    import android.util.AttributeSet;
    import android.view.View;
    import android.view.ViewGroup;
    
    /*
    Created By Dhavalkumar Solanki
    * */
    public class FlowLayout extends ViewGroup {
    
        private int line_height_space;
    
        public static class LayoutParams extends ViewGroup.LayoutParams {
    
            public int horizontal_spacing;
            public int vertical_spacing;
    
            /**
             * @param horizontal_spacing Pixels between items, horizontally
             * @param vertical_spacing   Pixels between items, vertically
             */
            public LayoutParams(int horizontal_spacing, int vertical_spacing) {
                super(0, 0);
                this.horizontal_spacing = horizontal_spacing;
                this.vertical_spacing = vertical_spacing;
            }
        }
    
        public FlowLayout(Context context) {
            super(context);
        }
    
        public FlowLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            assert (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED);
    
            final int width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
            int height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom();
            final int count = getChildCount();
            int line_height_space = 0;
    
            int xpos = getPaddingLeft();
            int ypos = getPaddingTop();
    
            int childHeightMeasureSpec;
            if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
            } else {
                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
            }
    
    
            for (int i = 0; i < count; i++) {
                final View child = getChildAt(i);
                if (child.getVisibility() != GONE) {
                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                    child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), childHeightMeasureSpec);
                    final int childw = child.getMeasuredWidth();
                    line_height_space = Math.max(line_height_space, child.getMeasuredHeight() + lp.vertical_spacing);
    
                    if (xpos + childw > width) {
                        xpos = getPaddingLeft();
                        ypos += line_height_space;
                    }
    
                    xpos += childw + lp.horizontal_spacing;
                }
            }
            this.line_height_space = line_height_space;
    
            if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) {
                height = ypos + line_height_space;
    
            } else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
                if (ypos + line_height_space < height) {
                    height = ypos + line_height_space;
                }
            }
            setMeasuredDimension(width, height);
        }
    
        @Override
        protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
            return new LayoutParams(1, 1); // default of 1px spacing
        }
    
        @Override
        protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
            if (p instanceof LayoutParams) {
                return true;
            }
            return false;
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            final int count = getChildCount();
            final int width = r - l;
            int xpos = getPaddingLeft();
            int ypos = getPaddingTop();
    
            for (int i = 0; i < count; i++) {
                final View child = getChildAt(i);
                if (child.getVisibility() != GONE) {
                    final int childw = child.getMeasuredWidth();
                    final int childh = child.getMeasuredHeight();
                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                    if (xpos + childw > width) {
                        xpos = getPaddingLeft();
                        ypos += line_height_space;
                    }
                    child.layout(xpos, ypos, xpos + childw, ypos + childh);
                    xpos += childw + lp.horizontal_spacing;
                }
            }
        }
    }
    

    Example :

    text_view.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tool="http://schemas.android.com/tools"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="5dp">
    
        <TextView
            android:id="@+id/tvText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="19sp"
            android:background="@drawable/unselected_tag"
            android:textColor="@color/colorPrimary"
            tool:text="Temp" />
    </RelativeLayout>
    

    activity_flow_layou_demo.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
    
        >
        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical">
    
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">
    
                    <TextView
                        android:id="@+id/tvTitleBusiness"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="Business Interest "
                        android:textColor="@color/colorPrimary"
                        android:textSize="25sp" />
    
                    <com.example.tristateandroid2.radardemo.FlowLayout
                        android:id="@+id/flowBusiness"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content">
    
                    </com.example.tristateandroid2.radardemo.FlowLayout>
                </LinearLayout>
    
                <LinearLayout
                    android:layout_marginTop="@dimen/activity_horizontal_margin"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">
    
                    <TextView
                        android:id="@+id/tvTitlePrivate"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="Private Interest "
                        android:textColor="@color/colorPrimary"
                        android:textSize="25sp" />
    
                    <com.example.tristateandroid2.radardemo.FlowLayout
                        android:id="@+id/flowPrivate"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content">
    
                    </com.example.tristateandroid2.radardemo.FlowLayout>
                </LinearLayout>
            </LinearLayout>
        </ScrollView>
    </RelativeLayout>
    

    FlowLayouDemo.java

    import android.graphics.Color;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.LinearLayout;
    import android.widget.TextView;
    
    import java.util.ArrayList;
    
    public class FlowLayouDemo extends AppCompatActivity {
        private TextView tvTitleBusiness;
        private FlowLayout flowBusiness;
        private TextView tvTitlePrivate;
        private FlowLayout flowPrivate;
        private ArrayList<TagModel> arrayList;
    
        private void findViews() {
            tvTitleBusiness = (TextView) findViewById(R.id.tvTitleBusiness);
            flowBusiness = (FlowLayout) findViewById(R.id.flowBusiness);
            tvTitlePrivate = (TextView) findViewById(R.id.tvTitlePrivate);
            flowPrivate = (FlowLayout) findViewById(R.id.flowPrivate);
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_flow_layou_demo);
            findViews();
            addLayouts();
        }
    
        private void addLayouts() {
            if (arrayList == null) {
                arrayList = new ArrayList<>();
            }
            flowBusiness.removeAllViews();
            flowPrivate.removeAllViews();
            for (int i = 0; i < 75; i++) {
    
                final boolean[] selected = {false};
                View view = this.getLayoutInflater().inflate(R.layout.text_view, null);
                final TextView textView = (TextView) view.findViewById(R.id.tvText);
                if (i % 5 == 0) {
                    arrayList.add(new TagModel(i, false, "Business VIEW : " + i));
                    textView.setText("Busi VIEW To  IS : " + i);
                } else {
                    arrayList.add(new TagModel(i, false, "TEXT IS : " + i));
                    textView.setText("Busi IS : " + i);
                }
                textView.setBackgroundResource(R.drawable.unselected_tag);
                textView.setTextColor(Color.parseColor("#3F51B5"));
                textView.setTag(i);
                if(i<=50){
                    flowBusiness.addView(view);
                }else {
                    textView.setText("Priv View : "+i);
                    flowPrivate.addView(view);
                }
    
                textView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        if (selected[0]) {
                            selected[0] = false;
                            textView.setBackgroundResource(R.drawable.unselected_tag);
                            textView.setTextColor(Color.parseColor("#3F51B5"));
                        } else {
                            selected[0] = true;
                            textView.setBackgroundResource(R.drawable.selected_tag);
                            textView.setTextColor(Color.parseColor("#FFFFFF"));
                        }
                    }
                });
    
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 08:33

    I don't have enough reputation to post a comment to Romain Guy's answer but that's where this answer should be (I created an account just to share my edit).

    Anyway, I see other people have found out his pretty cool FlowLayout solution has some issues. I could find one myself and I saw, as others, that some children were clipped. Looking in details at the algorithm it seems to be a very simple mistake in the calculation of the height. When the very last child is the one being put on a new line, then the height was not properly computed. I cleaned up a bit the computation (there was a weird use of "height" vs. currentHeight).

    The following change fixes the problem of "last child is clipped if on a new line":

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        int widthLimit = MeasureSpec.getSize(widthMeasureSpec) - getPaddingRight();
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    
        boolean growHeight = widthMode != MeasureSpec.UNSPECIFIED;
    
        int width = 0;
    
        int currentWidth = getPaddingLeft();
        int currentHeight = getPaddingTop();
    
        int maxChildHeight = 0;
    
        boolean breakLine = false;
        boolean newLine = false;
        int spacing = 0;
    
        final int count = getChildCount();
        for (int i = 0; i < count; i++)
        {
            View child = getChildAt(i);
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
    
            LayoutParams lp = (LayoutParams) child.getLayoutParams();
            spacing = mHorizontalSpacing;
    
            if (lp.horizontalSpacing >= 0)
            {
                spacing = lp.horizontalSpacing;
            }
    
            if (growHeight && (breakLine || ((currentWidth + child.getMeasuredWidth()) > widthLimit)))
            {               
                newLine = true;
                currentHeight += maxChildHeight + mVerticalSpacing;
    
                width = Math.max(width, currentWidth - spacing);
    
                currentWidth = getPaddingLeft();
                maxChildHeight = 0;
            }
            else
            {
                newLine = false;
            }
    
            maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
    
            lp.x = currentWidth;
            lp.y = currentHeight;
    
            currentWidth += child.getMeasuredWidth() + spacing;
    
            breakLine = lp.breakLine;
        }
    
        if (newLine == false)
        {
            width = Math.max(width, currentWidth - spacing);
        }
    
        width += getPaddingRight();
        int height = currentHeight + maxChildHeight + getPaddingBottom();
    
        setMeasuredDimension(resolveSize(width, widthMeasureSpec),
                resolveSize(height, heightMeasureSpec));
    }
    
    0 讨论(0)
  • 2020-11-22 08:34

    You should use FlexboxLayout with flexWrap="wrap" attribute.

    <com.google.android.flexbox.FlexboxLayout 
         android:layout_width="match_parent" 
         android:layout_height="wrap_content" 
         app:flexWrap="wrap">
    
    <!-- contents go here -->
    
    </com.google.android.flexbox.FlexboxLayout>
    

    For build instructions, see the github repo.

    More about this - https://android-developers.googleblog.com/2017/02/build-flexible-layouts-with.html

    0 讨论(0)
  • 2020-11-22 08:36

    A revision to @MattNotEquals() FlowLayout that supports MarginLayoutParams.

    This is just a minimalist implementation of MarginLayoutParms to support left, right, top, and bottom margins.

    import android.content.Context;
    import android.support.annotation.NonNull;
    import android.support.annotation.Nullable;
    import android.util.AttributeSet;
    import android.view.View;
    import android.view.ViewGroup;
    
    /**
     *  Original version courtesy of MattNotEquals() at http://stackoverflow.com/a/34169798/4515489 - 4/13/17.
     *  7/15/17 Revised to support MarginLayoutParams.
     */
    public class FlowLayout extends ViewGroup {
        // Custom layout that wraps child views to a new line.
    
        public FlowLayout(Context context) {
            super(context);
        }
    
        public FlowLayout(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int childLeft = getPaddingLeft();
            int childTop = getPaddingTop();
            int lowestBottom = 0;
            int lineHeight = 0;
            int myWidth = resolveSize(100, widthMeasureSpec);
            int wantedHeight = 0;
            for (int i = 0; i < getChildCount(); i++) {
                final View child = getChildAt(i);
                if (child.getVisibility() == View.GONE) {
                    continue;
                }
                child.measure(getChildMeasureSpec(widthMeasureSpec, 0, child.getLayoutParams().width),
                              getChildMeasureSpec(heightMeasureSpec, 0, child.getLayoutParams().height));
                int childWidth = child.getMeasuredWidth();
                int childHeight = child.getMeasuredHeight();
                lineHeight = Math.max(childHeight, lineHeight);
    
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                childLeft += lp.leftMargin;
                childTop += lp.topMargin;
                if (childLeft + childWidth + lp.rightMargin + getPaddingRight() > myWidth) { // Wrap this line
                    childLeft = getPaddingLeft() + lp.leftMargin;
                    childTop = lowestBottom + lp.topMargin; // Spaced below the previous lowest point
                    lineHeight = childHeight;
                }
                childLeft += childWidth + lp.rightMargin;
    
                if (childTop + childHeight + lp.bottomMargin > lowestBottom) { // New lowest point
                    lowestBottom = childTop + childHeight + lp.bottomMargin;
                }
            }
            wantedHeight += lowestBottom + getPaddingBottom(); // childTop + lineHeight + getPaddingBottom();
            setMeasuredDimension(myWidth, resolveSize(wantedHeight, heightMeasureSpec));
        }
    
        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            int childLeft = getPaddingLeft();
            int childTop = getPaddingTop();
            int lowestBottom = 0;
            int myWidth = right - left;
            for (int i = 0; i < getChildCount(); i++) {
                final View child = getChildAt(i);
                if (child.getVisibility() == View.GONE) {
                    continue;
                }
                int childWidth = child.getMeasuredWidth();
                int childHeight = child.getMeasuredHeight();
    
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                childLeft += lp.leftMargin;
                childTop += lp.topMargin;
                if (childLeft + childWidth + lp.rightMargin + getPaddingRight() > myWidth) { // Wrap this line
                    childLeft = getPaddingLeft() + lp.leftMargin;
                    childTop = lowestBottom + lp.topMargin; // Spaced below the previous lowest point
                }
                child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
                childLeft += childWidth + lp.rightMargin;
    
                if (childTop + childHeight + lp.bottomMargin > lowestBottom) { // New lowest point
                    lowestBottom = childTop + childHeight + lp.bottomMargin;
                }
            }
        }
    
        @Override
        public boolean shouldDelayChildPressedState() {
            return false;
        }
    
        @Override
        protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
            return p instanceof LayoutParams;
        }
    
        @Override
        protected LayoutParams generateDefaultLayoutParams() {
            return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        }
    
        @Override
        public LayoutParams generateLayoutParams(AttributeSet attrs) {
            return new FlowLayout.LayoutParams(getContext(), attrs);
        }
    
        @Override
        protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
            if (lp instanceof LayoutParams) {
                return new LayoutParams((LayoutParams) lp);
            }
            else if (lp instanceof MarginLayoutParams) {
                return new LayoutParams((MarginLayoutParams) lp);
            }
            else
                return super.generateLayoutParams(lp);
        }
    
        /**
         * Per-child layout information for layouts that support margins.
         */
        public static class LayoutParams extends MarginLayoutParams {
            public LayoutParams(@NonNull Context c, @Nullable AttributeSet attrs) {
                super(c, attrs);
            }
            public LayoutParams(int width, int height) {
                super(width, height);
            }
            public LayoutParams(@NonNull ViewGroup.LayoutParams source) {
                super(source);
            }
            public LayoutParams(@NonNull ViewGroup.MarginLayoutParams source) {
                super(source);
            }
            public LayoutParams(@NonNull LayoutParams source) {
                super(source);
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 08:39

    If you watch the talk I gave at the Devoxx University day (available on parleys.com) you will learn how to do it yourself. During the talk I wrote a FlowLayout implementation live on stage to show how simple it is to write custom layouts.

    The implementation is hosted here.

    0 讨论(0)
  • 2020-11-22 08:39

    There is now built in support in ConstraintLayout using a Flow widget. It has many options that can be used to achieve many type of flows.

    Example:

    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <androidx.constraintlayout.helper.widget.Flow
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:constraint_referenced_ids="item_1,item_2,item_3"
        app:flow_horizontalBias="0"
        app:flow_horizontalGap="10dp"
        app:flow_horizontalStyle="packed"
        app:flow_verticalGap="8dp"
        app:flow_wrapMode="aligned"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    
    <View
        android:id="@+id/item_1"
        android:layout_width="50dp"
        android:layout_height="50dp" />
    
    <View
        android:id="@+id/item_2"
        android:layout_width="50dp"
        android:layout_height="50dp" />
    
    <View
        android:id="@+id/item_3"
        android:layout_width="50dp"
        android:layout_height="50dp" />
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    Take a look in this post: https://medium.com/@tapanrgohil/constraintlayout-flow-bye-bye-to-linerlayout-78fd7fa9b679

    And here: https://www.bignerdranch.com/blog/constraintlayout-flow-simple-grid-building-without-nested-layouts/

    0 讨论(0)
提交回复
热议问题