Einfache Möglichkeit, ein dynamisches, aber quadratisches Layout zu erstellen

84

Ich verwende a GridView, um eine Reihe von Ansichten anzuzeigen, die im Wesentlichen sind LinearLayouts. Ich möchte LinearLayouts, dass alle quadratisch sind, aber ich möchte auch, dass sie dynamisch dimensioniert werden - das heißt, es gibt zwei Spalten, und ich möchte, dass sich die Spalten LinearLayoutsje nach Größe des Bildschirms dehnen, aber quadratisch bleiben. Gibt es eine Möglichkeit, dies über das xmlLayout zu tun, oder muss ich die Höhen und Breiten programmgesteuert einstellen?

Catherine
quelle

Antworten:

113

Eine saubere Lösung für quadratische GridViewElemente besteht darin, sie wie folgt zu erweitern RelativeLayoutoder zu LinearLayoutüberschreiben onMeasure:

@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}
jdamcd
quelle
2
Hierbei wird eher die Kennzahlspezifikation als setMeasuredDimension () verwendet, sodass dies gut ist. Aber es ist nicht sehr flexibel, da es immer die Breite braucht.
Adam
3
Wie @DavidMerriman oben sagte, wird die Ansicht, wenn die Ansicht breiter als hoch ist, unterhalb des Anzeigebereichs erweitert, wodurch ein Teil der Ansicht abgeschnitten wird.
vcapra1
5
Dies wäre flexibler, wenn der Wert von Math.min (widthMeasureSpec, heightMeasureSpec) an beide Argumente von super.onMeasure () übergeben würde
Paul Wintz
Dies funktioniert problemlos mit Aktivitäten, jedoch nicht mit einem App-Widget.
Apollo-Roboto
72

Es könnte spät sein zu antworten, aber dennoch wird es für die neuen Besucher hilfreich sein.

Mit dem neuen ConstraintLayout das in Android Studio 2.3 eingeführt wurde, ist es jetzt ganz einfach, reaktionsschnelle Layouts zu erstellen.

Fügen Sie in einem übergeordneten ConstraintLayout dieses Attribut hinzu, damit eines der untergeordneten Elemente / Layouts dynamisch quadratisch ist

app:layout_constraintDimensionRatio="w,1:1"

w gibt breitenbezogene Einschränkungen an und das Verhältnis 1: 1 sorgt für ein quadratisches Layout.

Himanshu Chauhan
quelle
9
Dies ist eine Antwort des Königs
Itzhar
2
Beste Antwort, wenn Sie ein vorhandenes Layout als Quadrat anzeigen möchten!
Quentin Klein
9
Stellen Sie sicher, dass "0dp" für Layoutbreite und -höhe im untergeordneten Layout festgelegt ist. Sonst funktioniert es nicht.
Thilina Kj
Es funktioniert auch für die Ansicht selbst. Ich meine, wenn das untergeordnete Element von contrantLayout quadratisch sein soll, können Sie das Attribut layout_constraintDimensionRatio direkt in der
untergeordneten
1
Vielen Dank für die Lösung; Ich habe gemacht, ob es einen vollständigen Überblick über meinen Code mit Ihrer Lösung gibt: gist.github.com/nadar71/006699e31ef7451813e6c97dacfbc5e2
android_dev71
44

In der XML-Datei gibt es nichts, mit dem Sie die Eigenschaften width und height verknüpfen können. Wahrscheinlich ist es am einfachsten, Unterklassen zu erstellen LinearLayoutund zu überschreibenonMeasure

@Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);
    int size = width > height ? height : width;
    setMeasuredDimension(size, size);
}

Ich habe dies verwendet, um Ansichten zu erstellen, die vorher immer quadratisch sind. Es sollte immer noch funktionieren für eine LinearLayout.

Weitere Informationen, die Ihnen dabei helfen: http://developer.android.com/guide/topics/ui/custom-components.html http://developer.android.com/reference/android/view/View.MeasureSpec.html

David Merriman
quelle
Ja, am Ende habe ich etwas Ähnliches gemacht. Vielen Dank!
Catherine
1
Wenn dies für Sie funktioniert hat, können Sie dies als die richtige Antwort markieren?
David Merriman
1
@ Catherine hey Catherine, kannst du dein Snippet posten? Vielleicht wäre es für andere nützlich :-)
Marek Sebera
3
Vergessen Sie nicht hinzuzufügen : super.onMeasure(widthMeasureSpec, widthMeasureSpec);!
deKajoo
1
@deKajoo Using super.onMeasure(widthMeasureSpec, widthMeasureSpec);gibt Ihnen ein Quadrat, aber es ist immer Breite x Breite. Wenn die angegebenen Maße breiter als hoch sind, liegt ein Problem vor. Meine Lösung verwendet das kleinere der beiden Maße, sodass Sie immer das größte Quadrat erhalten, das in das angegebene Rechteck passt.
David Merriman
43

