Diseño de widget innovador para Android

Intento crear una actividad que presente algunos datos al usuario. Los datos son tales que se pueden dividir en ‘palabras’, siendo cada uno un widget, y la secuencia de ‘palabras’ formaría los datos (‘oración’?), El widget de ViewGroup que contiene las palabras. Como el espacio requerido para todas las ‘palabras’ en una ‘oración’ excedería el espacio horizontal disponible en la pantalla, me gustaría envolver estas ‘oraciones’ como lo haría con una pieza de texto normal.

El siguiente código:

public class WrapTest extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LinearLayout l = new LinearLayout(this); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); LinearLayout.LayoutParams mlp = new LinearLayout.LayoutParams( new ViewGroup.MarginLayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)); mlp.setMargins(0, 0, 2, 0); for (int i = 0; i < 10; i++) { TextView t = new TextView(this); t.setText("Hello"); t.setBackgroundColor(Color.RED); t.setSingleLine(true); l.addView(t, mlp); } setContentView(l, lp); } } 

produce algo así como la imagen de la izquierda, pero me gustaría un diseño que presente los mismos widgets como en la derecha.

sin envoltura http://sofes.miximages.com/java/01-have.png envoltura http://sofes.miximages.com/java/01-want.png

¿Existe tal diseño o combinación de diseños y parámetros, o tengo que implementar mi propio ViewGroup para esto?

Desde mayo de 2016 hay un nuevo diseño llamado FlexboxLayout de Google, que es altamente configurable para el propósito que desee.

FlexboxLayout se encuentra en el repository de Google GitHub en https://github.com/google/flexbox-layout en este momento.

Puede usarlo en su proyecto agregando dependencia a su archivo build.gradle :

 dependencies { compile 'com.google.android:flexbox:0.3.2' } 

Obtenga más información sobre el uso de FlexboxLayout y todos los atributos que puede encontrar en el archivo Léame del repository o en los artículos de Mark Allison aquí:

https://blog.stylingandroid.com/flexboxlayout-part-1/

https://blog.stylingandroid.com/flexboxlayout-part2/

https://blog.stylingandroid.com/flexboxlayout-part-3/

Hice mi propio diseño que hace lo que quiero, pero es bastante limitado en este momento. Comentarios y sugerencias de mejora son, por supuesto, bienvenidos.

La actividad:

 package se.fnord.xmms2.predicate; import se.fnord.android.layout.PredicateLayout; import android.app.Activity; import android.graphics.Color; import android.os.Bundle; import android.widget.TextView; public class Predicate extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); PredicateLayout l = new PredicateLayout(this); for (int i = 0; i < 10; i++) { TextView t = new TextView(this); t.setText("Hello"); t.setBackgroundColor(Color.RED); t.setSingleLine(true); l.addView(t, new PredicateLayout.LayoutParams(2, 0)); } setContentView(l); } } 

O en un diseño XML:

  

