Android - LinearLayout Horizontal with wrapping children

Android

Android Problem Overview


Is there a property to set for Android's LinearLayout that will enable it to properly wrap child controls?

Meaning - I have changeable number of children and would like to lay out them horizontally like:

Example: Control1, Control2, Control3, ...

I do that by setting:

ll.setOrientation(LinearLayout.HORIZONTAL);
foreach (Child c in children)
ll.addView(c);

However, if I have large number of children, last one gets cuts off, instead of going to next line.

Any idea how this can be fixed?

Android Solutions


Solution 1 - Android

As of May 2016 Google has created its own FlexBoxLayout which should solve your problem.

You can find the GitHub repo here: https://github.com/google/flexbox-layout

Solution 2 - Android

This should be what you want:

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
 
/**
 *
 * @author RAW
 */
public class FlowLayout extends ViewGroup {
 
    private int line_height;
 
    public static class LayoutParams extends ViewGroup.LayoutParams {
 
        public final int horizontal_spacing;
        public final 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 = 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 = Math.max(line_height, child.getMeasuredHeight() + lp.vertical_spacing);
 
                if (xpos + childw > width) {
                    xpos = getPaddingLeft();
                    ypos += line_height;
                }
 
                xpos += childw + lp.horizontal_spacing;
            }
        }
        this.line_height = line_height;
 
        if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) {
            height = ypos + line_height;
 
        } else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
            if (ypos + line_height < height) {
                height = ypos + line_height;
            }
        }
        setMeasuredDimension(width, height);
    }
 
    @Override
    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(1, 1); // default of 1px spacing
    }

    @Override
    protected android.view.ViewGroup.LayoutParams generateLayoutParams(
        android.view.ViewGroup.LayoutParams p) {
        return new LayoutParams(1, 1, p);
    }
 
    @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;
                }
                child.layout(xpos, ypos, xpos + childw, ypos + childh);
                xpos += childw + lp.horizontal_spacing;
            }
        }
    }
}

and the XML file

/* you must write your package name and class name */
<org.android.FlowLayout
                android:id="@+id/flow_layout"
                android:layout_marginLeft="5dip"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"/>

Solution 3 - Android

For anyone who needs this kind of behaviour:

private void populateLinks(LinearLayout ll, ArrayList<Sample> collection, String header) {

	Display display = getWindowManager().getDefaultDisplay();
	int maxWidth = display.getWidth() - 10;

	if (collection.size() > 0) {
		LinearLayout llAlso = new LinearLayout(this);
		llAlso.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
				LayoutParams.WRAP_CONTENT));
		llAlso.setOrientation(LinearLayout.HORIZONTAL);

		TextView txtSample = new TextView(this);
		txtSample.setText(header);

		llAlso.addView(txtSample);
		txtSample.measure(0, 0);

		int widthSoFar = txtSample.getMeasuredWidth();
		for (Sample samItem : collection) {
			TextView txtSamItem = new TextView(this, null,
					android.R.attr.textColorLink);
			txtSamItem.setText(samItem.Sample);
			txtSamItem.setPadding(10, 0, 0, 0);
			txtSamItem.setTag(samItem);
			txtSamItem.setOnClickListener(new OnClickListener() {
				@Override
				public void onClick(View v) {
					TextView self = (TextView) v;
					Sample ds = (Sample) self.getTag();

					Intent myIntent = new Intent();
					myIntent.putExtra("link_info", ds.Sample);
					setResult("link_clicked", myIntent);
					finish();
				}
			});

			txtSamItem.measure(0, 0);
			widthSoFar += txtSamItem.getMeasuredWidth();

			if (widthSoFar >= maxWidth) {
				ll.addView(llAlso);

				llAlso = new LinearLayout(this);
				llAlso.setLayoutParams(new LayoutParams(
						LayoutParams.FILL_PARENT,
						LayoutParams.WRAP_CONTENT));
				llAlso.setOrientation(LinearLayout.HORIZONTAL);

				llAlso.addView(txtSamItem);
				widthSoFar = txtSamItem.getMeasuredWidth();
			} else {
				llAlso.addView(txtSamItem);
			}
		}

		ll.addView(llAlso);
	}
}

