Android – LinearLayout Horizontal con envoltura de niños

¿Hay alguna propiedad para establecer para LinearLayout de Android que le permita ajustar adecuadamente los controles secundarios?

Significado – Tengo un número variable de niños y me gustaría diseñarlos horizontalmente como:

 Ejemplo: Control1, Control2, Control3, ...

Lo hago al configurar:

 ll.setOrientation (LinearLayout.HORIZONTAL);  foreach (Child c en niños) ll.addView (c); 

Sin embargo, si tengo una gran cantidad de hijos, el último se corta, en lugar de ir a la siguiente línea.

¿Alguna idea de cómo se puede arreglar esto?

A partir de mayo de 2016, Google creó su propio FlexBoxLayout que debería resolver su problema.

Puede encontrar el repository GitHub aquí: https://github.com/google/flexbox-layout

Esto debería ser lo que quieras:

 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; } } } } 

y el archivo XML

 /* you must write your package name and class name */  

Para cualquiera que necesite este tipo de comportamiento:

 private void populateLinks(LinearLayout ll, ArrayList 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); } } 

Una vieja pregunta, pero en caso de que alguien termine aquí, dos bibliotecas que hacen exactamente eso:

https://github.com/blazsolar/FlowLayout

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

Buscando una solución para un problema similar pero más simple, es envolver el contenido de texto para niños en el diseño horizontal. La solución de kape123 funciona bien. Pero encuentre uno más simple para este problema, usando ClickableSpan. Tal vez podría ser útil para un caso simple. retazo:

  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); } 
 //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); } 

el método anterior agregará el imgeview uno al lado del otro como agrid y en su diseño

   

Publico esta solución, puede ayudar a alguien y ahorrar un tiempo y la uso en mi aplicación

Terminé usando un TagView :

  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 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