web-dev-qa-db-ja.com

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);
    }
}

左の図のようなものが生成されますが、右の図と同じウィジェットを表示するレイアウトが必要です。

non-wrapping

wrapping

そのようなレイアウトまたはレイアウトとパラメータの組み合わせがありますか、またはこれのために独自のViewGroupを実装する必要がありますか?

94

2016年5月以降、GoogleのFlexboxLayoutという新しいレイアウトがあり、目的に合わせて高度に設定できます。

FlexboxLayoutは、現時点では https://github.com/google/flexbox-layout のGoogle GitHubリポジトリにあります。

build.gradleファイルに依存関係を追加することで、プロジェクトで使用できます。

dependencies {
    compile 'com.google.Android:flexbox:0.3.2'
}

FlexboxLayoutの使用法と、リポジトリのreadmeまたはMark Allisonの記事にあるすべての属性の詳細については、こちらをご覧ください。

https://blog.stylingandroid.com/flexboxlayout-part-1/

https://blog.stylingandroid.com/flexboxlayout-part2/

https://blog.stylingandroid.com/flexboxlayout-part-3/

38
Lukas Novak

私は自分が望むことをする独自のレイアウトを作成しましたが、現時点ではかなり制限されています。もちろん、コメントや改善提案を歓迎します。

アクティビティ:

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;
            }
        }
    }
}

結果:

Wrapped widgets

94

これに非常に似たものを実装しましたが、スペースやパディングを処理するためのもう少し標準的な方法だと思います。皆さんの意見をお聞かせください。帰属なしで自由に再利用してください。

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>
43
Micah Hainline

ApmeMによる Android-flowlayout プロジェクトも改行をサポートしています。

enter image description here

16
Mohsen Afshin

これが私の単純化されたコード専用バージョンです。

    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()

    }
11
Melinda Green

最初の回答に問題があります:

child.measure(
    MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
    MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));

たとえば、ListViewでは、リストアイテムにheightMeasureSpec 0(UNSPECIFIED)が渡されるため、ここでは、サイズ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);

それは私にとってはうまくいくようですが、はるかに難しいEXACTモードを処理しません。

7
Mark

以前にここに投稿されたに基づいて私のわずかに修正されたバージョン:

  • 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://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;
            }
        }
    }
}
3
    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;
        }
    }
1

ここでのさまざまな答えのいくつかは、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オブジェクトを割り当てるためにこのメソッドが呼び出されるようです

1
Henry

上記の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;
    }
  }    
}
1
Thomas R.

lpのLayoutParamsの両方を_WRAP_CONTENT_に設定してみてください。

mlpを_WRAP_CONTENT_、_WRAP_CONTENT_に設定すると、TextView(s)tが「Hello」またはどんな文字列を入れても。 ltがどれだけ広いかを認識していないと思います。 setSingleLine(true)も貢献している可能性があります。

0
Will