Роздільна лінія віджетів для Android


100

Я намагаюся створити діяльність, яка представляє користувачеві деякі дані. Дані такі, що їх можна розділити на "слова", кожен з яких є віджетом, а послідовність "слів" формуватиме дані ("речення"?), Віджет 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?

Відповіді:


46

З травня 2016 року в Google з’явився новий макет під назвою FlexboxLayout від Google, який можна легко налаштувати за вашою метою.

Наразі FlexboxLayout знаходиться у сховищі Google GitHub за адресою https://github.com/google/flexbox-layout .

Ви можете використовувати його у своєму проекті, додавши залежність у свій build.gradleфайл:

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

Докладніше про використання FlexboxLayout та всі атрибути, які ви можете знайти в readme сховища або в статтях Mark Allison тут:

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

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

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


10
Мило, це виглядає саме так, що мені було потрібно 7 років тому :)
Генрік Густафссон

Врятував мій день, не залежно від інших бібліотек !!
Мехул Джойсар

@ Лукас - Я використовую те саме для Radio Group, інтерфейс працює нормально, але функціональність Radio Group порушена. Значить, в одній радіогрупі я можу поставити галочки на декількох кнопках радіо. Я хочу перевірити лише одну Радіо Кнопку одночасно. Будь ласка, допоможіть для цього.
Рахул Пацідар

Просто головою вгору це не працює в жодному прокручуваному контейнері! :(
JakeWilson801

@ JakeWilson801 Він працював у прокручуванні контейнера для мене. Але тепер у RecyclerView є FlexboxLayoutManager , тому, можливо, це вирішить вашу проблему.
Лукас Новак

95

Я зробив свій власний макет, який робить те, що я хочу, але наразі він досить обмежений. Коментарі та пропозиції щодо вдосконалення, звичайно, вітаються.

Діяльність:

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 з необхідністю атрибуції", тому ... Перевірте дно для деталей
Генрік Густафссон

8
Чомусь я отримую "ClassCastException android.view.view не можна передавати в android.view.viewgroup" при спробі надути подання в редакторі верстки Android. Будь-яка ідея, як це виправити?
гілочка

1
Для тих, хто приходить за цією відповіддю - onMeasure () містить деяку погану логіку і не працює, як очікувалося. Уявіть, що цей батько ViewGroup надсилає запит на вимірювання там, де його не хвилює висота, він надсилатиме 0 як висоту, а 0 - як режим вимірювання. PredicateLayout отримає 0 як власний зріст і змусить дітей бути AT_MOST 0 висоти.
devmiles.com

3
Замість того, щоб додати коментар до коду, ви не могли просто виправити помилку? :)
Генрік Густафссон

43

Я реалізував щось дуже подібне до цього, але використовуючи те, що, на мою думку, є трохи більш стандартним способом поводження з інтервалом та прокладками. Будь ласка, дайте мені знати, що ви думаєте, і не соромтесь повторно використовувати без атрибуції:

package com.asolutions.widget;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

import com.asolutions.widget.R;

public class RowLayout extends ViewGroup {
    public static final int DEFAULT_HORIZONTAL_SPACING = 5;
    public static final int DEFAULT_VERTICAL_SPACING = 5;
    private final int horizontalSpacing;
    private final int verticalSpacing;
    private List<RowMeasurement> currentRows = Collections.emptyList();