Y el diseño:

 package se.fnord.android.layout; import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; /** * ViewGroup that arranges child views in a similar way to text, with them laid * out one line at a time and "wrapping" to the next line as needed. * * Code licensed under CC-by-SA * * @author Henrik Gustafsson * @see http://stackoverflow.com/questions/549451/line-breaking-widget-layout-for-android * @license http://creativecommons.org/licenses/by-sa/2.5/ * */ 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 = (LayoutParams) 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; } } 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 LayoutParams generateDefaultLayoutParams() { return new LayoutParams(1, 1); // 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 = (LayoutParams) child.getLayoutParams(); if (xpos + childw > width) { xpos = getPaddingLeft(); ypos += line_height; } child.layout(xpos, ypos, xpos + childw, ypos + childh); xpos += childw + lp.width; } } } } 

Con el resultado:

Widgets envueltos http://fnord.se/android/01-have2.png

Implementé algo muy similar a esto, pero utilizando lo que creo es una forma un poco más estándar para manejar el espaciado y el relleno. Por favor, háganme saber lo que piensan, y siéntanse libres de reutilizar sin atribución:

 package com.asolutions.widget; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import com.asolutions.widget.R; public class RowLayout extends ViewGroup { public static final int DEFAULT_HORIZONTAL_SPACING = 5; public static final int DEFAULT_VERTICAL_SPACING = 5; private final int horizontalSpacing; private final int verticalSpacing; private List currentRows = Collections.emptyList(); public RowLayout(Context context, AttributeSet attrs) { super(context, attrs); TypedArray styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.RowLayout); horizontalSpacing = styledAttributes.getDimensionPixelSize(R.styleable.RowLayout_android_horizontalSpacing, DEFAULT_HORIZONTAL_SPACING); verticalSpacing = styledAttributes.getDimensionPixelSize(R.styleable.RowLayout_android_verticalSpacing, DEFAULT_VERTICAL_SPACING); styledAttributes.recycle(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int widthMode = MeasureSpec.getMode(widthMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); final int maxInternalWidth = MeasureSpec.getSize(widthMeasureSpec) - getHorizontalPadding(); final int maxInternalHeight = MeasureSpec.getSize(heightMeasureSpec) - getVerticalPadding(); List rows = new ArrayList(); RowMeasurement currentRow = new RowMeasurement(maxInternalWidth, widthMode); rows.add(currentRow); for (View child : getLayoutChildren()) { LayoutParams childLayoutParams = child.getLayoutParams(); int childWidthSpec = createChildMeasureSpec(childLayoutParams.width, maxInternalWidth, widthMode); int childHeightSpec = createChildMeasureSpec(childLayoutParams.height, maxInternalHeight, heightMode); child.measure(childWidthSpec, childHeightSpec); int childWidth = child.getMeasuredWidth(); int childHeight = child.getMeasuredHeight(); if (currentRow.wouldExceedMax(childWidth)) { currentRow = new RowMeasurement(maxInternalWidth, widthMode); rows.add(currentRow); } currentRow.addChildDimensions(childWidth, childHeight); } int longestRowWidth = 0; int totalRowHeight = 0; for (int index = 0; index < rows.size(); index++) { RowMeasurement row = rows.get(index); totalRowHeight += row.getHeight(); if (index < rows.size() - 1) { totalRowHeight += verticalSpacing; } longestRowWidth = Math.max(longestRowWidth, row.getWidth()); } setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? MeasureSpec.getSize(widthMeasureSpec) : longestRowWidth + getHorizontalPadding(), heightMode == MeasureSpec.EXACTLY ? MeasureSpec.getSize(heightMeasureSpec) : totalRowHeight + getVerticalPadding()); currentRows = Collections.unmodifiableList(rows); } private int createChildMeasureSpec(int childLayoutParam, int max, int parentMode) { int spec; if (childLayoutParam == LayoutParams.FILL_PARENT) { spec = MeasureSpec.makeMeasureSpec(max, MeasureSpec.EXACTLY); } else if (childLayoutParam == LayoutParams.WRAP_CONTENT) { spec = MeasureSpec.makeMeasureSpec(max, parentMode == MeasureSpec.UNSPECIFIED ? MeasureSpec.UNSPECIFIED : MeasureSpec.AT_MOST); } else { spec = MeasureSpec.makeMeasureSpec(childLayoutParam, MeasureSpec.EXACTLY); } return spec; } @Override protected void onLayout(boolean changed, int leftPosition, int topPosition, int rightPosition, int bottomPosition) { final int widthOffset = getMeasuredWidth() - getPaddingRight(); int x = getPaddingLeft(); int y = getPaddingTop(); Iterator rowIterator = currentRows.iterator(); RowMeasurement currentRow = rowIterator.next(); for (View child : getLayoutChildren()) { final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); if (x + childWidth > widthOffset) { x = getPaddingLeft(); y += currentRow.height + verticalSpacing; if (rowIterator.hasNext()) { currentRow = rowIterator.next(); } } child.layout(x, y, x + childWidth, y + childHeight); x += childWidth + horizontalSpacing; } } private List getLayoutChildren() { List children = new ArrayList(); for (int index = 0; index < getChildCount(); index++) { View child = getChildAt(index); if (child.getVisibility() != View.GONE) { children.add(child); } } return children; } protected int getVerticalPadding() { return getPaddingTop() + getPaddingBottom(); } protected int getHorizontalPadding() { return getPaddingLeft() + getPaddingRight(); } private final class RowMeasurement { private final int maxWidth; private final int widthMode; private int width; private int height; public RowMeasurement(int maxWidth, int widthMode) { this.maxWidth = maxWidth; this.widthMode = widthMode; } public int getHeight() { return height; } public int getWidth() { return width; } public boolean wouldExceedMax(int childWidth) { return widthMode == MeasureSpec.UNSPECIFIED ? false : getNewWidth(childWidth) > maxWidth; } public void addChildDimensions(int childWidth, int childHeight) { width = getNewWidth(childWidth); height = Math.max(height, childHeight); } private int getNewWidth(int childWidth) { return width == 0 ? childWidth : width + horizontalSpacing + childWidth; } } } 

