Prosty sposób na dynamiczny, ale kwadratowy układ

84

Używam, GridViewaby wyświetlić kilka widoków, które są zasadniczo LinearLayouts. Chcę, LinearLayoutsaby wszystkie były kwadratowe, ale chcę również, aby były dynamicznie zmieniane - to znaczy są dwie kolumny i chcę, LinearLayoutsaby rozciągały się w zależności od rozmiaru ekranu, ale pozostały kwadratowe. Czy istnieje sposób, aby to zrobić za pomocą xmlukładu, czy też muszę programowo ustawiać wysokości i szerokości?

Katarzyna
źródło

Odpowiedzi:

113

Sprytnym rozwiązaniem dla GridViewelementów kwadratowych jest rozszerzenie RelativeLayoutlub LinearLayouti zastąpienie w następujący onMeasuresposób:

@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}
jdamcd
źródło
2
Jest to użycie specyfikacji miary zamiast setMeasuredDimension (), więc to dobrze. Ale nie jest zbyt elastyczny, ponieważ zawsze przyjmuje szerokość.
Adam
3
Jak powiedział @DavidMerriman powyżej, jeśli widok jest szerszy niż wysoki, spowoduje to, że widok rozciągnie się poniżej obszaru widzenia, odcinając część widoku.
vcapra1
5
Byłoby to bardziej elastyczne, gdyby przekazywało wartość Math.min (widthMeasureSpec, heightMeasureSpec) do obu argumentów super.onMeasure ()
Paul Wintz
Będzie to łatwo działać w przypadku działań, ale nie będzie działać z widżetem aplikacji.
Apollo-Roboto
73

Odpowiedź może być późna, ale mimo to będzie pomocna dla nowych gości.

Dzięki nowemu ConstraintLayout wprowadzonemu w Android Studio 2.3 tworzenie responsywnych układów jest teraz dość łatwe.

W nadrzędnym ConstraintLayout, aby dowolny z jego elementów podrzędnych w widoku / układzie był dynamicznie kwadratowy, dodaj ten atrybut

app:layout_constraintDimensionRatio="w,1:1"

w ma na celu określenie ograniczeń dotyczących szerokości, a stosunek 1: 1 zapewnia kwadratowy układ.

Himanshu Chauhan
źródło
9
to jest królewska odpowiedź
itzhar,
2
Najlepsza odpowiedź, jeśli chcesz wyświetlić istniejący układ jako kwadrat!
Quentin Klein
9
Upewnij się, że ustawiłeś „0dp” dla szerokości i wysokości układu w układzie podrzędnym. W przeciwnym razie to nie zadziała.
thilina Kj
Działa również dla samego widoku, mam na myśli, że jeśli chcesz, aby dziecko contrantLayout było kwadratowe, możesz zdefiniować atrybut layout_constraintDimensionRatio bezpośrednio w widoku dziecka
Plinio.Santos
1
Bardzo dziękuję za rozwiązanie; Zrobiłem, jeśli pomoże to w pełnym rozwiązaniu mojego kodu, używając twojego rozwiązania: gist.github.com/nadar71/006699e31ef7451813e6c97dacfbc5e2
android_dev71
44

W kodzie XML nie ma nic, co umożliwiłoby połączenie właściwości width i height. Prawdopodobnie najłatwiejszą rzeczą do zrobienia jest utworzenie podklasy LinearLayouti nadpisanieonMeasure

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

Użyłem tego do tworzenia widoków, które zawsze były kwadratowe. Powinien nadal działać dla LinearLayout.

Więcej informacji, które pomogą to zrobić: http://developer.android.com/guide/topics/ui/custom-components.html http://developer.android.com/reference/android/view/View.MeasureSpec.html

David Merriman
źródło
Tak, skończyło się na tym, że zrobiłem coś podobnego. Dzięki!
Catherine
1
Jeśli to zadziałało, czy możesz oznaczyć to jako poprawną odpowiedź?
David Merriman
1
@Catherine hej Catherine, czy możesz opublikować swój fragment? Może by się przydało innym :-)
Marek Sebera
3
Nie zapomnij dodać super.onMeasure(widthMeasureSpec, widthMeasureSpec);:!
deKajoo
1
@deKajoo Używanie super.onMeasure(widthMeasureSpec, widthMeasureSpec);daje kwadrat, ale zawsze ma on szerokość x szerokość. Jeśli podane wymiary są szersze niż wysokie, będziesz miał problem. Moje rozwiązanie wykorzystuje mniejszy z dwóch wymiarów, dzięki czemu zawsze otrzymujesz największy kwadrat, który zmieści się w podanym prostokącie.
David Merriman
43

