Modo semplice per fare layout dinamico ma quadrato


84

Sto usando a GridViewper visualizzare una serie di visualizzazioni che sono essenzialmente LinearLayouts. Voglio che LinearLayoutssiano tutte quadrate, ma voglio anche che siano dimensionate dinamicamente, cioè ci sono due colonne e voglio LinearLayoutsche si estendano a seconda delle dimensioni dello schermo ma rimangano quadrate. C'è un modo per farlo attraverso il xmllayout o devo impostare le altezze e le larghezze a livello di programmazione?

Risposte:


113

Una buona soluzione per gli GridViewelementi quadrati è estendere RelativeLayouto LinearLayouted eseguire l'override in questo onMeasuremodo:

@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}

2
Questo sta usando la specifica della misura invece di usare setMeasuredDimension (), quindi va bene. Ma non è molto flessibile poiché prende sempre la larghezza.
Adam

3
Come ha detto @DavidMerriman sopra, se la vista è più ampia che alta, ciò farà sì che la vista si estenda al di sotto dell'area di visualizzazione, tagliando parte della vista.
vcapra1

5
Questo sarebbe più flessibile se passasse il valore di Math.min (widthMeasureSpec, heightMeasureSpec) in entrambi gli argomenti di super.onMeasure ()
Paul Wintz

Funzionerà facilmente con le attività ma non con un widget dell'app.
Apollo-Roboto

73

Potrebbe essere tardi per rispondere, ma comunque sarà utile ai nuovi visitatori.

Con il nuovo ConstraintLayout introdotto in Android Studio 2.3, ora è abbastanza facile creare layout reattivi.

In un ConstraintLayout genitore, per fare in modo che una delle sue viste / layout figlio sia quadrata dinamicamente, aggiungi questo attributo

app:layout_constraintDimensionRatio="w,1:1"

w serve a specificare i vincoli in base alla larghezza e il rapporto 1: 1 garantisce il layout quadrato.


9
questa è una risposta da re
itzhar

2
Risposta migliore se vuoi visualizzare un layout esistente come un quadrato!
Quentin Klein

9
Assicurati di impostare "0dp" per la larghezza e l'altezza del layout nel layout figlio. Altrimenti non funzionerà.
thilina Kj

Funziona anche per la vista stessa, voglio dire se vuoi che il figlio di un contrantLayout sia quadrato, puoi definire l'attributo layout_constraintDimensionRatio direttamente nella vista figlio
Plinio.Santos

1
Grazie mille per la soluzione; Ho creato se aiuta una sintesi
android_dev71

44

Non c'è niente nell'xml che ti permetta di collegare le proprietà di larghezza e altezza. Probabilmente la cosa più semplice da fare è creare una sottoclasse LinearLayoute sovrascrivereonMeasure

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

L'ho usato per creare viste che sono sempre quadrate prima. Dovrebbe funzionare ancora per un file LinearLayout.

Maggiori informazioni che aiuteranno a farlo: http://developer.android.com/guide/topics/ui/custom-components.html http://developer.android.com/reference/android/view/View.MeasureSpec.html


Sì, ho finito per fare qualcosa di simile. Grazie!
Catherine

1
Se questo ha funzionato per te, potresti contrassegnarlo come la risposta corretta?
David Merriman

1
@Catherine hey Catherine, puoi pubblicare il tuo snippet? Forse sarebbe utile per altri :-)
Marek Sebera

3
Non dimenticare di aggiungere super.onMeasure(widthMeasureSpec, widthMeasureSpec);:!
deKajoo

1
@deKajoo Usando super.onMeasure(widthMeasureSpec, widthMeasureSpec);ti dà un quadrato, ma è sempre larghezza x larghezza. Se le misure fornite sono più larghe che alte, avrai un problema. La mia soluzione utilizza la più piccola delle due misurazioni, in modo da ottenere sempre il quadrato più grande che si adatterà al rettangolo specificato.
David Merriman

43

Ho fatto in questo modo:

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

Con questa implementazione, il layout sarà quadrato, assumendo la dimensione inferiore tra larghezza e altezza. E può anche essere impostato con valori dinamici, come l'utilizzo del peso all'interno di un LinearLayout.


il metodo "onMeasure" deve essere sovrascritto in una classe che estende GridView?
An-droide

È sovrascritto nella classe in cui vuoi che sia quadrato. Per il caso della domanda, è il LinearLayouts all'interno di GridView.
Fernando Camargo

Questo mi ha aiutato a risolvere alcuni strani problemi nell'utilizzo di una vista quadrata in un StaggeredGridLayout. La mia implementazione più ingenua non era abbastanza buona. Grazie!
Peterdk

10

Possiamo farlo in un modo molto semplice: basta chiamare super.onMeasure()due volte.

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

Chiamando super.onMeasure()due volte, questo è meno efficiente in termini di processo di disegno, ma è un modo semplice per risolvere i problemi di layout che possono causare le altre risposte.


2
invece di chiamare super.onMeasure due volte, la seconda volta è sufficiente chiamare setMeasuredDimension (squareLen, squareLen);
user3802077

Chiamare super.onMeasure()due volte si assicura che il layout sia eseguito correttamente date le nuove dimensioni della vista. Senza questo dobbiamo attivare un layout in un modo diverso o gestire layout errati.
Richard Le Mesurier

4

È semplice come:

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

2

Aggiungi la seguente riga in XML:

app:layout_constraintDimensionRatio="1:1"

1
Volevo pubblicare la stessa risposta perché è meglio dell'app: layout_constraintDimensionRatio = "w, 1: 1" poiché mantiene sempre il rapporto se cambi configurazione
Alexey Simchenko

1

Ecco una soluzione che funziona per tutti i parametri di layout che possono essere impostati su view o viewgroup:

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

Saluti


Questa risposta ha alcune caratteristiche interessanti ed è potenzialmente più robusta, ma necessita di lavoro e dovrebbe costruire specifiche di misura che vengono passate a super.onMeasure () piuttosto che a setMeasuredDimension () in quanto consentirebbe una varietà di super classi.
Adam

1

Il mio suggerimento è creare una classe di layout personalizzata che eredita da FrameLayout. Esegui l'override del metodo OnMeasure () e inserisci qualsiasi controllo che desideri sia quadrato all'interno di SquareFrameLayout.

Ecco come viene fatto in Xamarin.Android:

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

Se vuoi che una vista (o qualsiasi altro controllo) sia quadrata (e centrata) aggiungila al tuo layout nel modo seguente:

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

0

Dai un'occhiata a SquareLayout , una libreria Android che fornisce una classe wrapper per diversi layout, rendendoli con dimensioni quadrate senza perdere alcuna funzionalità di base.

Le dimensioni vengono calcolate appena prima del rendering del layout , quindi non è previsto alcun rendering o altro da regolare una volta ottenuta la vista.

Per utilizzare la libreria, aggiungilo al tuo build.gradle:

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

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

Quello che ti serve è SquareLinearLayout .


Non funziona per me sul dispositivo reale :( Rendering bene
sull'anteprima di

0

Per chiunque voglia una soluzione con Kotlin, ecco cosa ho fatto con 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)
    }
}

0

Prova questo codice:

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);
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.