    public RowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.RowLayout);
        horizontalSpacing = styledAttributes.getDimensionPixelSize(R.styleable.RowLayout_android_horizontalSpacing,
                DEFAULT_HORIZONTAL_SPACING);
        verticalSpacing = styledAttributes.getDimensionPixelSize(R.styleable.RowLayout_android_verticalSpacing,
                DEFAULT_VERTICAL_SPACING);
        styledAttributes.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        final int maxInternalWidth = MeasureSpec.getSize(widthMeasureSpec) - getHorizontalPadding();
        final int maxInternalHeight = MeasureSpec.getSize(heightMeasureSpec) - getVerticalPadding();
        List<RowMeasurement> rows = new ArrayList<RowMeasurement>();
        RowMeasurement currentRow = new RowMeasurement(maxInternalWidth, widthMode);
        rows.add(currentRow);
        for (View child : getLayoutChildren()) {
            LayoutParams childLayoutParams = child.getLayoutParams();
            int childWidthSpec = createChildMeasureSpec(childLayoutParams.width, maxInternalWidth, widthMode);
            int childHeightSpec = createChildMeasureSpec(childLayoutParams.height, maxInternalHeight, heightMode);
            child.measure(childWidthSpec, childHeightSpec);
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();
            if (currentRow.wouldExceedMax(childWidth)) {
                currentRow = new RowMeasurement(maxInternalWidth, widthMode);
                rows.add(currentRow);
            }
            currentRow.addChildDimensions(childWidth, childHeight);
        }

        int longestRowWidth = 0;
        int totalRowHeight = 0;
        for (int index = 0; index < rows.size(); index++) {
            RowMeasurement row = rows.get(index);
            totalRowHeight += row.getHeight();
            if (index < rows.size() - 1) {
                totalRowHeight += verticalSpacing;
            }
            longestRowWidth = Math.max(longestRowWidth, row.getWidth());
        }
        setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? MeasureSpec.getSize(widthMeasureSpec) : longestRowWidth
                + getHorizontalPadding(), heightMode == MeasureSpec.EXACTLY ? MeasureSpec.getSize(heightMeasureSpec)
                : totalRowHeight + getVerticalPadding());
        currentRows = Collections.unmodifiableList(rows);
    }

    private int createChildMeasureSpec(int childLayoutParam, int max, int parentMode) {
        int spec;
        if (childLayoutParam == LayoutParams.FILL_PARENT) {
            spec = MeasureSpec.makeMeasureSpec(max, MeasureSpec.EXACTLY);
        } else if (childLayoutParam == LayoutParams.WRAP_CONTENT) {
            spec = MeasureSpec.makeMeasureSpec(max, parentMode == MeasureSpec.UNSPECIFIED ? MeasureSpec.UNSPECIFIED
                    : MeasureSpec.AT_MOST);
        } else {
            spec = MeasureSpec.makeMeasureSpec(childLayoutParam, MeasureSpec.EXACTLY);
        }
        return spec;
    }

    @Override
    protected void onLayout(boolean changed, int leftPosition, int topPosition, int rightPosition, int bottomPosition) {
        final int widthOffset = getMeasuredWidth() - getPaddingRight();
        int x = getPaddingLeft();
        int y = getPaddingTop();

        Iterator<RowMeasurement> rowIterator = currentRows.iterator();
        RowMeasurement currentRow = rowIterator.next();
        for (View child : getLayoutChildren()) {
            final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();
            if (x + childWidth > widthOffset) {
                x = getPaddingLeft();
                y += currentRow.height + verticalSpacing;
                if (rowIterator.hasNext()) {
                    currentRow = rowIterator.next();
                }
            }
            child.layout(x, y, x + childWidth, y + childHeight);
            x += childWidth + horizontalSpacing;
        }
    }

    private List<View> getLayoutChildren() {
        List<View> children = new ArrayList<View>();
        for (int index = 0; index < getChildCount(); index++) {
            View child = getChildAt(index);
            if (child.getVisibility() != View.GONE) {
                children.add(child);
            }
        }
        return children;
    }

    protected int getVerticalPadding() {
        return getPaddingTop() + getPaddingBottom();
    }

    protected int getHorizontalPadding() {
        return getPaddingLeft() + getPaddingRight();
    }

    private final class RowMeasurement {
        private final int maxWidth;
        private final int widthMode;
        private int width;
        private int height;

        public RowMeasurement(int maxWidth, int widthMode) {
            this.maxWidth = maxWidth;
            this.widthMode = widthMode;
        }

        public int getHeight() {
            return height;
        }

        public int getWidth() {
            return width;
        }

        public boolean wouldExceedMax(int childWidth) {
            return widthMode == MeasureSpec.UNSPECIFIED ? false : getNewWidth(childWidth) > maxWidth;
        }

        public void addChildDimensions(int childWidth, int childHeight) {
            width = getNewWidth(childWidth);
            height = Math.max(height, childHeight);
        }

        private int getNewWidth(int childWidth) {
            return width == 0 ? childWidth : width + horizontalSpacing + childWidth;
        }
    }
}