Solution 4 - Android

Old question, but in case someone ends up here, two libraries that do exactly that:

https://github.com/blazsolar/FlowLayout

https://github.com/ApmeM/android-flowlayout

Solution 5 - Android

Looking for solution for similar but simpler problem, that is to wrap child text content in horizontal layout. kape123's solution works fine. But find a simpler one for this problem, using ClickableSpan. Maybe it could be useful for some simple case. snippet:

        String[] stringSource = new String[sourceList.size()];
        for (int i = 0; c < sourceList.size(); i++) {
            String text = sourceList.get(i);
            stringSource[i] = text;
        }
        
        SpannableString totalContent = new SpannableString(TextUtils.join(",", stringSource));
        int start = 0;
        for (int j = 0; j < stringSource.length(); j++) {
            final String text = stringSource[j];
            ClickableSpan span = new ClickableSpan() {

	    @Override
                public void updateDrawState(TextPaint ds) {
                    ds.setUnderlineText(true);
                    ds.setColor(getResources().getColor(R.color.green));
                }
                @Override
                public void onClick(View widget) {
                    // the text clicked
                }
            };
	int end = (start += text.length());
            totalContent.setSpan(span, start, end, 0);
            star = end + 1;
        }

        TextView wrapperView = (TextView) findViewById(horizontal_container_id);
        wrapperView.setMovementMethod(LinkMovementMethod.getInstance());

        wrapperView.setText(totalContent, BufferType.SPANNABLE);
    }

Solution 6 - Android

In the past many custom solutions and libraries tried and indeed solved this problem.

Starting with Constraint Layout 2.0 we can now use Flow

> Flow is a new virtual layout for building chains that can wrap to the next line, or even another section of the screen, when they run out of room. This is useful when you’re laying out multiple items in a chain but you’re not quite sure how big the container will be at runtime. You can use this to build your layout based on a dynamic size in your application, like screen width on rotation.

Here is how the xml will look like:

<androidx.constraintlayout.helper.widget.Flow
   android:layout_width="0dp"
   android:layout_height="wrap_content"
   app:layout_constraintStart_toStartOf="parent"
   app:layout_constraintEnd_toEndOf="parent"
   app:layout_constraintTop_toTopOf="parent"
   app:flow_wrapMode="chain"
   app:constraint_referenced_ids="card1, card2, card3"
   />

Notice app:constraint_referenced_ids and app:flow_wrapMode properties.

We pass the views using the first one and we choose how to wrap them with the second.

app:flow_wrapMode accepts 3 different options:

none: create a single chain, overflowing if the content doesn’t fit

chain: on overflow, create add another chain for the overflow elements

align: similar to chain, but align rows into columns

For more details check Android Developers post

and the official docs

Solution 7 - Android

A modified version of the code from Randy Sugianto 'Yuku's answer and what I finally went with:

import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.View.MeasureSpec.*
import android.view.ViewGroup
import androidx.core.content.withStyledAttributes
import androidx.core.view.children
import *.*.*.R


class FlowLayout(context: Context, attributeSet: AttributeSet) : ViewGroup(context, attributeSet) {

    private var lineHeight: Int = 0

    private var horizontalSpacing = 0F
    private var verticalSpacing = 0F