Zrobiłem w ten sposób:

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

W tej implementacji twój układ będzie kwadratowy, zakładając mniejszy rozmiar między szerokością a wysokością. Można go nawet ustawić za pomocą wartości dynamicznych, takich jak użycie wagi wewnątrz LinearLayout.

Fernando Camargo
źródło
metoda „onMeasure” ma zostać zastąpiona w klasie rozszerzającej GridView?
An-droid
W klasie, w której chcesz, aby był kwadratowy, jest zastąpiony. W przypadku pytania jest to LinearLayouts wewnątrz GridView.
Fernando Camargo
Ten pomógł mi naprawić dziwne problemy z używaniem widoku kwadratowego w układzie StaggeredGridLayout. Moja bardziej naiwna realizacja nie była wystarczająco dobra. Dzięki!
Peterdk,
10

Możemy to zrobić w bardzo prosty sposób - wystarczy zadzwonić super.onMeasure()dwa razy.

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

super.onMeasure()Dwukrotne wywołanie jest mniej wydajne z punktu widzenia procesu rysowania, ale jest prostym sposobem rozwiązania problemów z układem, które mogą powodować inne odpowiedzi.

passerbywhu
źródło
2
zamiast dwukrotnie wywoływać super.onMeasure, za drugim razem wystarczy wywołać setMeasuredDimension (squareLen, squareLen);
user3802077
super.onMeasure()Dwukrotne wywołanie zapewnia, że ​​układ jest wykonany poprawnie, biorąc pod uwagę nowy rozmiar widoku. Bez tego musimy wywołać układ w inny sposób lub poradzić sobie z nieprawidłowymi układami.
Richard Le Mesurier
4

To jest tak proste, jak:

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
źródło
1

Oto rozwiązanie, które działa dla wszystkich parametrów układu, które można ustawić jako widok lub grupę widoku:

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

Twoje zdrowie

Zaid Daghestani
źródło
Ta odpowiedź ma kilka interesujących cech i jest potencjalnie bardziej niezawodna, ale wymaga pracy i powinna tworzyć specyfikacje miar, które są przekazywane do super.onMeasure (), a nie setMeasuredDimension (), ponieważ pozwoliłoby to na różne superklasy.
Adam
1

Moja sugestia jest taka, aby utworzyć niestandardową klasę układu, która dziedziczy po FrameLayout. Zastąp metodę OnMeasure () i umieść dowolną kontrolkę, którą chcesz, aby była kwadratowa wewnątrz tego SquareFrameLayout.

Oto jak to się robi w 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);
    }
}

Jeśli chcesz, aby Widok (lub jakakolwiek inna kontrolka) był kwadratowy (i wyśrodkowany), po prostu dodaj go do swojego układu w następujący sposób:

<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
źródło
1

Dodaj następujący wiersz w XML:

app:layout_constraintDimensionRatio="1:1"
Anurag Bhalekar
źródło
1
Chciałem opublikować tę samą odpowiedź, ponieważ jest lepsza niż app: layout_constraintDimensionRatio = "w, 1: 1", ponieważ zawsze zachowuje stosunek, jeśli zmienisz konfigurację
Alexey Simchenko
0

Sprawdź SquareLayout , bibliotekę systemu Android, która zapewnia klasę opakowującą dla różnych układów, renderując je w wymiarach kwadratowych bez utraty podstawowych funkcji.

Te wymiary są obliczane tuż przed Układ jest renderowany , stąd nie ma re-rendering czy coś takiego, jak kiedyś, aby dostosować widok jest uzyskane.

Aby skorzystać z biblioteki, dodaj to do pliku build.gradle:

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

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

Wymagany jest SquareLinearLayout .

Kaushik NP
źródło
Nie działa dla mnie na prawdziwym urządzeniu :( Renderowanie dobrze na podglądzie Android Studio, ale nie działa na urządzeniu
Eugene Voronoy
0

Dla każdego, kto chce rozwiązania Z Kotlinem, oto co zrobiłem 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)
    }
}
woda
źródło
0

Wypróbuj ten kod:

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
źródło