Для цього також потрібна запис під /res/values/attrs.xml, яку ви можете створити, якщо її ще немає.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="RowLayout">
        <attr name="android:verticalSpacing" />
        <attr name="android:horizontalSpacing" />
    </declare-styleable>
</resources>

Використання у форматі XML виглядає приблизно так:

<?xml version="1.0" encoding="utf-8"?>
<com.asolutions.widget.RowLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:padding="10dp"
    android:horizontalSpacing="10dp"
    android:verticalSpacing="20dp">
    <FrameLayout android:layout_width="30px" android:layout_height="40px" android:background="#F00"/>
    <FrameLayout android:layout_width="60px" android:layout_height="40px" android:background="#F00"/>
    <FrameLayout android:layout_width="70px" android:layout_height="20px" android:background="#F00"/>
    <FrameLayout android:layout_width="20px" android:layout_height="60px" android:background="#F00"/>
    <FrameLayout android:layout_width="10px" android:layout_height="40px" android:background="#F00"/>
    <FrameLayout android:layout_width="40px" android:layout_height="40px" android:background="#F00"/>
    <FrameLayout android:layout_width="40px" android:layout_height="40px" android:background="#F00"/>
    <FrameLayout android:layout_width="40px" android:layout_height="40px" android:background="#F00"/>
</com.asolutions.widget.RowLayout>

Я цим користуюся. Я додаю елементи динамічно до цього макета. Це додається, але кожен погляд - це спосіб сказати крихітний маленький. чому?
Умаїр А.

3
Це прекрасно працює для мене, оголошуючи макет і дітей у XML. Дякую!
Девід Снабель-Конт

3
Працює і для мене. Гарна робота Міхе! Дякуємо, що поділилися цим рішенням!
Марсело

Серед усіх розглянутих нами рішень це було єдине, яке не перекинуло ClassCastException при використанні його з файлу макета xml.
mdelolmo

Дякуємо, що опублікували це рішення! Тільки те, що мені було потрібно; врятувало мене години
JulianSymes


11

Ось моя спрощена версія для коду:

    package com.superliminal.android.util;

    import android.content.Context;
    import android.util.AttributeSet;
    import android.view.View;
    import android.view.ViewGroup;


    /**
     * A view container with layout behavior like that of the Swing FlowLayout.
     * Originally from http://nishantvnair.wordpress.com/2010/09/28/flowlayout-in-android/ itself derived from
     * http://stackoverflow.com/questions/549451/line-breaking-widget-layout-for-android
     *
     * @author Melinda Green
     */
    public class FlowLayout extends ViewGroup {
        private final static int PAD_H = 2, PAD_V = 2; // Space between child views.
        private int mHeight;

        public FlowLayout(Context context) {
            super(context);
        }

        public FlowLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            assert (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED);
            final int width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
            int height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom();
            final int count = getChildCount();
            int xpos = getPaddingLeft();
            int ypos = getPaddingTop();
            int childHeightMeasureSpec;
            if(MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST)
                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
            else
                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
            mHeight = 0;
            for(int i = 0; i < count; i++) {
                final View child = getChildAt(i);
                if(child.getVisibility() != GONE) {
                    child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), childHeightMeasureSpec);
                    final int childw = child.getMeasuredWidth();
                    mHeight = Math.max(mHeight, child.getMeasuredHeight() + PAD_V);
                    if(xpos + childw > width) {
                        xpos = getPaddingLeft();
                        ypos += mHeight;
                    }
                    xpos += childw + PAD_H;
                }
            }
            if(MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) {
                height = ypos + mHeight;
            } else if(MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
                if(ypos + mHeight < height) {
                    height = ypos + mHeight;
                }
            }
            height += 5; // Fudge to avoid clipping bottom of last row.
            setMeasuredDimension(width, height);
        } // end onMeasure()

        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            final int width = r - l;
            int xpos = getPaddingLeft();
            int ypos = getPaddingTop();
            for(int i = 0; i < getChildCount(); i++) {
                final View child = getChildAt(i);
                if(child.getVisibility() != GONE) {
                    final int childw = child.getMeasuredWidth();
                    final int childh = child.getMeasuredHeight();
                    if(xpos + childw > width) {
                        xpos = getPaddingLeft();
                        ypos += mHeight;
                    }
                    child.layout(xpos, ypos, xpos + childw, ypos + childh);
                    xpos += childw + PAD_H;
                }
            }
        } // end onLayout()

    }