    init {
        context.withStyledAttributes(attributeSet, R.styleable.FlowLayout) {
            horizontalSpacing = getDimension(R.styleable.FlowLayout_horizontalSpacing, 0F)
            verticalSpacing = getDimension(R.styleable.FlowLayout_verticalSpacing, 0F)
        }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {

        val width = getSize(widthMeasureSpec) - paddingLeft - paddingRight
        var height = getSize(heightMeasureSpec) - paddingTop - paddingBottom

        var xPosition = paddingLeft
        var yPosition = paddingTop

        val childHeightMeasureSpec = makeMeasureSpec(
            height, if (getMode(heightMeasureSpec) == AT_MOST) AT_MOST else UNSPECIFIED
        )

        children.forEach { child ->
            if (child.visibility != GONE) {
                val layoutParams = child.layoutParams as LayoutParamsWithSpacing
                child.measure(makeMeasureSpec(width, AT_MOST), childHeightMeasureSpec)
                val childWidth = child.measuredWidth
                lineHeight =
                        Math.max(lineHeight, child.measuredHeight + layoutParams.verticalSpacing)

                if (xPosition + childWidth > width) {
                    xPosition = paddingLeft
                    yPosition += lineHeight
                }

                xPosition += childWidth + layoutParams.horizontalSpacing
            }
        }

        if (getMode(heightMeasureSpec) == UNSPECIFIED ||
            getMode(heightMeasureSpec) == AT_MOST && yPosition + lineHeight < height
        ) {
            height = yPosition + lineHeight
        }

        setMeasuredDimension(width, height)
    }

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        val width = right - left
        var xPosition = paddingLeft
        var yPosition = paddingTop

        children.forEach { child ->
            if (child.visibility != View.GONE) {
                val layoutParams = child.layoutParams as LayoutParamsWithSpacing
                val childWidth = child.measuredWidth
                if (xPosition + childWidth > width) {
                    xPosition = paddingLeft
                    yPosition += lineHeight
                }
                child.layout(
                    xPosition, yPosition, xPosition + childWidth,
                    yPosition + child.measuredHeight
                )
                xPosition += layoutParams.horizontalSpacing
                xPosition += childWidth
            }
        }
    }

    override fun generateDefaultLayoutParams(): ViewGroup.LayoutParams =
        LayoutParamsWithSpacing(1, 1)

    override fun generateLayoutParams(layoutParams: LayoutParams) =
        LayoutParamsWithSpacing(horizontalSpacing.toInt(), verticalSpacing.toInt())

    override fun checkLayoutParams(layoutParams: LayoutParams) =
        layoutParams is LayoutParamsWithSpacing

    class LayoutParamsWithSpacing(val horizontalSpacing: Int, val verticalSpacing: Int) :
        ViewGroup.LayoutParams(0, 0)
}

In the style/attrs.xml file:

<resources>
    <declare-styleable name="FlowLayout">
        <attr name="horizontalSpacing" format="dimension" />
        <attr name="verticalSpacing" format="dimension" />
    </declare-styleable>
</resources>

Usage:

<*.*.*.*.FlowLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:horizontalSpacing="8dp"
    app:verticalSpacing="8dp">

    <!-- ... -->

