Android 的换行小部件布局
Posted
技术标签:
【中文标题】Android 的换行小部件布局【英文标题】:Line-breaking widget layout for Android 【发布时间】:2010-10-07 15:50:08 【问题描述】:我正在尝试创建一个向用户展示一些数据的活动。数据可以分为“单词”,每个单词都是一个小部件,“单词”序列将形成数据(“句子”?),即包含单词的 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);
产生类似于左图的东西,但我希望布局呈现与右图相同的小部件。
是否有这样的布局或布局和参数的组合,还是我必须为此实现自己的 ViewGroup?
【问题讨论】:
【参考方案1】:我做了自己想要的布局,但目前非常有限。当然欢迎提出意见和改进建议。
活动:
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_
android: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://***.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;
结果:
【讨论】:
我只是以编程方式进行的,因为为了传达想法而粘贴的次数要少得多:) 该网站让我同意“用户贡献的内容在 cc-wiki 下获得许可并需要注明出处”,所以...查看底部了解详情 由于某种原因,当我尝试在 Android 布局编辑器中扩展视图时,我得到“ClassCastException android.view.view 无法转换为 android.view.viewgroup”。关于如何解决这个问题的任何想法? 对于那些通过这个答案来的人 - onMeasure() 包含一些错误的逻辑,不会按预期工作。想象一下,这个 ViewGroup 的父级发送一个不关心高度的测量请求,它将发送 0 作为高度和 0 作为测量模式。 PredicateLayout 将得到 0 作为它自己的高度,并将强制子级为 AT_MOST 0 高度。 不是在代码中添加注释,就不能纠正错误吗? :)【参考方案2】:自 2016 年 5 月以来,Google 推出了名为 FlexboxLayout 的新布局,可根据您的需要高度配置。
FlexboxLayout 目前在 Google GitHub 存储库中,地址为 https://github.com/google/flexbox-layout。
您可以通过将依赖项添加到您的 build.gradle
文件中来在您的项目中使用它:
dependencies
compile 'com.google.android:flexbox:0.3.2'
更多关于 FlexboxLayout 的使用和所有属性,您可以在存储库自述文件或 Mark Allison 文章中找到:
https://blog.stylingandroid.com/flexboxlayout-part-1/
https://blog.stylingandroid.com/flexboxlayout-part2/
https://blog.stylingandroid.com/flexboxlayout-part-3/
【讨论】:
@Lukas- 我在 Radio Group 中使用相同的,UI 工作正常,但 Radio Group 的功能受到干扰。意味着,在同一个单选组中,我可以勾选多个单选按钮。我想同时检查一个单选按钮。请为此提供帮助。 请注意,它不适用于任何滚动容器! :( @JakeWilson801 它适用于我的滚动容器。但是现在有FlexboxLayoutManager in RecyclerView,所以也许它可以解决你的问题。 @LukasNovak,我必须在子视图之间添加分隔线。我已经添加了它,但分隔线的高度没有调整,它需要与子视图相同的全高。我想降低分隔线的高度。【参考方案3】:我实现了与此非常相似的东西,但使用我认为更标准的方法来处理间距和填充。请让我知道你们的想法,并随时重复使用而不注明出处:
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 下的条目,如果它还没有,您可以创建它。
<?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_
android:layout_
android:layout_gravity="center"
android:padding="10dp"
android:horizontalSpacing="10dp"
android:verticalSpacing="20dp">
<FrameLayout android:layout_ android:layout_ android:background="#F00"/>
<FrameLayout android:layout_ android:layout_ android:background="#F00"/>
<FrameLayout android:layout_ android:layout_ android:background="#F00"/>
<FrameLayout android:layout_ android:layout_ android:background="#F00"/>
<FrameLayout android:layout_ android:layout_ android:background="#F00"/>
<FrameLayout android:layout_ android:layout_ android:background="#F00"/>
<FrameLayout android:layout_ android:layout_ android:background="#F00"/>
<FrameLayout android:layout_ android:layout_ android:background="#F00"/>
</com.asolutions.widget.RowLayout>
【讨论】:
我正在使用这个。我正在向该布局动态添加项目。它被添加了,但每一个视图都是很小的。为什么? 在我尝试过的所有解决方案中,这个是唯一一个在从 xml 布局文件中使用它时没有抛出 ClassCastException 的解决方案。【参考方案4】:ApmeM 的 android-flowlayout 项目也支持换行符。
【讨论】:
【参考方案5】:这是我简化的纯代码版本:
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://***.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()
【讨论】:
我在布局上添加了一个视图并为类命名,然后将子按钮添加到该视图。但是我无法控制它们的高度?他们看起来都有一个我不想要的填充顶部/底部。 您可以设置 PAD_V = 0,或者添加构造函数参数来自定义填充。【参考方案6】:第一个答案有问题:
child.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
例如,在 ListView 中,列表项被传递了 0(未指定)的 heightMeasureSpec,因此,在这里,大小为 0(AT_MOST)的 MeasureSpec 被传递给所有子项。这意味着整个 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);
这似乎对我有用,虽然不处理精确模式,这会更棘手。
【讨论】:
这也解决了将布局放入滚动视图时的问题。感谢您解决这个问题,所以我不必自己解决。【参考方案7】:我根据之前在这里发布的稍微修改的版本:
它适用于 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;
【讨论】:
【参考方案8】:我已更新此示例以修复错误,现在,每行可以有不同的高度!
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://***.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;
【讨论】:
【参考方案9】:这里的一些不同答案会在 Exclipse 布局编辑器中给我一个 ClassCastException。就我而言,我想使用 ViewGroup.MarginLayoutParams 而不是自己制作。无论哪种方式,请确保在 generateLayoutParams 中返回自定义布局类所需的 LayoutParams 实例。这就是我的样子,只需将 MarginLayoutParams 替换为您的 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 对象。
【讨论】:
【参考方案10】: 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;
【讨论】:
【参考方案11】:我改编了上面的一些 ode 并实现了一个流式布局,它将所有子视图(水平和垂直)居中。它符合我的需要。
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;
【讨论】:
【参考方案12】:试试:
dependencies
implementation'com.google.android:flexbox:0.3.2'
【讨论】:
【参考方案13】:尝试将 lp 的两个 LayoutParams 都设置为 WRAP_CONTENT
。
将 mlp 设置为 WRAP_CONTENT
、WRAP_CONTENT
可确保您的 TextView(s) t 足够宽和足够高以容纳“Hello”或其他内容您放入其中的字符串。我认为 l 可能不知道您的 t 有多宽。 setSingleLine(true)
也可能有所贡献。
【讨论】:
据我所知,LinearLayout 不做任何包装。两个轴上的 WRAP_CONTENT 将使 LinearLayout-widget 高一行并延伸到屏幕之外。据我了解,文本小部件上的 setSingleLine 仅影响该小部件的大小和形状,而不影响其父布局。 @Will,我想你误解了 WRAP_CONTENT 的含义。以上是关于Android 的换行小部件布局的主要内容,如果未能解决你的问题,请参考以下文章