Фиксированное соотношение сторон

Как мне реализовать фиксированное соотношение сторон View? Я хотел бы иметь элементы с соотношением сторон 1:1 в формате GridView. Я думаю, что лучше создать подкласс детей, чем GridView?

РЕДАКТИРОВАТЬ: я предполагаю, что это нужно сделать программно, это не проблема. Кроме того, я не хочу ограничивать размер, только соотношение сторон.


person Jani    schedule 14.08.2011    source источник
comment
Джеспер Боргструп создал вспомогательный класс для использования с onMeasure: buzzingandroid.com/2012/11/   -  person c0nstruct0r    schedule 31.07.2013
comment
Google предоставил библиотеку поддержки Percent: developer.android. com/tools/support-library/   -  person c0nstruct0r    schedule 13.01.2016
comment
@ c0nstruct0r Классы PercentFrameLayout и PercentRelativeLayout из библиотеки поддержки устарели в версии 26.0.0. На данный момент вам следует рассмотреть возможность использования ConstraintLayout для изменения размера ваших представлений с предопределенным соотношением сторон. Смотрите мой ответ ниже.   -  person Eugene Brusov    schedule 25.09.2017
comment
Это плагин JQuery, который помогает с соотношением сторон: github.com/eissasoubhi/aspectratio.js   -  person Eissa    schedule 07.05.2019


Ответы (9)


Я реализовал FixedAspectRatioFrameLayout, поэтому я могу повторно использовать его и иметь любое размещенное представление с фиксированным соотношением сторон:

public class FixedAspectRatioFrameLayout extends FrameLayout
{
    private int mAspectRatioWidth;
    private int mAspectRatioHeight;

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

    public FixedAspectRatioFrameLayout(Context context, AttributeSet attrs)
    {
        super(context, attrs);

        init(context, attrs);
    }

    public FixedAspectRatioFrameLayout(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);

        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs)
    {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FixedAspectRatioFrameLayout);

        mAspectRatioWidth = a.getInt(R.styleable.FixedAspectRatioFrameLayout_aspectRatioWidth, 4);
        mAspectRatioHeight = a.getInt(R.styleable.FixedAspectRatioFrameLayout_aspectRatioHeight, 3);

        a.recycle();
    }
    // **overrides**

    @Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
    {
        int originalWidth = MeasureSpec.getSize(widthMeasureSpec);

        int originalHeight = MeasureSpec.getSize(heightMeasureSpec);

        int calculatedHeight = originalWidth * mAspectRatioHeight / mAspectRatioWidth;

        int finalWidth, finalHeight;

        if (calculatedHeight > originalHeight)
        {
            finalWidth = originalHeight * mAspectRatioWidth / mAspectRatioHeight; 
            finalHeight = originalHeight;
        }
        else
        {
            finalWidth = originalWidth;
            finalHeight = calculatedHeight;
        }

        super.onMeasure(
                MeasureSpec.makeMeasureSpec(finalWidth, MeasureSpec.EXACTLY), 
                MeasureSpec.makeMeasureSpec(finalHeight, MeasureSpec.EXACTLY));
    }
}
person TalL    schedule 27.05.2012
comment
Метод Init должен начинаться с нижнего регистра. Конвенция Java. - person Zsolt Safrany; 03.01.2013
comment
Я только что реализовал что-то в этом роде, основываясь на PreviewFrameLayout из приложения Camera AOSP. Однако я заметил, что гравитация больше не соблюдается — в случаях, когда высота ограничена, все лишние пробелы кажутся внизу, даже с Gravity.CENTER или Gravity.BOTTOM, например. Вы видели это? Если да, то случайно не придумали решение? Спасибо! - person CommonsWare; 30.03.2013
comment
@CommonsWare поместил FixedAspectRatioFrameLayout внутрь RelativeLayout и добавил android:layout_centerInParent="true" к FixedAspectRatioFrameLayout - person Oliv; 13.05.2013
comment
@Oliv: Да, я сделал что-то в этом роде: github.com/commonsguy/cwac-layouts< /а> - person CommonsWare; 13.05.2013
comment
Вставьте это в файл res/values/attrs.xml: - person tar; 04.06.2014
comment
@tar - вам нужно обернуть ‹declare-styleable ...› в ‹resources›‹/resources› - person Gallal; 20.01.2016
comment
Реализуйте этот класс в новом файле, а затем используйте его в макете вместо обычного FrameLayout. Google внедряет пользовательские представления в Android, если вам нужны основы. Но я бы предложил решение ниже с использованием собственной библиотеки поддержки процентов Google (которой не существовало, когда я писал это решение). - person TalL; 14.08.2016

Для новых пользователей вот лучшее решение без кода:

Новая библиотека поддержки под названием Библиотека процентной поддержки доступен в Android SDK v22 (я думаю, что MinAPI 7 не уверен):

