101 Stimmen

Zeilenbrechendes Widget-Layout für Android

Ich versuche, eine Aktivität zu erstellen, die dem Benutzer einige Daten präsentiert. Die Daten sind so, dass sie in "Wörter" unterteilt werden können, wobei jedes ein Widget ist, und eine Folge von "Wörtern" würde die Daten ("Satz"?) bilden, wobei das ViewGroup-Widget die Wörter enthält. Da der Platzbedarf für alle "Wörter" in einem "Satz" den verfügbaren horizontalen Platz auf dem Bildschirm übersteigen würde, möchte ich diese "Sätze" wie ein normales Stück Text umbrechen.

Der folgende Code:

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

ergibt etwas wie das linke Bild, aber ich möchte ein Layout, das die gleichen Widgets wie im rechten Bild zeigt.

non-wrapping

wrapping

Gibt es ein solches Layout oder eine Kombination von Layouts und Parametern, oder muss ich dafür meine eigene ViewGroup implementieren?

7voto

Mark Punkte 7074

Es gibt ein Problem mit der ersten Antwort:

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

In einer ListView zum Beispiel wird den Listenelementen eine heightMeasureSpec von 0 (UNSPECIFIED) übergeben, und so wird hier die MeasureSpec der Größe 0 (AT_MOST) an alle Kinder übergeben. Dies bedeutet, dass das gesamte PredicateLayout unsichtbar ist (Höhe 0).

Als schnelle Lösung habe ich die Kinderhöhe MeasureSpec wie folgt geändert:

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

und dann

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

was bei mir zu funktionieren scheint, obwohl es den EXACT-Modus nicht beherrscht, was viel schwieriger wäre.

4voto

Meine leicht abgewandelte Version, die auf einem früheren Beitrag hier basiert:

  • Es funktioniert im Eclipse Layout Editor
  • Es zentriert alle Elemente horizontal

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

3voto

Aurélien Guillard Punkte 1183

Ich habe dieses Beispiel aktualisiert, um einen Fehler zu beheben, jetzt kann jede Zeile eine andere Höhe haben!

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

1voto

Henry Punkte 2640

Einige der verschiedenen Antworten hier würden mir eine ClassCastException im Layout-Editor von Exclipse. In meinem Fall wollte ich ViewGroup.MarginLayoutParams verwenden, anstatt meine eigenen zu erstellen. Wie auch immer, stellen Sie sicher, dass Sie die Instanz von LayoutParams zurückgeben, die Ihre benutzerdefinierte Layoutklasse in generateLayoutParams benötigt. So sieht mein Layout aus. Ersetzen Sie einfach MarginLayoutParams durch das von Ihrer ViewGroup benötigte:

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

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

Es sieht so aus, dass diese Methode aufgerufen wird, um ein LayoutParams-Objekt für jedes Kind in der ViewGroup zuzuweisen.

1voto

Muhammad Aamir Ali Punkte 19629
    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;
        }
    }

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X