Ich habe es so gemacht:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int size;
    if(widthMode == MeasureSpec.EXACTLY && widthSize > 0){
        size = widthSize;
    }
    else if(heightMode == MeasureSpec.EXACTLY && heightSize > 0){
        size = heightSize;
    }
    else{
        size = widthSize < heightSize ? widthSize : heightSize;
    }

    int finalMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
    super.onMeasure(finalMeasureSpec, finalMeasureSpec);
}

Bei dieser Implementierung ist Ihr Layout quadratisch, wobei die geringere Größe zwischen Breite und Höhe angenommen wird. Und es kann sogar mit dynamischen Werten festgelegt werden, z. B. mithilfe der Gewichtung in einem LinearLayout.

Fernando Camargo
quelle
Die Methode "onMeasure" soll in einer Klasse überschrieben werden, die GridView erweitert.
An-Droide
Es wird in der Klasse überschrieben, in der es quadratisch sein soll. Für den Fall der Frage sind es die LinearLayouts in der GridView.
Fernando Camargo
Dieser hat mir geholfen, einige seltsame Probleme bei der Verwendung einer quadratischen Ansicht in einem StaggeredGridLayout zu beheben. Meine naivere Umsetzung war nicht gut genug. Vielen Dank!
Peterdk
10

Wir können es auf sehr einfache Weise tun - rufen Sie einfach super.onMeasure()zweimal an.

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int width = getMeasuredWidth();
    int height = getMeasuredHeight();
    int squareLen = Math.min(width, height);

    super.onMeasure(
        MeasureSpec.makeMeasureSpec(squareLen, MeasureSpec.EXACTLY), 
        MeasureSpec.makeMeasureSpec(squareLen, MeasureSpec.EXACTLY));
}

Durch super.onMeasure()zweimaliges Aufrufen ist dies im Hinblick auf den Zeichenprozess weniger effizient, aber es ist eine einfache Möglichkeit, Layoutprobleme zu beheben, die die anderen Antworten verursachen können.

Passantwhu
quelle
2
Anstatt super.onMeasure zweimal aufzurufen, müssen Sie das zweite Mal nur setMeasuredDimension (squareLen, squareLen) aufrufen.
user3802077
Durch super.onMeasure()zweimaliges Aufrufen wird sichergestellt, dass das Layout angesichts der neuen Größe der Ansicht korrekt ausgeführt wird. Ohne dies müssen wir ein Layout auf andere Weise auslösen oder mit falschen Layouts umgehen.
Richard Le Mesurier
3

Es ist so einfach wie:

public class SquareRelativeLayout extends RelativeLayout {

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

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

    public SquareRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
    {
        if (widthMeasureSpec < heightMeasureSpec) 
            super.onMeasure(widthMeasureSpec, widthMeasureSpec);
        else
            super.onMeasure(heightMeasureSpec, heightMeasureSpec);
    }
}
Abdul Wasae
quelle
1

Hier ist eine Lösung, die für alle Layoutparameter funktioniert, die auf Ansicht oder Ansichtsgruppe eingestellt werden können:

    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);
    int widthDesc = MeasureSpec.getMode(widthMeasureSpec);
    int heightDesc = MeasureSpec.getMode(heightMeasureSpec);
    int size = 0;
    if (widthDesc == MeasureSpec.UNSPECIFIED
            && heightDesc == MeasureSpec.UNSPECIFIED) {
        size = DP(defaultSize); // Use your own default size, in our case
                                // it's 125dp
    } else if ((widthDesc == MeasureSpec.UNSPECIFIED || heightDesc == MeasureSpec.UNSPECIFIED)
            && !(widthDesc == MeasureSpec.UNSPECIFIED && heightDesc == MeasureSpec.UNSPECIFIED)) {
                    //Only one of the dimensions has been specified so we choose the dimension that has a value (in the case of unspecified, the value assigned is 0)
        size = width > height ? width : height;
    } else {
                    //In all other cases both dimensions have been specified so we choose the smaller of the two
        size = width > height ? height : width;
    }
    setMeasuredDimension(size, size);

Prost

Zaid Daghestani
quelle
Diese Antwort weist einige interessante Merkmale auf und ist möglicherweise robuster, erfordert jedoch Arbeit und sollte Messspezifikationen erstellen, die an super.onMeasure () und nicht an setMeasuredDimension () übergeben werden, da dies eine Vielzahl von Superklassen ermöglichen würde.
Adam
1

Mein Vorschlag ist, eine benutzerdefinierte Layoutklasse zu erstellen, die von FrameLayout erbt. Überschreiben Sie die OnMeasure () -Methode und fügen Sie das gewünschte Steuerelement in das SquareFrameLayout ein.