источник: android-developers.blogspot.in

Библиотека Percent Support Library предоставляет процентные размеры и поля, а также новую возможность в этом выпуске задавать собственное соотношение сторон с помощью app:aspectRatio. Установив только одну ширину или высоту и используя aspectRatio, PercentFrameLayout или PercentRelativeLayout автоматически отрегулирует другое измерение, чтобы в макете использовалось заданное соотношение сторон.

Чтобы включить, добавьте это в свой build.gradle :

compile 'com.android.support:percent:23.1.1'

Теперь оберните представление (которое должно быть квадратным) с помощью PercentRelativeLayout< /a> / PercentFrameLayout:

<android.support.percent.PercentRelativeLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content">
     <ImageView
         app:layout_aspectRatio="100%"
         app:layout_widthPercent="100%"/>
 </android.support.percent.PercentRelativeLayout>

Вы можете увидеть пример здесь.

person ShahiM    schedule 15.02.2016
comment
Хотя PercentRelativeLayout хорош для некоторых случаев использования, он не работает с представлениями прокрутки до M и намного сложнее, поскольку поддерживает дочерние размеры. Если вам просто нужно представление с фиксированным соотношением сторон, тогда принятый ответ будет лучшим. - person GuillermoMP; 01.10.2016
comment
Библиотека процентов устарела с 26-rc1 в пользу ConstraintLayout... Которые не очень связаны друг с другом. - person JediBurrell; 19.07.2017

Чтобы не использовать стороннее решение и учитывая тот факт, что и PercentFrameLayout, и PercentRelativeLayout устарели в 26.0.0, я предлагаю вам рассмотреть возможность использования ConstraintLayout в качестве корневого макета для ваших элементов сетки.

Ваш item_grid.xml может выглядеть так:

<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/imageview_item"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:scaleType="centerCrop"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintDimensionRatio="H,1:1" />

</android.support.constraint.ConstraintLayout>

В результате вы получите что-то вроде этого:

Элементы сетки с фиксированным соотношением размеров

person Eugene Brusov    schedule 25.09.2017
comment
Спасибо, это работает как шарм, однако Android Studio жалуется, что H, 1: 1 является недопустимым числом с плавающей запятой. Если вы установите app:layout_constraintDimensionRatio=1.0, он будет работать так же, но без предупреждения. - person GenError; 05.12.2019

Недавно я создал вспомогательный класс для этой самой проблемы и написал пост об этом в блоге.

Мясо кода выглядит следующим образом:

/**
 * Measure with a specific aspect ratio<br />
 * <br />
 * @param widthMeasureSpec The width <tt>MeasureSpec</tt> passed in your <tt>View.onMeasure()</tt> method
 * @param heightMeasureSpec The height <tt>MeasureSpec</tt> passed in your <tt>View.onMeasure()</tt> method
 * @param aspectRatio The aspect ratio to calculate measurements in respect to 
 */
public void measure(int widthMeasureSpec, int heightMeasureSpec, double aspectRatio) {
    int widthMode = MeasureSpec.getMode( widthMeasureSpec );
    int widthSize = widthMode == MeasureSpec.UNSPECIFIED ? Integer.MAX_VALUE : MeasureSpec.getSize( widthMeasureSpec );
    int heightMode = MeasureSpec.getMode( heightMeasureSpec );
    int heightSize = heightMode == MeasureSpec.UNSPECIFIED ? Integer.MAX_VALUE : MeasureSpec.getSize( heightMeasureSpec );

    if ( heightMode == MeasureSpec.EXACTLY && widthMode == MeasureSpec.EXACTLY ) {
        /* 
         * Possibility 1: Both width and height fixed
         */
        measuredWidth = widthSize;
        measuredHeight = heightSize;

    } else if ( heightMode == MeasureSpec.EXACTLY ) {
        /*
         * Possibility 2: Width dynamic, height fixed
         */
        measuredWidth = (int) Math.min( widthSize, heightSize * aspectRatio );
        measuredHeight = (int) (measuredWidth / aspectRatio);

    } else if ( widthMode == MeasureSpec.EXACTLY ) {
        /*
         * Possibility 3: Width fixed, height dynamic
         */
        measuredHeight = (int) Math.min( heightSize, widthSize / aspectRatio );
        measuredWidth = (int) (measuredHeight * aspectRatio);

    } else {
        /* 
         * Possibility 4: Both width and height dynamic
         */
        if ( widthSize > heightSize * aspectRatio ) {
            measuredHeight = heightSize;
            measuredWidth = (int)( measuredHeight * aspectRatio );
        } else {
            measuredWidth = widthSize;
            measuredHeight = (int) (measuredWidth / aspectRatio);
        }

    }
}
person JesperB    schedule 12.12.2012
comment
FixedAspectRatioFrameLayout на основе этого кода: github.com /triposo/barone/blob/master/src/com/triposo/barone/ - person aleb; 01.08.2013
comment
Я не уверен, что вам нужно делать все эти вещи, просто вызовите базовый класс onMeasure, чтобы выполнить тяжелую работу и исправить измеренную высоту из измеренной ширины * соотношения сторон перед возвратом. - person Prakash Nadar; 18.05.2014
comment
Зависит от того, что вы хотите сделать. Я думаю, вы описываете возможность 3 (фиксированная ширина, динамическая высота) в приведенном выше коде. - person JesperB; 18.05.2014

