适用于Android的突破性小部件布局

我正在尝试创build一个向用户显示一些数据的活动。 数据是这样的,它可以分成“单词”,每个单词都是一个小部件,“单词”序列将形成数据('句子'?),包含单词的ViewGroup小部件。 由于“句子”中所有“单词”所需的空间将超过显示器上可用的水平空间,因此我想将这些“句子”包装成正常的文本。

以下代码:

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

产生类似于左图片的东西,但是我想要一个布局呈现相同的小部件,就像在正确的一个。

非包装http://fnord.se/android/01-have.png 包装http://fnord.se/android/01-want.png

是否有这样的布局或布局和参数的组合,或者我必须为此实现我自己的ViewGroup?

自2016年5月起,Google提供了一种名为FlexboxLayout的新布局,可根据需要进行高度configuration。

FlexboxLayout目前位于https://github.com/google/flexbox-layout的; Google GitHub存储库中。

您可以在您的项目中使用它,方法是将依赖项添加到您的build.gradle文件中:

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

更多关于FlexboxLayout使用情况以及您可以在存储库自述文件或Mark Allison文章中find的所有属性:

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

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

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

我做了我自己想要的布局,但目前这个布局非常有限。 评论和改进build议当然是受欢迎的。

活动:

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

或者在一个XML布局中:

 <se.fnord.android.layout.PredicateLayout android:id="@+id/predicate_layout" android:layout_width="fill_parent" android:layout_height="wrap_content" /> 

和布局:

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

结果是:

包裹的小部件android/01-have2.png

我实现了一个非常相似的东西,但使用我认为是一个更加标准的方式来处理间距和填充。 请让我知道你们的想法,并随时重复使用没有署名:

 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<RowMeasurement> 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<RowMeasurement> rows = new ArrayList<RowMeasurement>(); 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<RowMeasurement> 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<View> getLayoutChildren() { List<View> children = new ArrayList<View>(); 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; } } } 

这还需要/res/values/attrs.xml下的条目,如果尚未存在,可以创build该条目。

 <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="RowLayout"> <attr name="android:verticalSpacing" /> <attr name="android:horizontalSpacing" /> </declare-styleable> </resources> 

在xml布局中的用法如下所示:

 <?xml version="1.0" encoding="utf-8"?> <com.asolutions.widget.RowLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:padding="10dp" android:horizontalSpacing="10dp" android:verticalSpacing="20dp"> <FrameLayout android:layout_width="30px" android:layout_height="40px" android:background="#F00"/> <FrameLayout android:layout_width="60px" android:layout_height="40px" android:background="#F00"/> <FrameLayout android:layout_width="70px" android:layout_height="20px" android:background="#F00"/> <FrameLayout android:layout_width="20px" android:layout_height="60px" android:background="#F00"/> <FrameLayout android:layout_width="10px" android:layout_height="40px" android:background="#F00"/> <FrameLayout android:layout_width="40px" android:layout_height="40px" android:background="#F00"/> <FrameLayout android:layout_width="40px" android:layout_height="40px" android:background="#F00"/> <FrameLayout android:layout_width="40px" android:layout_height="40px" android:background="#F00"/> </com.asolutions.widget.RowLayout> 

ApmeM的android- flowlayout项目也支持换行符。

在这里输入图像说明

这是我的简化代码版本:

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

第一个问题有答案:

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

例如,在ListView中,列表项传递的heightMeasureSpec为0(未知),所以在这里,MeasureSpec的大小0(AT_MOST)被传递给所有的孩子。 这意味着整个PredicateLayout是不可见的(高度0)。

作为一个快速修复,我改变了这样的孩子高度MeasureSpec:

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

接着

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

这似乎为我工作,虽然不处理的EXACT模式将更加棘手。

我稍微修改的版本基于以前发布在这里:

  • 它在Eclipse布局编辑器中工作
  • 它将所有项目水平居中

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

我已经更新了这个示例来修复一个bug,现在,每一行都可以有不同的高度!

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

这里的一些不同的答案会给我一个在Exarch布局编辑器中的ClassCastException 。 在我的情况下,我想使用ViewGroup.MarginLayoutParams而不是自己做。 无论哪种方式,请确保在generateLayoutParams中返回您的自定义布局类所需的LayoutParams实例。 这就是我的样子,只需将MarginLayoutParamsreplace为您的ViewGroup所需的:

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

它看起来像调用这个方法来为ViewGroup中的每个子对象分配一个LayoutParams对象。

  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<this.users.length; i++) { row.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); this.tag_button = new Button(this); this.tag_button.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, 70)); //get the size of the button text Paint mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setTextSize(tag_button.getTextSize()); mPaint.setTypeface(Typeface.create(Typeface.SERIF, Typeface.NORMAL)); float size = mPaint.measureText(tag_button.getText().toString(), 0, tag_button.getText().toString().length()); size = size+28; this.totalTextWidth += size; if(totalTextWidth < screenWidth) { row.addView(tag_button); } else { this.tags.addView(row); row = new LinearLayout(this); row.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); row.addView(tag_button); this.totalTextWidth = size; } } 

我修改了上面的一些代码,并实现了一个以所有儿童视图为中心的stream式布局,横向和纵向。 它符合我的需求。

 public class CenteredFlowLayout extends ViewGroup { private int lineHeight; private int centricHeightPadding; private final int halfGap; public static final List<View> LINE_CHILDREN = new ArrayList<View>(); 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<View> 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; } } } 

Try setting both of lp 's LayoutParams to be WRAP_CONTENT .

Setting mlp to be WRAP_CONTENT , WRAP_CONTENT ensures that your TextView(s) t are just wide and tall enough enough to hold "Hello" or whatever String you put in them. I think l may not be aware of how wide your t 's are. The setSingleLine(true) may be contributing too.