Разрушающий макет виджета для Android
Я пытаюсь создать действие, которое представляет некоторые данные пользователю. Данные таковы, что их можно разделить на "слова", каждое из которых является виджетом, и последовательность "слов" будет формировать данные ("предложение"?), Виджет 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 для этого?
12 ответов
С мая 2016 года появился новый макет под названием FlexboxLayout от Google, который легко настраивается для любых целей.
FlexboxLayout в данный момент находится в репозитории Google GitHub по адресу https://github.com/google/flexbox-layout.
Вы можете использовать его в своем проекте, добавив зависимость к вашему build.gradle
файл:
dependencies {
compile 'com.google.android:flexbox:0.3.2'
}
Подробнее об использовании FlexboxLayout и всех атрибутах, которые вы можете найти в readme репозитория или в статьях Mark Allison здесь:
https://blog.stylingandroid.com/flexboxlayout-part-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_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://stackru.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;
}
}
}
}
С результатом:
Я реализовал что-то очень похожее на это, но с помощью того, что я считаю, немного более стандартным способом обработки пробелов и отступов. Пожалуйста, дайте мне знать, что вы, ребята, думаете, и не стесняйтесь повторно использовать без указания авторства:
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>
Проект https://github.com/ApmeM/android-flowlayout от ApmeM также поддерживает разрывы строк.
Вот моя упрощенная версия только для кода:
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://stackru.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 (UNSPECIFIED), и поэтому здесь 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; } } } }
Я обновил этот пример, чтобы исправить ошибку, теперь каждая строка может иметь разную высоту!
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://stackru.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;
}
}
}
}
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;
}
}
Пытаться :
dependencies {
implementation'com.google.android:flexbox:0.3.2'
}
Некоторые из приведенных здесь ответов дадут мне ClassCastException в редакторе макетов Exclipse. В моем случае я хотел использовать ViewGroup.MarginLayoutParams, а не делать свой собственный. В любом случае, убедитесь, что вы возвращаете экземпляр LayoutParams, который нужен вашему классу макета, в generateLayoutParams. Вот как выглядит мой, просто замените MarginLayoutParams на тот, который нужен вашей ViewGroup:
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof MarginLayoutParams;
}
Похоже, этот метод вызывается для назначения объекта LayoutParams для каждого дочернего элемента в ViewGroup.
Я адаптировал некоторые из приведенных выше од и реализовал макет потока, который центрирует все дочерние представления, горизонтальные и вертикальные. Это соответствует моим потребностям.
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;
}
}
}
Попробуйте установить оба параметра LayoutParams в lp как WRAP_CONTENT
,
Установка mlp быть WRAP_CONTENT
, WRAP_CONTENT
гарантирует, что ваши TextView (s) t достаточно широки и достаточно высоки, чтобы вместить "Hello" или любую строку, которую вы в них положили. Я думаю, что я, возможно, не осознаю, насколько широки твои. setSingleLine(true)
может быть способствующим тоже.