Esto también requiere una entrada en /res/values/attrs.xml, que puede crear si aún no está allí.

        

El uso en un diseño xml se ve así:

            

El proyecto android-flowlayout de ApmeM admite saltos de línea también.

enter image description here

Aquí está mi versión simplificada, solo de código:

  package com.superliminal.android.util; import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; /** * A view container with layout behavior like that of the Swing FlowLayout. * Originally from http://nishantvnair.wordpress.com/2010/09/28/flowlayout-in-android/ itself derived from * http://stackoverflow.com/questions/549451/line-breaking-widget-layout-for-android * * @author Melinda Green */ public class FlowLayout extends ViewGroup { private final static int PAD_H = 2, PAD_V = 2; // Space between child views. private int mHeight; 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 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); mHeight = 0; for(int i = 0; i < count; i++) { final View child = getChildAt(i); if(child.getVisibility() != GONE) { child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), childHeightMeasureSpec); final int childw = child.getMeasuredWidth(); mHeight = Math.max(mHeight, child.getMeasuredHeight() + PAD_V); if(xpos + childw > width) { xpos = getPaddingLeft(); ypos += mHeight; } xpos += childw + PAD_H; } } if(MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) { height = ypos + mHeight; } else if(MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) { if(ypos + mHeight < height) { height = ypos + mHeight; } } height += 5; // Fudge to avoid clipping bottom of last row. setMeasuredDimension(width, height); } // end onMeasure() @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int width = r - l; int xpos = getPaddingLeft(); int ypos = getPaddingTop(); for(int i = 0; i < getChildCount(); i++) { final View child = getChildAt(i); if(child.getVisibility() != GONE) { final int childw = child.getMeasuredWidth(); final int childh = child.getMeasuredHeight(); if(xpos + childw > width) { xpos = getPaddingLeft(); ypos += mHeight; } child.layout(xpos, ypos, xpos + childw, ypos + childh); xpos += childw + PAD_H; } } } // end onLayout() } 

Hay un problema con la primera Respuesta:

 child.measure( MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); 

En ListView, por ejemplo, los elementos de la lista se pasan a heightMeasureSpec de 0 (UNSPECIFIED) y, por lo tanto, aquí, MeasureSpec de tamaño 0 (AT_MOST) se pasa a todos los elementos secundarios. Esto significa que todo PredicateLayout es invisible (altura 0).

Como una solución rápida, cambié la altura del niño MeasureSpec así:

 int childHeightMeasureSpec; if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) { childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST); } else { childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } 

y entonces

 child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), childHeightMeasureSpec); 

