【问题标题】:Line-breaking widget layout for AndroidAndroid 的换行小部件布局
【发布时间】: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?

【问题讨论】:

    标签: java android user-interface layout


    【解决方案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://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;
                }
            }
        }
    }
    

    结果:

    【讨论】:

    • 我只是以编程方式进行的,因为为了传达想法而粘贴的次数要少得多:)
    • 该网站让我同意“用户贡献的内容在 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_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>
    

    【讨论】:

    • 我正在使用这个。我正在向该布局动态添加项目。它被添加了,但每一个视图都是很小的。为什么?
    • 在我尝试过的所有解决方案中,这个是唯一一个在从 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://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()
      
          }
      

      【讨论】:

      • 我在布局上添加了一个视图并为类命名,然后将子按钮添加到该视图。但是我无法控制它们的高度?他们看起来都有一个我不想要的填充顶部/底部。
      • 您可以设置 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://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;
                    }
                }
            }
        }
        

        【讨论】:

          【解决方案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 的含义。
                  猜你喜欢
                  • 1970-01-01
                  • 2014-01-12
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多