101 votes

Disposition de widgets de rupture de ligne pour Android

Je suis en train de créer une activité qui présente des données relatives à l'utilisateur. Les données sont tels qu'il peut être divisé en "mots", chacun étant un widget, et la séquence de "mots" forme les données ('phrase'?), le ViewGroup widget contenant les mots. Comme l'espace nécessaire pour tous les "mots" dans une "phrase" permettrait de dépasser l'espace horizontal disponible sur l'écran, j'aimerais conclure ces "phrases", comme vous le feriez pour un morceau de texte.

Le code suivant:

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

les rendements de quelque chose comme l'image de gauche, mais je voudrais une mise en page présentant les mêmes widgets comme dans celle de droite.

non-wrappingwrapping

Est-il une telle disposition ou de la combinaison de mises en page et les paramètres, ou dois-je mettre en place mon propre ViewGroup pour cela?

95voto

Henrik Gustafsson Points 11755

J'ai fait ma propre mise en page qui fait ce que je veux, mais elle est assez limitée pour le moment. Les commentaires et suggestions d'amélioration sont bien sûr les bienvenus.

L'activité:

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

Ou dans une mise en page XML:

 <se.fnord.android.layout.PredicateLayout
    android:id="@+id/predicate_layout"
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content"
/>
 

Et la mise en page:

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

Avec le résultat:

Widgets enveloppés

43voto

Micah Hainline Points 6629

J'ai implémenté quelque chose de très similaire à cela, mais utiliser ce que je pense est un moyen un peu plus standard de gérer l'espacement et le remplissage. Dites-moi ce que vous en pensez, et n'hésitez pas à le réutiliser sans l'attribuer:

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

Cela nécessite également une entrée sous /res/values/attrs.xml, que vous pouvez créer si ce n’est pas déjà fait.

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

L'utilisation dans une présentation XML ressemble à ceci:

 <?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>
 

16voto

Mohsen Afshin Points 3643

Le projet android-flowlayout de ApmeM support line s'arrête également.

entrez la description de l'image ici

12voto

Melinda Green Points 1033

Voici ma version simplifiée, en code uniquement:

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

    }
 

7voto

westmeadboy Points 61

Il y a un problème avec la première Réponse:

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

Dans une liste, par exemple, les éléments de la liste sont passés d'un heightMeasureSpec de 0 (non spécifié) et donc, ici, le MeasureSpec de taille 0 (AT_MOST) est passée à tous les enfants. Cela signifie que l'ensemble de la PredicateLayout est invisible (hauteur 0).

Comme une solution rapide, j'ai changé l'enfant hauteur MeasureSpec comme ceci:

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

et puis

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

qui semble fonctionner pour moi bien que ne gère pas le mode EXACT qui serait beaucoup plus difficile.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X