que parece funcionar para mí, aunque no maneja el modo EXACT, que sería mucho más complicado.

Mi versión ligeramente modificada basada en publicada aquí anteriormente:

  • Funciona en el editor de diseño Eclipse
  • Centra todos los elementos horizontalmente

     import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; 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, ViewGroup.LayoutParams viewGroupLayout) { super(viewGroupLayout); this.horizontal_spacing = horizontal_spacing; this.vertical_spacing = 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(); int lastHorizontalSpacing = 0; int rowStartIdx = 0; 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) { final int freeSpace = width - xpos + lastHorizontalSpacing; xpos = getPaddingLeft() + freeSpace / 2; for (int j = rowStartIdx; j < i; ++j) { final View drawChild = getChildAt(j); drawChild.layout(xpos, ypos, xpos + drawChild.getMeasuredWidth(), ypos + drawChild.getMeasuredHeight()); xpos += drawChild.getMeasuredWidth() + ((LayoutParams)drawChild.getLayoutParams()).horizontal_spacing; } lastHorizontalSpacing = 0; xpos = getPaddingLeft(); ypos += line_height; rowStartIdx = i; } child.layout(xpos, ypos, xpos + childw, ypos + childh); xpos += childw + lp.horizontal_spacing; lastHorizontalSpacing = lp.horizontal_spacing; } } if (rowStartIdx < count) { final int freeSpace = width - xpos + lastHorizontalSpacing; xpos = getPaddingLeft() + freeSpace / 2; for (int j = rowStartIdx; j < count; ++j) { final View drawChild = getChildAt(j); drawChild.layout(xpos, ypos, xpos + drawChild.getMeasuredWidth(), ypos + drawChild.getMeasuredHeight()); xpos += drawChild.getMeasuredWidth() + ((LayoutParams)drawChild.getLayoutParams()).horizontal_spacing; } } } } 

He actualizado esta muestra para corregir un error, ahora, ¡cada línea puede tener una altura diferente!

 import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; /** * ViewGroup that arranges child views in a similar way to text, with them laid * out one line at a time and "wrapping" to the next line as needed. * * Code licensed under CC-by-SA * * @author Henrik Gustafsson * @see http://stackoverflow.com/questions/549451/line-breaking-widget-layout-for-android * @license http://creativecommons.org/licenses/by-sa/2.5/ * * Updated by Aurélien Guillard * Each line can have a different height * */ public class FlowLayout extends ViewGroup { 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 if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) { childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); } 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(); if (xpos + childw > width) { xpos = getPaddingLeft(); ypos += line_height; } xpos += childw + lp.horizontal_spacing; line_height = child.getMeasuredHeight() + lp.vertical_spacing; } } 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 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(); int lineHeight = 0; 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 += lineHeight; } lineHeight = child.getMeasuredHeight() + lp.vertical_spacing; child.layout(xpos, ypos, xpos + childw, ypos + childh); xpos += childw + lp.horizontal_spacing; } } } } 

Algunas de las diferentes respuestas aquí me daría una ClassCastException en el editor de diseño de Exclipse. En mi caso, quería usar ViewGroup.MarginLayoutParams en lugar de hacer el mío. De cualquier manera, asegúrese de devolver la instancia de LayoutParams que necesita su clase de diseño personalizado en generateLayoutParams. Esto es lo que parece el mío, simplemente reemplace MarginLayoutParams por el que necesita su Grupo de Vista:

 @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof MarginLayoutParams; } 

Parece que se llama a este método para asignar un objeto LayoutParams para cada elemento secundario en ViewGroup.

  LinearLayout row = new LinearLayout(this); //get the size of the screen Display display = getWindowManager().getDefaultDisplay(); this.screenWidth = display.getWidth(); // deprecated for(int i=0; i 