Я использовал и мне понравилась реализация Jake Wharton ImageView (аналогично для других представлений), другим она тоже может понравиться:

AspectRatioImageView.java — ImageView, учитывающий соотношение сторон, применяемое к определенному измерению

Хорошо, что это уже можно стилизовать в xml.

person Czechnology    schedule 24.08.2014

Я создал библиотеку макетов, используя ответ TalL. Не стесняйтесь использовать его.

RatioLayouts

Установка

Добавьте это в начало файла

repositories {
    maven {
        url  "http://dl.bintray.com/riteshakya037/maven" 
    }
}


dependencies {
    compile 'com.ritesh:ratiolayout:1.0.0'
}

использование

Определите пространство имен «приложение» в корневом представлении в вашем макете

xmlns:app="http://schemas.android.com/apk/res-auto"

Включите эту библиотеку в свой макет

<com.ritesh.ratiolayout.RatioRelativeLayout
        android:id="@+id/activity_main_ratio_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:fixed_attribute="WIDTH" // Fix one side of the layout 
        app:horizontal_ratio="2" // ratio of 2:3
        app:vertical_ratio="3">

Обновлять

С введением ConstraintLayout вам не нужно писать ни одного строку кода или использовать сторонние или полагаться на PercentFrameLayout, которые устарели в 26.0.0.

Вот пример того, как сохранить соотношение сторон 1:1 для макета с помощью ConstraintLayout:

<android.support.constraint.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginEnd="0dp"
        android:layout_marginStart="0dp"
        android:layout_marginTop="0dp"
        android:background="@android:color/black"
        app:layout_constraintDimensionRatio="H,1:1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </LinearLayout>

</android.support.constraint.ConstraintLayout>
person Ritesh Shakya    schedule 30.05.2017

Просто переопределите onSizeChanged и рассчитайте отношение там.

Формула соотношения сторон:

newHeight =  original_height / original_width x new_width

это даст вам что-то вроде этого:

 @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);

    //3:5 ratio
    float RATIO = 5/3;
    setLayoutParams(new LayoutParams((int)RATIO * w, w));

}

надеюсь это поможет!

person Jmorvan    schedule 13.02.2013

ExoPlayer от Google поставляется с AspectRatioFrameLayout. который вы используете следующим образом:

<com.google.android.exoplayer2.ui.AspectRatioFrameLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:resize_mode="fixed_width">
    <!-- https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.html#RESIZE_MODE_FIXED_WIDTH -->

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</com.google.android.exoplayer2.ui.AspectRatioFrameLayout>

Затем вы должны установить соотношение сторон программно:

aspectRatioFrameLayout.setAspectRatio(16f/9f)

Обратите внимание, что вы также можете установить режим изменения размера программно с помощью setResizeMode.


Поскольку вы, очевидно, не собираетесь использовать всю библиотеку ExoPlayer для одного класса, вы можете просто скопировать и вставить файл с GitHub в свой проект (это открытый исходный код):

https://github.com/google/ExoPlayer/blob/release-v2/library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java

Не забудьте также захватить атрибут resize_mode:

https://github.com/google/ExoPlayer/blob/release-v2/library/ui/src/main/res/values/attrs.xml#L18-L25

<attr name="resize_mode" format="enum">
  <enum name="fit" value="0"/>
  <enum name="fixed_width" value="1"/>
  <enum name="fixed_height" value="2"/>
  <enum name="fill" value="3"/>
  <enum name="zoom" value="4"/>
</attr>
person Albert Vila Calvo    schedule 27.05.2019

Вы можете найти сторонние библиотеки. Вместо того, чтобы использовать их, используйте макет ограничения. Код ниже устанавливает соотношение сторон ImageView как 16:9 независимо от размера и ориентации экрана.

<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:scaleType="fitXY"
        android:src="@drawable/mat3"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0"
        app:layout_constraintDimensionRatio="H,16:9"/>

</androidx.constraintlayout.widget.ConstraintLayout>

app:layout_constraintDimensionRatio="H,16:9". Здесь высота задается по отношению к ширине макета.

Портретная активность

Ландшафтная деятельность

Для вашего вопроса установите android:layout_width="match_parent" и используйте app:layout_constraintDimensionRatio="H,1:1" в своем представлении.

person Tejas Mane    schedule 25.08.2020