</*.*.*.*.FlowLayout>

Solution 8 - Android

I wanted a very simple solution that was flexible (which is why I use LinearLayouts). This is what I came up with.

https://github.com/ShalakoSnell/Wrapping_Linear_Layout

Note: I have included an example method using textviews (see textViewArrayListForExample()) The XML is just a parent view LinearLayout with id and vertical orientation, nothing else required. To use: pass in an array of views that are wrapped in LinearLayouts, along with the parent view and the context. (see viewAdapterArrayList(ArrayList textViews))

Passing in an array of LinearLayouts is what makes this approach so flexible, as it allows you to add different view types. So in the first LinearLayout you could have text and in the second you could have an image then in the third a button and so on...

Portrait example Landscape example 50dp margins in verticalLinearLayout (sorry I can't add images yet... see the links.

MainActivity.java

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

        new WrappingLinearLayout(
                viewAdapterArrayList(textViewArrayListForExample()), // <-- replace this with you own array of LinearLayouts
                (LinearLayout) findViewById(R.id.verticalLinearLayout),
                this);
    }

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/verticalLinearLayout"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

</LinearLayout>

WrappingLinearLayout.Java

package com.example.wrapping_linear_layout;

import android.content.Context;
import android.widget.LinearLayout;

import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;

public class WrappingLinearLayout {

    public WrappingLinearLayout(@NotNull final ArrayList<LinearLayout> views, @NotNull final LinearLayout verticalLinearLayout, @NotNull final Context context) {
        verticalLinearLayout.post(new Runnable() {
            @Override
            public void run() {
                execute(views, verticalLinearLayout, context);
            }
        });

    }

    private void execute(@NotNull ArrayList<LinearLayout> views, @NotNull final LinearLayout verticalLinearLayout, @NotNull final Context context) {

        ArrayList<LinearLayout> horizontalLinearLayouts = new ArrayList<>();
        LinearLayout horizontalLinearLayout = new LinearLayout(context);
        horizontalLinearLayouts.add(horizontalLinearLayout);
        int verticalLinearLayoutWidth = verticalLinearLayout.getMeasuredWidth()
                - (verticalLinearLayout.getPaddingLeft()
                        + verticalLinearLayout.getPaddingRight());

        int totalWidthOfViews = 0;

        for (LinearLayout view : views) {

            view.measure(0, 0);
            int currentViewWidth = view.getMeasuredWidth();
            if (totalWidthOfViews + view.getMeasuredWidth() > verticalLinearLayoutWidth) {
                horizontalLinearLayout = new LinearLayout(context);
                horizontalLinearLayouts.add(horizontalLinearLayout);
                totalWidthOfViews = 0;
            }

            totalWidthOfViews += currentViewWidth;

            horizontalLinearLayout.addView(view);
        }

        for (LinearLayout linearLayout : horizontalLinearLayouts) {
            verticalLinearLayout.addView(linearLayout);
        }
    }

}

Additional code for example use case:

    private ArrayList<LinearLayout> viewAdapterArrayList(ArrayList<TextView> textViews) {
        ArrayList<LinearLayout> views = new ArrayList<>();
        for (TextView textView : textViews) {
            LinearLayout linearLayout = new LinearLayout(this);
            linearLayout.addView(textView);
            views.add(linearLayout);
        }
        return views;
    }

    private ArrayList<TextView> textViewArrayListForExample() {
        ArrayList<TextView> textViews = new ArrayList<>();
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
        );
        for (int i = 0; i < 40; i++) {
            TextView textView = new TextView(this);
            textView.setText("View " + i + " |");
            if (i < 20) {
                if (i % 5 == 0) {
                    textView.setText("View longer view " + i + " |");
                } else if (i % 7 == 0) {
                    textView.setText("View different length view " + i + " |");
                } else if (i % 9 == 0) {
                    textView.setText("View very long view that is so long it's really long " + i + " |");
                }
            }
            textView.setMaxLines(1);
            textView.setBackground(new ColorDrawable(Color.BLUE));
            textView.setTextColor(Color.WHITE);
            textView.setLayoutParams(layoutParams);
            textView.setPadding(20, 2, 20, 2);
            layoutParams.setMargins(10, 2, 10, 2);
            textViews.add(textView);
        }
        return textViews;
    }
}

Solution 9 - Android

//this method will add image view to liner grid and warp it if no space in new child LinearLayout grid 
private void addImageToLinyerLayout(LinearLayout ll , ImageView v)
{
	//set the padding and margin and weight 
	v.setPadding(5, 5, 5, 5);
	
	Display display = getWindowManager().getDefaultDisplay();
    int maxWidth = display.getWidth() - 10;
	int maxChildeNum = (int) ( maxWidth / (110)) ; 
	Toast.makeText(getBaseContext(), "c" + v.getWidth() ,
			Toast.LENGTH_LONG).show();
	//loop through all child of the LinearLayout
	for (int i = 0; i < ll.getChildCount(); i++) {
        View chidv = ll.getChildAt(i);
        Class c = chidv.getClass();
        if (c == LinearLayout.class) {
            //here we are in the child lay out check to add the imageView if there is space 
        	//Available else we will add it to new linear layout 
        	LinearLayout chidvL = (LinearLayout)chidv; 
        	if(chidvL.getChildCount() < maxChildeNum)
        	{
        		chidvL.addView(v);
        		return;
        	}
        } else{
           continue;
        } 
    }
	
	//if you reached here this means there was no roam for adding view so we will 
	//add new linear layout 
    LinearLayout childLinyer = new LinearLayout(this);
    childLinyer.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
            LayoutParams.WRAP_CONTENT));
    
    childLinyer.setOrientation(LinearLayout.HORIZONTAL);
    ll.addView(childLinyer);
    childLinyer.addView(v);
	
}

the above method will add the imgeview side by side like agrid and in your layout

  <LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" 
    android:id="@+id/imageslayout"
    ></LinearLayout>

i post this solution may it help some one and save some one time and i use it in my app

Solution 10 - Android

I ended up using a [TagView][1]:

<com.cunoraz.tagview.TagView
        android:id="@+id/tag_group"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp" />




  TagView tagGroup = (TagView)findviewById(R.id.tag_view);
 //You can add one tag
 tagGroup.addTag(Tag tag);
 //You can add multiple tag via ArrayList
 tagGroup.addTags(ArrayList<Tag> tags);
 //Via string array
 addTags(String[] tags);


   //set click listener
      tagGroup.setOnTagClickListener(new OnTagClickListener() {
            @Override
            public void onTagClick(Tag tag, int position) {
            }
        });

   //set delete listener
            tagGroup.setOnTagDeleteListener(new OnTagDeleteListener() {
            @Override
            public void onTagDeleted(final TagView view, final Tag tag, final int position) {
            }
        });

[![enter image description here][2]][2]

[1]: https://github.com/Cutta/TagView "TagView" [2]: http://i.stack.imgur.com/fXTEY.png

Solution 11 - Android

Google offers its own solution: class FlowLayout

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.google.android.material.internal;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.MeasureSpec;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewGroup.MarginLayoutParams;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.core.view.MarginLayoutParamsCompat;
import androidx.core.view.ViewCompat;
import com.google.android.material.R.styleable;

@RestrictTo({Scope.LIBRARY_GROUP})
public class FlowLayout extends ViewGroup {
  private int lineSpacing;
  private int itemSpacing;
  private boolean singleLine;

  public FlowLayout(Context context) {
    this(context, (AttributeSet)null);
  }

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

  public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    this.singleLine = false;
    this.loadFromAttributes(context, attrs);
  }

  @TargetApi(21)
  public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    this.singleLine = false;
    this.loadFromAttributes(context, attrs);
  }

  private void loadFromAttributes(Context context, AttributeSet attrs) {
    TypedArray array = context.getTheme().obtainStyledAttributes(attrs, styleable.FlowLayout, 0, 0);
    this.lineSpacing = array.getDimensionPixelSize(styleable.FlowLayout_lineSpacing, 0);
    this.itemSpacing = array.getDimensionPixelSize(styleable.FlowLayout_itemSpacing, 0);
    array.recycle();
  }

  protected int getLineSpacing() {
    return this.lineSpacing;
  }

  protected void setLineSpacing(int lineSpacing) {
    this.lineSpacing = lineSpacing;
  }

  protected int getItemSpacing() {
    return this.itemSpacing;
  }

  protected void setItemSpacing(int itemSpacing) {
    this.itemSpacing = itemSpacing;
  }

  protected boolean isSingleLine() {
    return this.singleLine;
  }

  public void setSingleLine(boolean singleLine) {
    this.singleLine = singleLine;
  }

  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int maxWidth = widthMode != -2147483648 && widthMode != 1073741824 ? 2147483647 : width;
    int childLeft = this.getPaddingLeft();
    int childTop = this.getPaddingTop();
    int childBottom = childTop;
    int maxChildRight = 0;
    int maxRight = maxWidth - this.getPaddingRight();

    int finalWidth;
    for(finalWidth = 0; finalWidth < this.getChildCount(); ++finalWidth) {
      View child = this.getChildAt(finalWidth);
      if (child.getVisibility() != 8) {
        this.measureChild(child, widthMeasureSpec, heightMeasureSpec);
        LayoutParams lp = child.getLayoutParams();
        int leftMargin = 0;
        int rightMargin = 0;
        if (lp instanceof MarginLayoutParams) {
          MarginLayoutParams marginLp = (MarginLayoutParams)lp;
          leftMargin += marginLp.leftMargin;
          rightMargin += marginLp.rightMargin;
        }

        int childRight = childLeft + leftMargin + child.getMeasuredWidth();
        if (childRight > maxRight && !this.isSingleLine()) {
          childLeft = this.getPaddingLeft();
          childTop = childBottom + this.lineSpacing;
        }

        childRight = childLeft + leftMargin + child.getMeasuredWidth();
        childBottom = childTop + child.getMeasuredHeight();
        if (childRight > maxChildRight) {
          maxChildRight = childRight;
        }

        childLeft += leftMargin + rightMargin + child.getMeasuredWidth() + this.itemSpacing;
      }
    }

    finalWidth = getMeasuredDimension(width, widthMode, maxChildRight);
    int finalHeight = getMeasuredDimension(height, heightMode, childBottom);
    this.setMeasuredDimension(finalWidth, finalHeight);
  }

  private static int getMeasuredDimension(int size, int mode, int childrenEdge) {
    switch(mode) {
    case -2147483648:
      return Math.min(childrenEdge, size);
    case 1073741824:
      return size;
    default:
      return childrenEdge;
    }
  }

  protected void onLayout(boolean sizeChanged, int left, int top, int right, int bottom) {
    if (this.getChildCount() != 0) {
      boolean isRtl = ViewCompat.getLayoutDirection(this) == 1;
      int paddingStart = isRtl ? this.getPaddingRight() : this.getPaddingLeft();
      int paddingEnd = isRtl ? this.getPaddingLeft() : this.getPaddingRight();
      int childStart = paddingStart;
      int childTop = this.getPaddingTop();
      int childBottom = childTop;
      int maxChildEnd = right - left - paddingEnd;

      for(int i = 0; i < this.getChildCount(); ++i) {
        View child = this.getChildAt(i);
        if (child.getVisibility() != 8) {
          LayoutParams lp = child.getLayoutParams();
          int startMargin = 0;
          int endMargin = 0;
          if (lp instanceof MarginLayoutParams) {
            MarginLayoutParams marginLp = (MarginLayoutParams)lp;
            startMargin = MarginLayoutParamsCompat.getMarginStart(marginLp);
            endMargin = MarginLayoutParamsCompat.getMarginEnd(marginLp);
          }

          int childEnd = childStart + startMargin + child.getMeasuredWidth();
          if (!this.singleLine && childEnd > maxChildEnd) {
            childStart = paddingStart;
            childTop = childBottom + this.lineSpacing;
          }

          childEnd = childStart + startMargin + child.getMeasuredWidth();
          childBottom = childTop + child.getMeasuredHeight();
          if (isRtl) {
            child.layout(maxChildEnd - childEnd, childTop, maxChildEnd - childStart - startMargin, childBottom);
          } else {
            child.layout(childStart + startMargin, childTop, childEnd, childBottom);
          }

          childStart += startMargin + endMargin + child.getMeasuredWidth() + this.itemSpacing;
        }
      }

    }
  }
}

This class works right similar to class FlowLayout described above But you shouldn't add any new class to your project, and designer works better with this class than with custom

Solution 12 - Android

You can use ConstraintLayout and Flow.
Here is a sample layout.xml.
flow_wrapMode : chain
flow_verticalGap, flow_horizontalGap: gap
flow_maxElementsWrap : max columns

<androidx.constraintlayout.helper.widget.Flow
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    app:constraint_referenced_ids="process_type1, process_type2, process_type3, process_type4, process_type5"
    app:flow_horizontalAlign="start"
    app:flow_wrapMode="chain"
    app:flow_verticalGap="10dp"
    app:flow_horizontalGap="10dp"
    app:flow_horizontalBias="0"
    app:flow_horizontalStyle="packed"
    app:flow_maxElementsWrap="2"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.0"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
	<ToggleButton
	    android:id="@+id/process_type1"
	    android:layout_width="wrap_content"
	    android:layout_height="32dp"
	    android:paddingHorizontal="10dp"
	    android:checked="false"
	    style="@style/BlueGrayRoundCheckButton"
	    android:textSize="16sp"
	    android:textOff="@string/process_type1"
	    android:textOn="@string/process_type1"/>
	<ToggleButton
	    android:id="@+id/process_type2"
	    android:layout_width="wrap_content"
	    android:layout_height="32dp"
	    android:layout_marginLeft="@dimen/dim10"
	    android:paddingHorizontal="10dp"
	    android:checked="false"
	    style="@style/BlueGrayRoundCheckButton"
	    android:textSize="16sp"
	    android:textOff="@string/process_type2"
	    android:textOn="@string/process_type2"/>
	<ToggleButton
	    android:id="@+id/process_type3"
	    android:layout_width="wrap_content"
	    android:layout_height="32dp"
	    android:paddingHorizontal="10dp"
	    android:checked="false"
	    style="@style/BlueGrayRoundCheckButton"
	    android:textSize="16sp"
	    android:textOff="@string/process_type3"
	    android:textOn="@string/process_type3"/>
	<ToggleButton
	    android:id="@+id/process_type4"
	    android:layout_width="wrap_content"
	    android:layout_height="32dp"
	    android:layout_marginLeft="@dimen/dim10"
	    android:paddingHorizontal="10dp"
	    android:checked="false"
	    style="@style/BlueGrayRoundCheckButton"
	    android:textSize="16sp"
	    android:textOff="@string/process_type4"
	    android:textOn="@string/process_type4"/>
	<ToggleButton
	    android:id="@+id/process_type5"
	    android:layout_width="wrap_content"
	    android:layout_height="32dp"
	    android:layout_marginLeft="@dimen/dim10"
	    android:paddingHorizontal="10dp"
	    android:checked="false"
	    style="@style/BlueGrayRoundCheckButton"
	    android:textSize="16sp"
	    android:textOff="@string/process_type5"
	    android:textOn="@string/process_type5"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionnikib3roView Question on Stackoverflow
Solution 1 - AndroidmysteryhoboView Answer on Stackoverflow
Solution 2 - AndroidRandy Sugianto 'Yuku'View Answer on Stackoverflow
Solution 3 - Androidnikib3roView Answer on Stackoverflow
Solution 4 - AndroidkitoView Answer on Stackoverflow
Solution 5 - AndroidlannyfView Answer on Stackoverflow
Solution 6 - AndroidSarantis TofasView Answer on Stackoverflow
Solution 7 - AndroidjanoschView Answer on Stackoverflow
Solution 8 - AndroidrsmediapcView Answer on Stackoverflow
Solution 9 - AndroidM.Ali El-SayedView Answer on Stackoverflow
Solution 10 - AndroidAlberto MView Answer on Stackoverflow
Solution 11 - AndroidAlexander GavriliukView Answer on Stackoverflow
Solution 12 - AndroidZhmingView Answer on Stackoverflow