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

84

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

Екатерина
источник

Ответы:

113

Изящное решение для квадратных GridViewэлементов - это расширить RelativeLayoutили LinearLayoutпереопределить onMeasureследующим образом:

@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}
jdamcd
источник
2
Здесь используется спецификация меры, а не setMeasuredDimension (), так что это хорошо. Но он не очень гибкий, так как всегда принимает ширину.
Адам
3
Как сказал @DavidMerriman выше, если вид шире, чем высота, это приведет к тому, что вид будет расширяться ниже области просмотра, отрезая часть обзора.
vcapra1
5
Это было бы более гибким, если бы он передавал значение Math.min (widthMeasureSpec, heightMeasureSpec) в оба аргумента super.onMeasure ()
Пол
Это будет легко работать с действиями, но не будет работать с виджетами приложения.
Apollo-Roboto
73

Возможно, ответить поздно, но все же это будет полезно новым посетителям.

С новым ConstraintLayout, представленным в Android Studio 2.3, теперь довольно легко создавать адаптивные макеты.

В родительском ConstraintLayout, чтобы сделать любой из его дочерних видов / макетов динамически квадратным, добавьте этот атрибут

app:layout_constraintDimensionRatio="w,1:1"

w определяет ограничения по ширине, а соотношение 1: 1 обеспечивает квадратную компоновку.

Химаншу Чаухан
источник
9
это царь ответ
itzhar 05
2
Лучший ответ, если вы хотите отобразить существующий макет в виде квадрата!
Quentin Klein
9
Не забудьте установить «0dp» для ширины и высоты макета в дочернем макете. Иначе не получится.
thilina Kj
Это также работает для самого представления, я имею в виду, что если вы хотите, чтобы дочерний элемент contrantLayout был квадратным, вы можете определить атрибут layout_constraintDimensionRatio непосредственно в дочернем представлении
Plinio.Santos
1
Большое спасибо за решение; Я сделал, если это поможет мне понять
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);
}

Я использовал это для создания представлений, которые раньше всегда были квадратными. Он по-прежнему должен работать для файла LinearLayout.

Дополнительная информация, которая поможет в этом: http://developer.android.com/guide/topics/ui/custom-components.html http://developer.android.com/reference/android/view/View.MeasureSpec.html.

Дэвид Мерриман
источник
Да, в итоге я сделал нечто подобное. Благодаря!
Екатерина
1
Если это сработало для вас, не могли бы вы отметить это как правильный ответ?
Дэвид Мерриман
1
@Catherine Привет, Кэтрин, ты можешь опубликовать свой фрагмент? Может быть, это будет полезно для других :-)
Марек Себера
3
Не забудьте добавить super.onMeasure(widthMeasureSpec, widthMeasureSpec);:!
deKajoo 05
1
@deKajoo Использование super.onMeasure(widthMeasureSpec, widthMeasureSpec);действительно дает вам квадрат, но всегда ширина x ширина. Если указанные размеры шире, чем высота, у вас возникнут проблемы. В моем решении используется меньшее из двух измерений, так что вы всегда получаете наибольший квадрат, который умещается в данном прямоугольнике.
Дэвид Мерриман
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?
An-droid
В том классе, в котором вы хотите, чтобы он был квадратным, он имеет приоритетное значение. В случае вопроса, это 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()дважды гарантирует, что макет выполнен правильно с учетом нового размера представления. Без этого нам нужно запускать макет другим способом или иметь дело с неправильными макетами.
Ричард Ле Мезурье
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);
    }
}
Абдул Васае
источник
2

Добавьте следующую строку в XML:

app:layout_constraintDimensionRatio="1:1"
Анураг Бхалкар
источник
1
Я хотел опубликовать тот же ответ, потому что он лучше, чем app: layout_constraintDimensionRatio = "w, 1: 1", поскольку он всегда сохраняет соотношение при изменении конфигурации
Алексей Симченко
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);
    }
}

Если вы хотите, чтобы View (или любой другой элемент управления) был квадратным (и центрированным), просто добавьте его в свой макет следующим образом:

<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
источник
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

Если кому-то нужно решение с Kotlin, вот что я сделал 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);
    }
}
reza_khalafi
источник