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_CONTENTWRAP_CONTENT 可确保您的 TextView(s) t 足够宽和足够高以容纳“Hello”或其他内容您放入其中的字符串。我认为 l 可能不知道您的 t 有多宽。 setSingleLine(true) 也可能有所贡献。

【讨论】:

据我所知,LinearLayout 不做任何包装。两个轴上的 WRAP_CONTENT 将使 LinearLayout-widget 高一行并延伸到屏幕之外。据我了解,文本小部件上的 setSingleLine 仅影响该小部件的大小和形状,而不影响其父布局。 @Will,我想你误解了 WRAP_CONTENT 的含义。

以上是关于Android 的换行小部件布局的主要内容,如果未能解决你的问题,请参考以下文章

添加/删除包含多个小部件pyqt的布局

如何在 Flutter 中的行小部件内的容器上添加边框?

当颤动行小部件中发生溢出时,显示隐藏元素数量的计数器

现代 Android 小部件开发

Android桌面小部件AppWidget开发

滚动小部件中的 Kivy 标签文本换行