Я додав погляд на макет і дав класу це ім’я, а потім додав дочірні кнопки до цього подання. Але висоту їх я не можу контролювати? Всі вони виглядають, щоб мати підкладку зверху / знизу, що я не хочу.
Мігель

Ви можете встановити PAD_V = 0 або додати аргументи конструктора для налаштування прокладки.
Мелінда Грін

7

Виникла проблема з першою відповіддю:

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

Наприклад, у ListView, елементи списку передаються висотоюMeasureSpec 0 (НЕСПЕКТИВНО), і ось тут MeasureSpec розміром 0 (AT_MOST) передається всім дітям. Це означає, що весь PredicateLayout невидимий (висота 0).

Як швидке виправлення, я змінив MeasureSpec висоти дитини так:

int childHeightMeasureSpec;
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
    childHeightMeasureSpec =
        MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
}
else {
    childHeightMeasureSpec = 
        MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);            
}

і потім

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

який, здається, працює для мене, хоча не справляється з ТОЧНИМ режимом, що було б набагато складніше.


2
Це також виправляє проблему, коли макет розміщується в огляді прокрутки. Дякую, що ви вирішили цю проблему, тому мені не довелося це робити для себе.
skorulis

4

Моя дещо змінена версія на основі опублікованих тут раніше:

  • Він працює в редакторі макетів 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;
            }
        }
    }
    }

3

Я оновив цей зразок, щоб виправити помилку, тепер кожен рядок може мати різну висоту!

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

/**
 * ViewGroup that arranges child views in a similar way to text, with them laid
 * out one line at a time and "wrapping" to the next line as needed.
 * 
 * Code licensed under CC-by-SA
 *  
 * @author Henrik Gustafsson
 * @see http://stackoverflow.com/questions/549451/line-breaking-widget-layout-for-android
 * @license http://creativecommons.org/licenses/by-sa/2.5/
 *
 * Updated by Aurélien Guillard
 * Each line can have a different height
 * 
 */
public class FlowLayout extends ViewGroup {

    public static class LayoutParams extends ViewGroup.LayoutParams {

        public final int horizontal_spacing;
        public final int vertical_spacing;

        /**
         * @param horizontal_spacing Pixels between items, horizontally
         * @param vertical_spacing Pixels between items, vertically
         */
        public LayoutParams(int horizontal_spacing, int vertical_spacing) {
            super(0, 0);
            this.horizontal_spacing = horizontal_spacing;
            this.vertical_spacing = vertical_spacing;
        }
    }

    public FlowLayout(Context context) {
        super(context);
    }

    public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        assert (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED);

        final int width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();

        int height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom();
        final int count = getChildCount();
        int line_height = 0;

        int xpos = getPaddingLeft();
        int ypos = getPaddingTop();

        int childHeightMeasureSpec;

