I want to display multiple text view in a layout dynamically.
Suppose,User selects 10 items from drop down menu then I can display it to layout one after another.I need same view as attached image -
I have selected item in a list,now I am not getting how to display data in desire manner.When I try list view all data are display one after other vertically.
When I use Linearlayout data can be either added in horizontal or vertical order.So I am not getting correct way to display data.
Please help me in solving this issue.
Thanks in advance.
what you are looking for is called chips.why not use material design chips, you can refer it here. http://www.google.com/design/spec/components/chips.html#
you can implement like using grid view if you are not interested using chips just have one row of gridview and populate it there with one cell
Normally if you use LinearLayout with horizontal orientation, it will put your items as you want.
try this one..i used to acheive using custom view.if you are ok with this just give a try..
Custom View class is.
public class WrapLayout extends ViewGroup {
private int paddingHorizontal;
private int paddingVertical;
public WrapLayout(Context context) {
super(context);
init();
}
public WrapLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WrapLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
paddingHorizontal = getResources().getDimensionPixelSize(10);
paddingVertical = getResources().getDimensionPixelSize(10);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int childLeft = getPaddingLeft();
int childTop = getPaddingTop();
int lineHeight = 0;
// 100 is a dummy number, widthMeasureSpec should always be EXACTLY for FlowLayout
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;
}
// let the child measure itself
child.measure(
getChildMeasureSpec(widthMeasureSpec, 0, child.getLayoutParams().width),
getChildMeasureSpec(heightMeasureSpec, 0, child.getLayoutParams().height));
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
// lineheight is the height of current line, should be the height of the heightest view
lineHeight = Math.max(childHeight, lineHeight);
if (childWidth + childLeft + getPaddingRight() > myWidth) {
// wrap this line
childLeft = getPaddingLeft();
childTop += paddingVertical + lineHeight;
lineHeight = childHeight;
}
childLeft += childWidth + paddingHorizontal;
}
wantedHeight += 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 lineHeight = 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();
lineHeight = Math.max(childHeight, lineHeight);
if (childWidth + childLeft + getPaddingRight() > myWidth) {
childLeft = getPaddingLeft();
childTop += paddingVertical + lineHeight;
lineHeight = childHeight;
}
child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
childLeft += childWidth + paddingHorizontal;
}
}
}
and my xml is :
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/container"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/editText_sample"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add It"
android:id="#+id/addit"
/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- i mean where you put the WrapLayout class in your project that package name -->
<your.package.name.WrapLayout
android:id="#+id/flow_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"/>
</ScrollView>
</LinearLayout>
And finally in MainActivity
ViewGroup flowContainer;
In OnCreate
flowContainer= (ViewGroup) findViewById(R.id.flow_container);
final EditText text=(EditText)findViewById(R.id.editText_sample);
Button addIT=(Button)findViewById(R.id.addit);
addIT.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String data=text.getText().toString();
flowContainer.addView(createTextView(data),
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
}
});
Copy this function within mainactivity
private View createTextView(String text) {
TextView textView = new TextView(this);
textView.setText(text);
textView.setGravity(Gravity.CENTER);
textView.setCompoundDrawablesWithIntrinsicBounds(0,0,R.mipmap.ic_launcher,0);
return textView;
}
Related
I want to remove image which was recently added if anyone click minus button.
I have added image one by one on plus button click.
can see in snapshot
On plus button click images are going to add one by one.
want to remove on click of minus button recently added image.
image.setOnClickListener(new View.OnClickListener() {
#RequiresApi(api = Build.VERSION_CODES.N)
#Override
public void onClick(View v) {
ImageView image = new ImageView(Water.this);
image.setBackgroundResource(R.drawable.glass);
predicate.addView(image);
}
});
ImageView minus=(ImageView)findViewById(R.id.minus);
minus.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ImageView image = new ImageView(Water.this);
image.setImageBitmap(null);
predicate.removeView(image);
//image.setBackgroundResource(R.drawable.glass);
//((ViewGroup) image.getParent()).removeView(image);
//predicate.removeView(image);
}
});
xml
<TextView
android:id="#+id/waterdescription"
android:text="Water Intake"
android:textSize="16dp"
android:layout_weight="1"
android:layout_marginLeft="20dp"
android:layout_width="wrap_content"
android:textColor="#283D65"
android:textStyle="bold"
android:layout_height="wrap_content"
/>
<ImageView
android:id="#+id/minus"
android:layout_weight="1"
android:layout_toRightOf="#+id/waterdescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/minus"
/>
<ImageView
android:id="#+id/image"
android:layout_weight="1"
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/plus"
/>
predicate Layout
public class PredicateLayout extends ViewGroup {
private int line_height;
public PredicateLayout(Context context) {
super(context);
}
public PredicateLayout(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);
// The next line is WRONG!!! Doesn't take into account requested MeasureSpec mode!
int height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom();
final int count = getChildCount();
int line_height = 0;
int xpos = getPaddingLeft();
int ypos = getPaddingTop();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = child.getLayoutParams();
child.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED));
final int childw = child.getMeasuredWidth();
line_height = Math.max(line_height, child.getMeasuredHeight() + lp.height);
if (xpos + childw > width) {
xpos = getPaddingLeft();
ypos += line_height;
}
xpos += childw + lp.width + 8;
}
}
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 + 20);
}
#Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(2, 2); // default of 1px spacing
}
#Override
protected boolean checkLayoutParams(LayoutParams p) {
return (p instanceof LayoutParams);
}
#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 = child.getLayoutParams();
if (xpos + childw > width) {
xpos = getPaddingLeft();
ypos += line_height;
}
child.layout(xpos, ypos, xpos + childw, ypos + childh);
xpos += childw + lp.width + 8;
}
}
}
}
You're not referencing the imageView you added in plus button's onClick() method.
minus.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ImageView image = new ImageView(Water.this);
image.setImageBitmap(null);
predicate.removeView(image);
//image.setBackgroundResource(R.drawable.glass);
//((ViewGroup) image.getParent()).removeView(image);
//predicate.removeView(image);
}
});
ImageView image = new ImageView(Water.this); in this line, you're creating a new ImageView with water and trying to remove it from parent layout. But you didn't even add this.
What you need to do is to keep a reference to the the views you are adding in plus button's onClick() method.
You can do something like:
public class PredicateLayout extends ViewGroup {
private LinkedList<ImageView> imageViews;
//other parts are omitted...
public PredicateLayout(Context context, AttributeSet attrs) {
super(context, attrs);
imageViews = new LinkedList();
}
//...some other code...
public LinkedList<ImageView> getImageViews(){
return imageViews;
}
}
and when adding:
Plus Button:
...onClick() {
//..
predicate.addView(image);
predicate.getImageViews().add(image);
}
Minus Button:
...onClick(){
//pollLast returns last element in the list
ImageView lastAddedImageView = predicate.getImageViews().pollLast()
predicate.removeView(lastAddedImageView);
}
Add view like:
ImageView image = new ImageView(Water.this);
image.setId(Integer.parseInt("1234"));
image.setBackgroundResource(R.drawable.glass);
predicate.addView(image);
And remove it:
View rView=(ImageView)view.findViewById(Integer.parseInt("1234"));
predicate.removeView(rView);
I need to create a basic Tag Cloud. My aim is to add multiple TextView(Tags) that automatically fits themselves in a new line if the number exceeds the device width. (Like Instagram).
Right now when the width of the layout containing (TextView)tags exceeds the (device width) the TextView(s) at the end wraps itself.
I tried using RelativeLayout but i am unable to figure out the logic.
I viewed this post but if there is a better or simply a cleaner solution.
Thanks for your precious time.
It is not necessary that the result looks like Instagram.
The cleaner solution is to write your own custom ViewGroup class. Find the sample below.
For complete descriptive explanation visit, How to Create Custom Layout in Android by Extending ViewGroup Class.
public class TagLayout extends ViewGroup {
public TagLayout(Context context) {
super(context);
}
public TagLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TagLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
int curWidth, curHeight, curLeft, curTop, maxHeight;
//get the available size of child view
final int childLeft = this.getPaddingLeft();
final int childTop = this.getPaddingTop();
final int childRight = this.getMeasuredWidth() - this.getPaddingRight();
final int childBottom = this.getMeasuredHeight() - this.getPaddingBottom();
final int childWidth = childRight - childLeft;
final int childHeight = childBottom - childTop;
maxHeight = 0;
curLeft = childLeft;
curTop = childTop;
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() == GONE)
return;
//Get the maximum size of the child
child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.AT_MOST));
curWidth = child.getMeasuredWidth();
curHeight = child.getMeasuredHeight();
//wrap is reach to the end
if (curLeft + curWidth >= childRight) {
curLeft = childLeft;
curTop += maxHeight;
maxHeight = 0;
}
//do the layout
child.layout(curLeft, curTop, curLeft + curWidth, curTop + curHeight);
//store the max height
if (maxHeight < curHeight)
maxHeight = curHeight;
curLeft += curWidth;
}
}
}
To use the TagLayout, you can add it to your activity/fragment layout declaration.
main_activity.xml
<com.javatechig.taglayout.TagLayout
android:id="#+id/tagLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
Now we can have a custom view to allow some level of customization for each tag item layout.
tag_layout.xml
<TextView
android:id="#+id/tagTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:background="#a000"
android:padding="10dp"
android:textColor="#fff" />
MainActivity.java
Finally, from activity class you can add the tag items as follows.
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TagLayout tagLayout = (TagLayout) findViewById(R.id.tagLayout);
LayoutInflater layoutInflater = getLayoutInflater();
String tag;
for (int i = 0; i <= 20; i++) {
tag = "#tag" + i;
View tagView = layoutInflater.inflate(R.layout.tag_layout, null, false);
TextView tagTextView = (TextView) tagView.findViewById(R.id.tagTextView);
tagTextView.setText(tag);
tagLayout.addView(tagView);
}
}
}
Result
I believe the solution above always consider the whole screen width, so if the developer sets android:layout_width parameter for example it won't respect its value or even parent's margin and padding values.
I've fixed it as follows:
private int mScreenWidth = 0;
private int mAvailableWidth = -1;
public TagLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
Display display = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
Point deviceSize = new Point();
display.getSize(deviceSize);
mScreenWidth = deviceSize.x;
}
private void calculateAvailableWidth() {
if(getLayoutParams() != null && getLayoutParams().width > 0) {
mAvailableWidth = getLayoutParams().width;
return;
}
mAvailableWidth = mScreenWidth;
ViewGroup parent = this;
while(parent != null) {
mAvailableWidth -= parent.getPaddingLeft() + parent.getPaddingRight();
if(parent.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)parent.getLayoutParams();
mAvailableWidth -= layoutParams.leftMargin + layoutParams.rightMargin;
}
if(parent.getParent() instanceof ViewGroup)
parent = (ViewGroup)parent.getParent();
else
parent = null;
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
int currentRowWidth = 0;
int currentRowHeight = 0;
int maxItemWidth = 0;
int maxWidth = 0;
int maxHeight = 0;
if(mAvailableWidth == -1)
calculateAvailableWidth();
for(int i = 0; i < count; i++) {
View child = getChildAt(i);
if(child.getVisibility() == GONE)
continue;
try {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
}
catch(Exception e) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
int childWidth = child.getMeasuredWidth() + child.getPaddingRight() + child.getPaddingLeft();
int childHeight = child.getMeasuredHeight() + child.getPaddingTop() + child.getPaddingBottom();
maxItemWidth = Math.max(maxItemWidth, childWidth);
if(currentRowWidth + childWidth < mAvailableWidth) {
currentRowWidth += childWidth;
maxWidth = Math.max(maxWidth, currentRowWidth);
currentRowHeight = Math.max(currentRowHeight, childHeight);
}
else {
currentRowWidth = childWidth;
maxHeight += currentRowHeight;
}
}
if(getLayoutParams().width == LayoutParams.WRAP_CONTENT) {
mAvailableWidth = maxItemWidth;
maxWidth = maxItemWidth;
}
maxHeight += currentRowHeight + getPaddingTop() + getPaddingBottom();
setMeasuredDimension(maxWidth, maxHeight);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int count = getChildCount();
int currentLeft = getPaddingLeft();
int currentTop = getPaddingTop();
int currentRight;
int currentBottom;
int parentWidth = this.getPaddingRight() - this.getPaddingLeft() + right;
for(int i = 0; i < count; i++) {
View child = getChildAt(i);
if(child.getVisibility() == View.GONE)
return;
int currentWidth = child.getMeasuredWidth() + child.getPaddingRight() + child.getPaddingLeft();
int currentHeight = child.getMeasuredHeight() + child.getPaddingBottom() + child.getPaddingTop();
if(currentLeft + currentWidth > parentWidth) {
currentLeft = getPaddingLeft();
currentTop += currentHeight;
}
currentBottom = currentTop + currentHeight;
currentRight = currentLeft + currentWidth;
child.layout(currentLeft, currentTop, currentRight, currentBottom);
currentLeft += currentWidth;
}
}
This way you are able to set android:layout_width="250dp" and get a result like this:
Set android:layout_width="match_parent" and get a result like this:
Or event use android:layout_width="wrap_content and get a result like this:
Hope it helps.
I wrote the following ViewGroup
public class myViewGroup extends ViewGroup{
List<View> qResult;
List<Point> qLoc;
ImageView qImage;
public QueryViewLayout(Context context){
super(context);
}
public QueryViewLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public QueryViewLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
qResult = new LinkedList<View>();
qLoc = new LinkedList<Point>();
qImage = null;
}
public void addMainView(ImageBorderView view){
qImage = view;
super.removeAllViews();
super.addView(view);
}
public void addResultView(View result, Point loc){
super.addView(result);
qResult.add(result);
qLoc.add(loc);
}
/**
* Any layout manager that doesn't scroll will want this.
*/
#Override
public boolean shouldDelayChildPressedState() {
return false;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
// Measurement will ultimately be computing these values.
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
// Only main view affects the layouts measure
if (qImage != null) {
if (qImage.getVisibility() != GONE) {
// Measure the child.
qImage.measure(widthMeasureSpec, heightMeasureSpec);
maxWidth = qImage.getMeasuredWidth();
maxHeight = qImage.getMeasuredHeight();
childState = qImage.getMeasuredState();
}
}
for (View child:qResult){
if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED)
child.measure(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST));
}
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Report our final dimensions.
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int count = getChildCount();
int parentLeft = left + getPaddingLeft();
int parentRight = right - getPaddingRight();
final int parentTop = top + getPaddingTop();
final int parentBottom = bottom - getPaddingBottom();
if (qImage == null) return;
qImage.layout(parentLeft, parentTop, parentRight, parentBottom);
Iterator<Point> loc = qLoc.iterator();
for (View child:qResult) {
Point p = loc.next();
if (child.getVisibility() != GONE) {
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
Point locOnView = qImage.projectOnView(p);
width = (width < (int) Math.max(parentRight - (int) locOnView.x, locOnView.x - parentLeft)) ?
width : (parentLeft + parentRight)/2;
height = (height < (int) Math.max(parentBottom - (int) locOnView.y, locOnView.y - parentTop)) ?
height : (parentBottom + parentTop)/2;
int x = (width < (parentRight - (int) locOnView.x)) ? (int) locOnView.x : (parentRight - width);
int y = (height < parentBottom - (int) locOnView.y) ? (int) locOnView.y : (parentBottom - height);
// Place the child.
child.layout(x, y, x + width, y + height);
}
}
}
}
It is supposed to show some arbitrary view on top of an image, given a location for that view, when I use a GridView as the arbitrary view, even though I have defined a certain width for the GridView it is forced to have a width as large as the frame. In the measure phase I changed the mode to
MeasureSpec.AT_MOST
for both width and height of the overlay view, but this does not seem to work, can someone please help.
here is the xml where I, inflate the GridView from
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/result_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:columnWidth="#dimen/result_view_column_width"
android:numColumns="2"
android:verticalSpacing="2dp"
android:horizontalSpacing="2dp"
android:stretchMode="none"
android:gravity="center"
android:layout_margin = "2dp"
android:background="#drawable/solid_with_shadow" />
After a lot of trial and error, replacing
child.measure(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST));
with
measureChild(child, MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST));
worked for me, I am not sure why, but a wild guess would be calling measure on a child does not read all the xml props, but measureChild(child, ...) does.
I'm interesting to get the same effect like in css float: left style. I'm using now RelativeLayout and android:layout_toRightOf but need views to goes to next line when they can't fit. How to achieve this in android layout ?
<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">
<Button
android:id="#+id/a"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:width="300dp" />
<Button
android:id="#+id/b"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="#+id/a"
android:width="300dp" />
<Button
android:id="#+id/c"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="#+id/b"
android:width="300dp" />
</RelativeLayout>
I need something like in this example
On wider screen i need to get for example 3 buttons in one line, but on smaller screen have to 2 buttons in one line and third in second line. Like in images below.
Smaller device
Wider device
For single rows use a LinearLayout with horizontal orientation instead of a RelativeLayout.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<!-- add your views here -->
</LinearLayout>
For multiple rows you way want to automatically "flow" your elements to a new line. You can take a look at the android flow-layout project or try to implement your own according to this custom implementation
/** Custom view which extends {#link RelativeLayout}
* and which places its children horizontally,
* flowing over to a new line whenever it runs out of width.*/
public class HorizontalFlowLayout
extends RelativeLayout
{
/** Constructor to use when creating View from code.*/
public HorizontalFlowLayout(Context context)
{
super(context);
}
/** Constructor that is called when inflating View from XML.*/
public HorizontalFlowLayout(Context context, AttributeSet attrs)
{
super(context, attrs);
}
/** Perform inflation from XML and apply a class-specific base style.*/
public HorizontalFlowLayout(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// need to call super.onMeasure(...) otherwise get some funny behaviour
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
// increment the x position as we progress through a line
int xpos = getPaddingLeft();
// increment the y position as we progress through the lines
int ypos = getPaddingTop();
// the height of the current line
int line_height = 0;
// go through children
// to work out the height required for this view
// call to measure size of children not needed I think?!
// getting child's measured height/width seems to work okay without it
//measureChildren(widthMeasureSpec, heightMeasureSpec);
View child;
MarginLayoutParams childMarginLayoutParams;
int childWidth, childHeight, childMarginLeft, childMarginRight, childMarginTop, childMarginBottom;
for (int i = 0; i < getChildCount(); i++)
{
child = getChildAt(i);
if (child.getVisibility() != GONE)
{
childWidth = child.getMeasuredWidth();
childHeight = child.getMeasuredHeight();
if (child.getLayoutParams() != null
&& child.getLayoutParams() instanceof MarginLayoutParams)
{
childMarginLayoutParams = (MarginLayoutParams)child.getLayoutParams();
childMarginLeft = childMarginLayoutParams.leftMargin;
childMarginRight = childMarginLayoutParams.rightMargin;
childMarginTop = childMarginLayoutParams.topMargin;
childMarginBottom = childMarginLayoutParams.bottomMargin;
}
else
{
childMarginLeft = 0;
childMarginRight = 0;
childMarginTop = 0;
childMarginBottom = 0;
}
if (xpos + childMarginLeft + childWidth + childMarginRight + getPaddingRight() > width)
{
// this child will need to go on a new line
xpos = getPaddingLeft();
ypos += line_height;
line_height = childMarginTop + childHeight + childMarginBottom;
}
else
// enough space for this child on the current line
line_height = Math.max(
line_height,
childMarginTop + childHeight + childMarginBottom);
xpos += childMarginLeft + childWidth + childMarginRight;
}
}
ypos += line_height + getPaddingBottom();
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED)
// set height as measured since there's no height restrictions
height = ypos;
else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST
&& ypos < height)
// set height as measured since it's less than the maximum allowed
height = ypos;
setMeasuredDimension(width, height);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
// increment the x position as we progress through a line
int xpos = getPaddingLeft();
// increment the y position as we progress through the lines
int ypos = getPaddingTop();
// the height of the current line
int line_height = 0;
View child;
MarginLayoutParams childMarginLayoutParams;
int childWidth, childHeight, childMarginLeft, childMarginRight, childMarginTop, childMarginBottom;
for (int i = 0; i < getChildCount(); i++)
{
child = getChildAt(i);
if (child.getVisibility() != GONE)
{
childWidth = child.getMeasuredWidth();
childHeight = child.getMeasuredHeight();
if (child.getLayoutParams() != null
&& child.getLayoutParams() instanceof MarginLayoutParams)
{
childMarginLayoutParams = (MarginLayoutParams)child.getLayoutParams();
childMarginLeft = childMarginLayoutParams.leftMargin;
childMarginRight = childMarginLayoutParams.rightMargin;
childMarginTop = childMarginLayoutParams.topMargin;
childMarginBottom = childMarginLayoutParams.bottomMargin;
}
else
{
childMarginLeft = 0;
childMarginRight = 0;
childMarginTop = 0;
childMarginBottom = 0;
}
if (xpos + childMarginLeft + childWidth + childMarginRight + getPaddingRight() > r - l)
{
// this child will need to go on a new line
xpos = getPaddingLeft();
ypos += line_height;
line_height = childHeight + childMarginTop + childMarginBottom;
}
else
// enough space for this child on the current line
line_height = Math.max(
line_height,
childMarginTop + childHeight + childMarginBottom);
child.layout(
xpos + childMarginLeft,
ypos + childMarginTop,
xpos + childMarginLeft + childWidth,
ypos + childMarginTop + childHeight);
xpos += childMarginLeft + childWidth + childMarginRight;
}
}
}
}
Use this code to mek your three buttons in 1 line for any screen size device.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:weightSum="3" >
<Button
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"/>
<Button
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"/>
<Button
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"/>
</LinearLayout>
How can I do something like a FlowLayout in Android?
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.
implementation 'com.google.android:flexbox:2.0.1'
More about this - https://android-developers.googleblog.com/2017/02/build-flexible-layouts-with.html
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));
}
There is a library from Google, called "flexbox-layout". You should check it out.
To use it in RecyclerView, you can use something like that:
val layoutManager = FlexboxLayoutManager(activity)
layoutManager.flexDirection = FlexDirection.ROW
layoutManager.flexWrap = FlexWrap.WRAP
layoutManager.justifyContent = JustifyContent.FLEX_START
layoutManager.alignItems = AlignItems.FLEX_START
recyclerView.layoutManager=layoutManager
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"));
}
}
});
}
}
}
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/
Like one of the previous answers, I started with the solution here: http://hzqtc.github.io/2013/12/android-custom-layout-flowlayout.html
I extended it to account for varying heights of children as below.
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
// Custom layout that wraps child views to a new line
public class FlowLayout extends ViewGroup {
private int marginHorizontal;
private int marginVertical;
public FlowLayout(Context context) {
super(context);
init();
}
public FlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() { // Specify the margins for the children
marginHorizontal = getResources().getDimensionPixelSize(R.dimen.activity_half_horizontal_margin);
marginVertical = getResources().getDimensionPixelSize(R.dimen.activity_half_vertical_margin);
}
#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);
if (childWidth + childLeft + getPaddingRight() > myWidth) { // Wrap this line
childLeft = getPaddingLeft();
childTop = marginVertical + lowestBottom; // Spaced below the previous lowest point
lineHeight = childHeight;
}
childLeft += childWidth + marginHorizontal;
if (childHeight + childTop > lowestBottom) { // New lowest point
lowestBottom = childHeight + childTop;
}
}
wantedHeight += 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();
if (childWidth + childLeft + getPaddingRight() > myWidth) { // Wrap this line
childLeft = getPaddingLeft();
childTop = marginVertical + lowestBottom; // Spaced below the previous lowest point
}
child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
childLeft += childWidth + marginHorizontal;
if (childHeight + childTop > lowestBottom) { // New lowest point
lowestBottom = childHeight + childTop;
}
}
}
}
I used this as a solution for wrapping multi-line TextEdits. Hope it helps!
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);
}
}
}
Nice simple self-contained FlowLayout code here (just a few concise gist.github files):
http://hzqtc.github.io/2013/12/android-custom-layout-flowlayout.html
However, the activity there out of the box didn't work for me to load the custom layout.
I found this work-around [ using the 2-param .inflate() call from this example ]:
#Override
protected void onCreate(Bundle savedInstanceState)
{
// ..
setContentView(R.layout.main_res_layout_activity_main);
ViewGroup flowContainer = getFlowLayoutView();
// ..
}
ViewGroup getFlowLayoutView()
{
LayoutInflater inflater = getLayoutInflater();
ViewGroup flowLayout =
(ViewGroup)
inflater.inflate(
R.layout.main_res_layout_activity_main,
(FlowLayout) findViewById(R.id.flow_container)
);
return flowLayout;
}