Linienwidriges Widget-Layout für Android

Ich versuche eine Aktivität zu erstellen, die dem Benutzer einige Daten präsentiert. Die Daten sind so, dass sie in “Wörter” unterteilt werden können, wobei jedes ein Widget ist und eine Folge von “Wörtern” die Daten (“Satz”?) Bildet, wobei das ViewGroup-Widget die Wörter enthält. Da der Platzbedarf für alle “Wörter” in einem “Satz” den verfügbaren horizontalen Platz auf dem Bildschirm überschreiten würde, möchte ich diese “Sätze” wie einen normalen Text umschließen.

Der folgende Code:

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

ergibt etwas wie das linke Bild, aber ich würde ein Layout wünschen, das die gleichen Widgets wie im rechten darstellt.

nicht verpacken http://sofde.miximages.com/java/01-have.png wrapping http://sofde.miximages.com/java/01-want.png

Gibt es ein solches Layout oder eine Kombination von Layouts und Parametern, oder muss ich dafür eine eigene ViewGroup implementieren?

Seit Mai 2016 gibt es ein neues Layout namens FlexboxLayout von Google, das für den gewünschten Zweck hoch konfigurierbar ist.

FlexboxLayout befindet sich momentan im Google GitHub-Repository unter https://github.com/google/flexbox-layout .

Sie können es in Ihrem Projekt verwenden, indem Sie der build.gradle Datei eine Abhängigkeit build.gradle :

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

Weitere Informationen zur Verwendung von FlexboxLayout und zu allen Attributen finden Sie in der Readme-Datei des Repositorys oder in Mark Allison-Artikeln hier:

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

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

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

Ich habe mein eigenes Layout erstellt, das tut, was ich will, aber es ist im Moment ziemlich begrenzt. Kommentare und Verbesserungsvorschläge sind natürlich willkommen.

Die Aktivität:

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

Oder in einem XML-Layout:

  

Und das Layout:

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

Mit dem Ergebnis:

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

Ich habe etwas sehr ähnliches implementiert, aber ich benutze etwas, das meiner Meinung nach etwas mehr Standard ist, um mit Abstand und Padding umzugehen. Bitte lassen Sie mich wissen, was Sie denken, und fühlen Sie sich frei, ohne Zuschreibung zu verwenden:

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

Dies erfordert auch einen Eintrag unter /res/values/attrs.xml, den Sie erstellen können, wenn er nicht bereits vorhanden ist.

 < ?xml version="1.0" encoding="utf-8"?>       

Die Verwendung in einem XML-Layout sieht folgendermaßen aus:

 < ?xml version="1.0" encoding="utf-8"?>           

Das Android-Flowlayout- Projekt von ApmeM unterstützt auch Zeilenumbrüche.

Bildbeschreibung hier eingeben

Hier ist meine vereinfachte, Nur-Code-Version:

  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() } 

Es gibt ein Problem mit der ersten Antwort:

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

In einer ListView beispielsweise wird den Listenelementen eine heightMeasureSpec von 0 (ENCRICTED) übergeben, und daher wird hier die MeasureSpec der Größe 0 (AT_MOST) an alle untergeordneten Elemente übergeben. Dies bedeutet, dass das gesamte PredicateLayout unsichtbar ist (Höhe 0).

Als schnelle Lösung habe ich die Kindhöhe MeasureSpec wie folgt geändert:

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

und dann

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

Das scheint für mich zu funktionieren, obwohl es nicht mit dem EXACT-Modus arbeitet, der viel komplizierter wäre.

Meine leicht modifizierte Version basiert hier auf vorher gepostet:

  • Es funktioniert im Eclipse-Layout-Editor
  • Es zentriert alle Elemente horizontal

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

Ich habe dieses Beispiel aktualisiert, um einen Fehler zu beheben, jetzt kann jede Zeile eine andere Höhe haben!

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

Einige der verschiedenen Antworten hier geben mir eine ClassCastException im Exclips-Layout-Editor. In meinem Fall wollte ich ViewGroup.MarginLayoutParams verwenden, anstatt mich selbst zu erstellen. Stellen Sie auf jeden Fall sicher, dass Sie die Instanz von LayoutParams, die Ihre benutzerdefinierte Layoutklasse benötigt, in generateLayoutParams zurückgeben. So sieht meins aus, ersetzen Sie einfach MarginLayoutParams mit dem, was Ihre ViewGroup benötigt:

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

Es sieht so aus, als ob diese Methode aufgerufen wird, um ein LayoutParams-Objekt für jedes Kind in der ViewGroup zuzuweisen.

  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 

Ich habe einige der obigen Ode angepasst und ein Fließschema implementiert, das alle Kindansichten horizontal und vertikal zentriert. Es passt zu meinen Bedürfnissen.

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

Versuchen Sie, beide LayoutParams von lp als WRAP_CONTENT zu WRAP_CONTENT .

Wenn Sie mlp auf WRAP_CONTENT , stellt WRAP_CONTENT sicher, dass Ihre TextView (s) t gerade breit und hoch genug sind, um “Hello” oder was auch immer für String zu enthalten. Ich denke, ich bin mir vielleicht nicht bewusst, wie breit deine T sind. Die setSingleLine(true) kann ebenfalls beitragen.