        if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);

        } else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);

        } else {
            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        }

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), childHeightMeasureSpec);
                final int childw = child.getMeasuredWidth();

                if (xpos + childw > width) {
                    xpos = getPaddingLeft();
                    ypos += line_height;
                }

                xpos += childw + lp.horizontal_spacing;
                line_height = child.getMeasuredHeight() + lp.vertical_spacing;
            }
        }

        if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) {
            height = ypos + line_height;

        } else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
            if (ypos + line_height < height) {
                height = ypos + line_height;
            }
        }
        setMeasuredDimension(width, height);
    }

    @Override
    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(1, 1); // default of 1px spacing
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        if (p instanceof LayoutParams) {
            return true;
        }
        return false;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int count = getChildCount();
        final int width = r - l;
        int xpos = getPaddingLeft();
        int ypos = getPaddingTop();
        int lineHeight = 0;

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final int childw = child.getMeasuredWidth();
                final int childh = child.getMeasuredHeight();
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                if (xpos + childw > width) {
                    xpos = getPaddingLeft();
                    ypos += lineHeight;
                }

                lineHeight = child.getMeasuredHeight() + lp.vertical_spacing;

                child.layout(xpos, ypos, xpos + childw, ypos + childh);
                xpos += childw + lp.horizontal_spacing;
            }
        }
    }
}

1

Деякі з різних відповідей тут дадуть мені ClassCastException в редакторі макетів Exclipse. У моєму випадку я хотів використовувати ViewGroup.MarginLayoutParams, а не робити свій власний. У будь-якому випадку переконайтеся, що повертаєте екземпляр LayoutParams, який потрібен вашому користувальницькому класу компонування в generatedLayoutParams. Ось як виглядає моя, просто замініть MarginLayoutParams на потрібний ViewGroup:

@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
    return new MarginLayoutParams(getContext(), attrs);
}

@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    return p instanceof MarginLayoutParams;
}

Схоже, цей метод закликається призначити об'єкт LayoutParams для кожної дитини в ViewGroup.


1
    LinearLayout row = new LinearLayout(this);

    //get the size of the screen
    Display display = getWindowManager().getDefaultDisplay();
    this.screenWidth = display.getWidth();  // deprecated

    for(int i=0; i<this.users.length; i++) {

        row.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));

        this.tag_button = new Button(this);
        this.tag_button.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, 70));
        //get the size of the button text
        Paint mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setTextSize(tag_button.getTextSize());
        mPaint.setTypeface(Typeface.create(Typeface.SERIF, Typeface.NORMAL));
        float size = mPaint.measureText(tag_button.getText().toString(), 0, tag_button.getText().toString().length());
        size = size+28;
        this.totalTextWidth += size;

        if(totalTextWidth < screenWidth) {
            row.addView(tag_button);
        } else {
            this.tags.addView(row);
            row = new LinearLayout(this);
            row.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
            row.addView(tag_button);
            this.totalTextWidth = size;
        }
    }

1

Я адаптував частину оди вище та застосував схему потоку, яка зосереджує всі дитячі погляди, горизонтальні та вертикальні. Це відповідає моїм потребам.

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


0

Спробуйте встановити обидва LP LayoutParams «s бути WRAP_CONTENT.

Установка MLP бути WRAP_CONTENT, WRAP_CONTENTгарантує , що ваш TextView (s) т тільки широкий і досить високий , досить , щоб тримати «Hello» або будь-який інший рядки ви поклали в них. Я думаю, що я, можливо, не знаю, наскільки широкі твої т . setSingleLine(true)Може сприяти теж.


Наскільки мені відомо, LinearLayout не робить обгортання. WRAP_CONTENT на обох осях зробить віджет LinearLayout на одну лінію високою і розширить шлях від екрана. І, як я розумію, setSingleLine на текстовому віджеті впливає лише на розмір і форму цього віджета, а не на його батьківський макет.
Генрік Густафссон

@Вось, я думаю, ви неправильно зрозуміли значення WRAP_CONTENT.
jfritz42
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.