So wird es in Xamarin.Android gemacht:

public class SquareFrameLayout : FrameLayout
{
    private const string _tag = "SquareFrameLayout";

    public SquareFrameLayout(Android.Content.Context context):base(context) {}
    public SquareFrameLayout(IntPtr javaReference, Android.Runtime.JniHandleOwnership transfer):base(javaReference, transfer) {}
    public SquareFrameLayout(Android.Content.Context context, IAttributeSet attrs):base(context, attrs) {}
    public SquareFrameLayout(Android.Content.Context context, IAttributeSet attrs, int defStyleAttr):base(context, attrs, defStyleAttr) {}
    public SquareFrameLayout(Android.Content.Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes):base(context, attrs, defStyleAttr, defStyleRes) {}

    protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        var widthMode = MeasureSpec.GetMode(widthMeasureSpec);
        int widthSize = MeasureSpec.GetSize(widthMeasureSpec);
        var heightMode = MeasureSpec.GetMode(heightMeasureSpec);
        int heightSize = MeasureSpec.GetSize(heightMeasureSpec);

        int width, height;

        switch (widthMode)
        {
            case MeasureSpecMode.Exactly:
                width = widthSize;
                break;
            case MeasureSpecMode.AtMost:
                width = Math.Min(widthSize, heightSize);
                break;
            default:
                width = 100;
                break;
        }

        switch (heightMode)
        {
            case MeasureSpecMode.Exactly:
                height = heightSize;
                break;
            case MeasureSpecMode.AtMost:
                height = Math.Min(widthSize, heightSize);
                break;
            default:
                height = 100;
                break;
        }

        Log.Debug(_tag, $"OnMeasure({widthMeasureSpec}, {heightMeasureSpec}) => Width mode: {widthMode}, Width: {widthSize}/{width}, Height mode: {heightMode}, Height: {heightSize}/{height}");
        var size = Math.Min(width, height);
        var newMeasureSpec = MeasureSpec.MakeMeasureSpec(size, MeasureSpecMode.Exactly);
        base.OnMeasure(newMeasureSpec, newMeasureSpec);
    }
}

Wenn Sie möchten, dass eine Ansicht (oder ein anderes Steuerelement) quadratisch (und zentriert) ist, fügen Sie sie einfach wie folgt zu Ihrem Layout hinzu:

<your.namespace.SquareFrameLayout
    android:id="@+id/squareContainer"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center">
    <View
        android:id="@+id/squareContent"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</your.namespace.SquareFrameLayout>
sdippl
quelle
0

Schauen Sie sich SquareLayout an , eine Android-Bibliothek die eine Wrapper-Klasse für verschiedene Layouts bereitstellt und diese quadratisch dimensioniert, ohne dass Kernfunktionen verloren gehen.

Die Bemaßungen werden unmittelbar vor dem Rendern des Layouts berechnet. Daher muss nach dem Abrufen der Ansicht kein erneutes Rendern oder ähnliches angepasst werden.

Um die Bibliothek zu verwenden, fügen Sie dies Ihrem build.gradle hinzu:

repositories {
    maven {
        url "https://maven.google.com"
    }
}

dependencies {
    compile 'com.github.kaushikthedeveloper:squarelayout:0.0.3'
}

Das, was Sie benötigen, ist SquareLinearLayout .

Kaushik NP
quelle
Funktioniert nicht für mich auf einem realen Gerät :( Rendering gut in der Android Studio-Vorschau, funktioniert aber nicht auf einem Gerät
Eugene Voronoy
0

Für alle, die eine Lösung suchen Mit Kotlin habe ich Folgendes gemacht FrameLayout.

package your.package.name

import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout

class SquareLayout: FrameLayout {

    constructor(ctx: Context) : super(ctx)
    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        if (widthMeasureSpec < heightMeasureSpec)
            super.onMeasure(widthMeasureSpec, widthMeasureSpec)
        else
            super.onMeasure(heightMeasureSpec, heightMeasureSpec)
    }
}
Wasser
quelle
0

Versuchen Sie diesen Code:

public class SquareRelativeLayout extends RelativeLayout {
    public SquareRelativeLayout(Context context) {
        super(context);
    }

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int size;
        if (widthMode == MeasureSpec.EXACTLY && widthSize > 0) {
            size = widthSize;
        } else if (heightMode == MeasureSpec.EXACTLY && heightSize > 0) {
            size = heightSize;
        } else {
            size = widthSize < heightSize ? widthSize : heightSize;
        }

        int finalMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
        super.onMeasure(finalMeasureSpec, finalMeasureSpec);
    }
}
reza_khalafi
quelle