Простий спосіб зробити динамічний, але квадратний макет


84

Я використовую a, GridViewщоб відобразити купу переглядів, які є по суті LinearLayouts. Я хочу, LinearLayoutsщоб усі були квадратними, але я також хочу, щоб вони мали динамічний розмір - тобто є дві колонки, і я хочу, LinearLayoutsщоб розтягування здійснювалося залежно від розміру екрана, але залишалося квадратним. Чи є спосіб зробити це за допомогою xmlмакета, чи мені доводиться програмно встановлювати висоту та ширину?

Відповіді:


113

Оптимальним рішенням для квадратних GridViewпредметів є розширення RelativeLayoutабо LinearLayoutперевизначення onMeasureтаким чином:

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

2
Це використовується специфікація міри, а не setMeasuredDimension (), так що це добре. Але це не дуже гнучко, оскільки він завжди бере ширину.
Адам

3
Як сказав @DavidMerriman вище, якщо вигляд ширший, ніж високий, це призведе до того, що вигляд простягнеться нижче зони огляду, відрізаючи частину виду.
vcapra1

5
Це було б більш гнучко, якби він передавав значення Math.min (widthMeasureSpec, heightMeasureSpec) в обидва аргументи super.onMeasure ()
Пол Вінц

Це легко працюватиме з діяльністю, але не працюватиме з віджетом програми.
Аполлон-Робото,

72

Можливо, пізно відповісти, але все ж це буде корисно для нових відвідувачів.

З новим ConstraintLayout представленому в Android Studio 2.3, тепер досить легко створювати адаптивні макети.

Додайте цей атрибут у батьківський ConstraintLayout, щоб зробити будь-яке з його дочірніх видів / макетів динамічно квадратними

app:layout_constraintDimensionRatio="w,1:1"

w - це вказати обмеження по ширині, а співвідношення 1: 1 забезпечує квадратне розміщення.


9
це королівська відповідь
itzhar

2
Найкраща відповідь, якщо ви хочете відобразити існуючий макет у вигляді квадрата!
Квентін Кляйн,

9
Не забудьте встановити "0dp" для ширини та висоти макета в дочірньому макеті. Інакше це не спрацює.
thilina Kj

Це також працює для самого подання, я маю на увазі, якщо ви хочете, щоб дочірній матері contntLayout був квадратним, ви можете визначити атрибут layout_constraintDimensionRatio безпосередньо в дочірньому поданні
Plinio. Сантос,

1
Щиро дякую за рішення; Я зробив, якщо це допомагає завершити суть мого коду, використовуючи ваше рішення: gist.github.com/nadar71/006699e31ef7451813e6c97dacfbc5e2
android_dev71

44

У xml немає нічого, що дозволить вам пов’язати властивості ширини та висоти. Напевно, найпростіше, що можна зробити, це підклас LinearLayoutі перевизначенняonMeasure

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

Я використовував це для створення подань, які раніше завжди були квадратними. Це все одно має працювати для a LinearLayout.

Більше інформації, яка допоможе зробити це: http://developer.android.com/guide/topics/ui/custom-components.html http://developer.android.com/reference/android/view/View.MeasureSpec.html


Так, в підсумку я зробив щось подібне. Дякую!
Кетрін

1
Якби це працювало у вас, чи могли б ви позначити це як правильну відповідь?
Девід Мерріман,

1
@Catherine привіт Кетрін, ти можеш розмістити свій фрагмент? Можливо, це було б корисно для інших :-)
Marek Sebera

3
Не забудьте додати super.onMeasure(widthMeasureSpec, widthMeasureSpec);:!
deKajoo

1
@deKajoo Використання super.onMeasure(widthMeasureSpec, widthMeasureSpec);дає вам квадрат, але це завжди ширина х ширина. Якщо дані вимірювань ширші, ніж високі, у вас виникнуть проблеми. Моє рішення використовує менший з двох вимірювань, так що ви завжди отримуєте найбільший квадрат, який поміститься в заданий прямокутник.
Девід Мерріман,

43

Я зробив так:

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

З цією реалізацією ваш макет буде квадратним, припускаючи менший розмір між шириною та висотою. І його навіть можна встановити з динамічними значеннями, наприклад, використовуючи вагу всередині LinearLayout.


метод "onMeasure" повинен бути замінений у класі, який розширює GridView?
Ан-дроїд

Tt замінено в класі, для якого ви хочете, щоб він був квадратним. Для випадку запитання це LinearLayouts усередині GridView.
Фернандо Камарго,

Це допомогло мені виправити деякі дивні проблеми при використанні квадратного подання у StaggeredGridLayout. Моя більш наївна реалізація була недостатньо хорошою. Дякую!
Peterdk

10

Ми можемо зробити це дуже простим способом - просто зателефонуйте super.onMeasure()двічі.

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()Двічі зателефонувавши , це менш ефективно з точки зору процесу креслення, але це простий спосіб вирішити проблеми з макетом, які можуть спричинити інші відповіді.


2
замість того, щоб викликати super.onMeasure двічі, вдруге потрібно лише викликати setMeasuredDimension (squareLen, squareLen);
user3802077

super.onMeasure()Двічі зателефонувавши, переконайтесь, що макет зроблено правильно, враховуючи новий розмір подання. Без цього нам потрібно запустити макет по-іншому або мати справу з неправильними макетами.
Richard Le Mesurier

4

Це так просто, як:

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

1

Ось рішення, яке працює для всіх параметрів макета, які можна встановити для перегляду або групи перегляду:

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

На ура


Ця відповідь має кілька цікавих характеристик і потенційно є більш надійною, але потребує роботи і повинна побудувати специфікації вимірювань, які передаються в super.onMeasure (), а не setMeasuredDimension (), оскільки це дозволить для різноманітних супер класів.
Адам

1

Я пропоную створити власний клас макета, який успадковується від FrameLayout. Перевизначте метод OnMeasure () і поставте будь-який елемент керування, який ви хочете бути квадратним, всередині цього SquareFrameLayout.

Ось як це робиться в 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);
    }
}

Якщо ви хочете, щоб подання (або будь-який інший елемент керування) було квадратним (і відцентрованим), просто додайте його у свій макет наступним чином:

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

1

Додайте такий рядок у XML:

app:layout_constraintDimensionRatio="1:1"

1
Я хотів опублікувати ту саму відповідь, тому що це краще, ніж app: layout_constraintDimensionRatio = "w, 1: 1", оскільки він завжди зберігає співвідношення, якщо ви змінюєте конфігурацію
Олексій Симченко

0

Перегляньте SquareLayout , бібліотеку Android яка забезпечує клас обгортки для різних макетів, роблячи їх розміром не втрачаючи жодних основних функціональних можливостей.

Ці розміри розраховуються безпосередньо перед Компонування виявляється , отже , немає ніякого повторного рендеринга або що - небудь , як , наприклад , щоб налаштувати , як тільки View виходить.

Щоб користуватися Бібліотекою, додайте це до вашого build.gradle:

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

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

Вам потрібен SquareLinearLayout .


Не працює у мене на реальному пристрої :( Добре рендеринг в попередньому перегляді Android Studio, але не працює на пристрої
Євген Вороной,

0

Для тих, хто хоче вирішити питання з Котліном, ось що я зробив 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

Спробуйте цей код:

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);
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.