Adapte algo de la oda anterior e implementé un diseño de flujo que centra todas las vistas secundarias, horizontales y verticales. Se ajusta a mis necesidades.

 public class CenteredFlowLayout extends ViewGroup { private int lineHeight; private int centricHeightPadding; private final int halfGap; public static final List LINE_CHILDREN = new ArrayList(); public static class LayoutParams extends ViewGroup.LayoutParams { public final int horizontalSpacing; public final int verticalSpacing; public LayoutParams(int horizontalSpacing, int verticalSpacing) { super(0, 0); this.horizontalSpacing = horizontalSpacing; this.verticalSpacing = verticalSpacing; } } public CenteredFlowLayout(Context context) { super(context); halfGap = getResources().getDimensionPixelSize(R.dimen.half_gap); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight(); int height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom(); final int maxHeight = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom(); final int count = getChildCount(); int lineHeight = 0; int xAxis = getPaddingLeft(); int yAxis = 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 CentricFlowLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams(); child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), childHeightMeasureSpec); final int childMeasuredWidth = child.getMeasuredWidth(); lineHeight = Math.max(lineHeight, child.getMeasuredHeight() + lp.verticalSpacing); if (xAxis + childMeasuredWidth > width) { xAxis = getPaddingLeft(); yAxis += lineHeight; } else if (i + 1 == count) { yAxis += lineHeight; } xAxis += childMeasuredWidth + lp.horizontalSpacing; } } this.lineHeight = lineHeight; if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) { height = yAxis + lineHeight; } else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) { if (yAxis + lineHeight < height) { height = yAxis + lineHeight; } } if (maxHeight == 0) { maxHeight = height + getPaddingTop(); } centricHeightPadding = (maxHeight - height) / 2; setMeasuredDimension(width, disableCenterVertical ? height + getPaddingTop() : maxHeight); } @Override protected CentricFlowLayout.LayoutParams generateDefaultLayoutParams() { return new CentricFlowLayout.LayoutParams(halfGap, halfGap); } @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 yAxis = centricHeightPadding + getPaddingTop() + getPaddingBottom(); View child; int measuredWidth; int lineWidth = getPaddingLeft() + getPaddingRight(); CentricFlowLayout.LayoutParams lp; int offset; LINE_CHILDREN.clear(); for (int i = 0; i < count; i++) { child = getChildAt(i); lp = (LayoutParams) child.getLayoutParams(); if (GONE != child.getVisibility()) { measuredWidth = child.getMeasuredWidth(); if (lineWidth + measuredWidth + lp.horizontalSpacing > width) { offset = (width - lineWidth) / 2; layoutHorizontalCentricLine(LINE_CHILDREN, offset, yAxis); lineWidth = getPaddingLeft() + getPaddingRight() + measuredWidth + lp.horizontalSpacing; yAxis += lineHeight; LINE_CHILDREN.clear(); LINE_CHILDREN.add(child); } else { lineWidth += measuredWidth + lp.horizontalSpacing; LINE_CHILDREN.add(child); } } } offset = (width - lineWidth) / 2; layoutHorizontalCentricLine(LINE_CHILDREN, offset, yAxis); } private void layoutHorizontalCentricLine(final List children, final int offset, final int yAxis) { int xAxis = getPaddingLeft() + getPaddingRight() + offset; for (View child : children) { final int measuredWidth = child.getMeasuredWidth(); final int measuredHeight = child.getMeasuredHeight(); final CentricFlowLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams(); child.layout(xAxis, yAxis, xAxis + measuredWidth, yAxis + measuredHeight); xAxis += measuredWidth + lp.horizontalSpacing; } } } 

Intente configurar ambos LayoutParams de lp para que sean WRAP_CONTENT .

Configurando mlp para que sea WRAP_CONTENT , WRAP_CONTENT asegura que tus TextView (s) t sean lo suficientemente anchos y lo suficientemente altos como para contener “Hello” o cualquier cadena que pongas en ellos. Creo que puede que no sepa cuán amplias son sus t . El setSingleLine(true) puede estar contribuyendo.