我正在尝试创建一个向用户显示一些数据的活动。数据可以分为“单词”,每个单词都是一个小部件,“单词”序列将形成数据(“句子”?),包含单词的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?
答案 0 :(得分:94)
我制作了自己的布局,可以满足我的需要,但目前它非常有限。评论和改进建议当然是受欢迎的。
活动:
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;
}
}
}
}
结果:
答案 1 :(得分:43)
我实现了与此非常类似的东西,但使用我认为处理间距和填充的更标准的方法。请让我知道你们的想法,并且可以在没有归属的情况下重复使用:
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_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>
答案 2 :(得分:37)
自2016年5月起,Google推出了名为FlexboxLayout的新版面,可根据您的需要进行高度配置。
此时,FlexboxLayout位于https://github.com/google/flexbox-layout的Google GitHub存储库中。
您可以通过向build.gradle
文件添加依赖项来在项目中使用它:
dependencies {
compile 'com.google.android:flexbox:0.3.2'
}
有关FlexboxLayout用法的更多信息以及您可以在资源库自述文件或Mark Allison文章中找到的所有属性:
https://blog.stylingandroid.com/flexboxlayout-part-1/
答案 3 :(得分:16)
ApmeM的android-flowlayout项目也支持换行符。
答案 4 :(得分:11)
这是我的简化代码版本:
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()
}
答案 5 :(得分:7)
第一个答案有问题:
child.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
例如,在ListView中,列表项传递的heightMeasureSpec为0(未知),因此,此处,大小为0的MeasureSpec(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);
这似乎对我有用,虽然不处理精确模式,这将更加棘手。
答案 6 :(得分:4)
我之前发布的稍微修改过的版本:
它将所有项目水平居中
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;
}
}
}
}
答案 7 :(得分:3)
我已更新此示例以修复错误,现在,每行可以有不同的高度!
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;
}
}
}
}
答案 8 :(得分:1)
这里的一些不同答案会在Exclipse布局编辑器中给我一个 ClassCastException 。就我而言,我想使用ViewGroup.MarginLayoutParams而不是自己创建。无论哪种方式,请确保在generateLayoutParams中返回自定义布局类所需的LayoutParams实例。这就是我的样子,只需用你的ViewGroup需要的那个替换MarginLayoutParams:
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof MarginLayoutParams;
}
看起来调用此方法为ViewGroup中的每个子项分配LayoutParams对象。
答案 9 :(得分:1)
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;
}
}
答案 10 :(得分:1)
我修改了上面的一些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;
}
}
}
答案 11 :(得分:1)
尝试:
dependencies {
implementation'com.google.android:flexbox:0.3.2'
}
答案 12 :(得分:0)
尝试将 lp 的LayoutParams设置为WRAP_CONTENT
。
将 mlp 设置为WRAP_CONTENT
,WRAP_CONTENT
可确保您的TextView(s) t 足够宽和足以容纳“你好“或你放入其中的任何字符串。我认为 l 可能不知道你的 t 有多宽。 setSingleLine(true)
也可能有所贡献。