Квадратный макет в GridLayoutManager для RecyclerView

Я пытаюсь сделать сетку с квадратными изображениями. Я подумал, что должно быть возможно манипулировать GridLayoutManager, манипулируя onMeasure, чтобы сделать

super.onMeasure(recycler, state, widthSpec, widthSpec); 

вместо

super.onMeasure(recycler, state, widthSpec, heightSpec);

но, к сожалению, это не сработало.

Любые идеи?


person Paul Woitaschek    schedule 25.10.2014    source источник
comment
вы были правы с переопределением onMeasure, но не в LayoutManager, сделайте это в представлении, созданном в адаптере в onCreateViewHolder   -  person pskink    schedule 26.10.2014


Ответы (9)


Чтобы иметь квадратные элементы в моем RecyclerView, я предоставляю простую оболочку для своего корневого элемента View; Я использую следующий SquareRelativeLayout вместо RelativeLayout.

package net.simplyadvanced.widget;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.RelativeLayout;

/** A RelativeLayout that will always be square -- same width and height,
 * where the height is based off the width. */
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);
    }

    @TargetApi(VERSION_CODES.LOLLIPOP)
    public SquareRelativeLayout(Context context, AttributeSet attrs,         int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // Set a square layout.
        super.onMeasure(widthMeasureSpec, widthMeasureSpec);
    }

}

Затем в моем макете XML для адаптера я только что сослался на пользовательское представление, как показано ниже. Хотя это можно сделать и программно.

<?xml version="1.0" encoding="utf-8"?>
<net.simplyadvanced.widget.SquareRelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/elementRootView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <!-- More widgets here. -->

</net.simplyadvanced.widget.SquareRelativeLayout>

Примечание. В зависимости от ориентации вашей сетки вы можете захотеть, чтобы ширина основывалась на высоте (GridLayoutManager.HORIZONTAL), а не высота основывалась на ширине (GridLayoutManager.VERTICAL).

person Anonsage    schedule 26.10.2014
comment
Большое спасибо за эту помощь - person Nauman Afzaal; 14.05.2015
comment
замечательная работа, краткое примечание, нет необходимости в вашей сноске, так как GridLayoutManager позаботится об этом - person Muhammad Naderi; 12.07.2015
comment
Почему вы используете RelativeLayout? Разве LinearLayout не имеет больше смысла? - person tccpg288; 20.09.2016
comment
Вы должны иметь возможность использовать любой корневой вид, который вы хотите. Ключ в том, чтобы переопределить onMeasure(...) - person Anonsage; 21.09.2016
comment
Работайте с Glide идеально. - person wonsuc; 21.05.2017
comment
Если вам нужен полностью видимый квадрат в ландшафтном режиме, вы можете сделать это, получив минимальное значение (из ширины и высоты) и установив это значение для обоих размеров. Пример: onMeasure(...) { int size = Math.min(widthMeasureSpec, heightMeasureSpec); super.onMeasure (размер, размер); } - person Ryan Amaral; 11.09.2017
comment
Конструктор, ориентированный на API леденцов, у меня не работал. Не удалось найти конструктор. - person LeonardoSibela; 10.01.2020

Ограничение макета решает эту проблему. Используйте 1_

recyclerview_grid_layout.xml

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

    <ImageView
        android:id="@+id/imageview"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintDimensionRatio="H,1:1"
        android:scaleType="centerCrop"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>

</android.support.constraint.ConstraintLayout>

ИЗМЕНИТЬ

Установите ширину ImageView на 0dp. match_parent теперь устарело для ConstraintLayout.

person veritas1    schedule 19.01.2017
comment
Уважаемый, у меня не работает... Не знаю почему... После обновления инструментов сборки больше никогда не будет квадратных элементов в GridLayoutManager. - person Mateus; 30.01.2017
comment
Сработало, лучшее решение для поддержания кода коротким и чистым. Спасибо - person Pelanes; 04.07.2017
comment
@andryr Я согласен с тобой. Мне также пришлось установить ширину 0dp. Вероятно, это связано с некоторыми недавними обновлениями с макетом ограничения. - person Teffi; 09.09.2017
comment
Работает с app:layout_constraintDimensionRatio="W, 1:1" - person Sven; 12.02.2019
comment
Это должен быть принятый ответ, так как нам нужно решение с наибольшим количеством обновлений. Текущий принятый ответ требует написания большего количества стандартного кода. - person blueware; 05.03.2019
comment
лучшие решения. - person hkchakladar; 09.06.2019
comment
Спасибо, решение простое и элегантное! - person Yamashiro Rion; 27.08.2019
comment
этот ответ должен быть одобрен - person Sergei Buvaka; 30.11.2019

Если кто-то хочет масштабировать вид по-другому - вот как вы это делаете:

private static final double WIDTH_RATIO = 3;
private static final double HEIGHT_RATIO = 4;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = (int) (HEIGHT_RATIO / WIDTH_RATIO * widthSize);
    int newHeightSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
    super.onMeasure(widthMeasureSpec, newHeightSpec);
}
person gswierczynski    schedule 28.03.2015
comment
Спасибо за эту дополнительную информацию. Умножение widthMeasureSpec на желаемое соотношение сторон казалось логичным, но явно не работало. - person Mavamaarten; 11.07.2017

Начиная с API 26 (библиотека поддержки 26.0), можно использовать ConstraintLayout, который предоставляет свойство соотношения сторон, чтобы заставить представления быть квадратными: https://developer.android.com/training/constraint-layout/index.htm

android {
    compileSdkVersion 26
    buildToolsVersion '26.0.2'
    ...
}
...
dependencies {
    compile 'com.android.support:appcompat-v7:26.0.2'
    compile 'com.android.support.constraint:constraint-layout:1.1.0-beta1' //use whatever version is current
}

Пример макета, который я использую в GridLayoutManager:

<?xml version="1.0" encoding="utf-8"?>
<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="match_parent"
    android:layout_margin="@dimen/margin_small"
    android:background="@drawable/border_gray"
    android:gravity="center">

    <android.support.constraint.ConstraintLayout
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintDimensionRatio="h,1:1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <!-- place your content here -->


    </android.support.constraint.ConstraintLayout>

</android.support.constraint.ConstraintLayout>

app:layout_constraintDimensionRatio="h,1:1" здесь ключевой атрибут

person vir us    schedule 05.11.2017

Пожалуйста, попробуйте это расширение FrameLayout. Он выполняет двойное измерение для улучшения согласованности. Он также поддерживает пользовательские свойства XML для настройки требуемого соотношения сторон из макетов.

public class StableAspectFrameLayout extends FrameLayout {

    private int aspectWidth = 1;
    private int aspectHeight = 1;

    public StableAspectFrameLayout(Context context) {
        this(context, null, 0);
    }

    public StableAspectFrameLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public StableAspectFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        extractCustomAttrs(context, attrs);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public StableAspectFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        extractCustomAttrs(context, attrs);
    }

    private void extractCustomAttrs(Context context, AttributeSet attrs) {
        if (attrs == null) return;
        TypedArray a = context.getResources().obtainAttributes(attrs, R.styleable.StableAspectFrameLayout);
        try {
            aspectWidth = a.getInteger(R.styleable.StableAspectFrameLayout_aspect_width, 1);
            aspectHeight = a.getInteger(R.styleable.StableAspectFrameLayout_aspect_height, 1);
        } finally {
            a.recycle();
        }
    }

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

        int newSpecWidth = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
        int newH = Math.round(((float) getMeasuredWidth()) * aspectHeight / aspectWidth);
        int newSpecHeigh = MeasureSpec.makeMeasureSpec(newH, MeasureSpec.EXACTLY);
        super.onMeasure(newSpecWidth, newSpecHeigh);
    }
}

И содержимое для attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--  StableAspectFrameLayout  -->
    <declare-styleable name="StableAspectFrameLayout">
        <attr name="aspect_width" format="integer"/>
        <attr name="aspect_height" format="integer"/>
    </declare-styleable>

</resources>
person Stanislav Perchenko    schedule 28.10.2016

Небольшое обновление ConstraintLayout для androidx.

Включите эту строку в свой build.gradle:

implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2'

Я хотел получить RecycleView с GridLayoutManager с квадратными CardViews и использовал такой макет для элементов:

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

    <androidx.cardview.widget.CardView
        android:id="@+id/cardView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        card_view:cardElevation="4dp"
        app:layout_constraintDimensionRatio="H,1:1"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        >

На ConstraintLayout

  • layout_width="match_parent" важен для того, чтобы элемент занимал столько места, сколько предоставляет RecyclerView.
  • layout_height="wrap_content" не позволяет элементу заполнять всю высоту, заданную RecyclerView, а использует ограниченную высоту, предоставленную ConstraintLayout. В моем случае, когда я использовал FrameLayout или LinearLayout, элементы были «высокими».

На дочернем узле, в моем случае CardView

  • ограничение размера до нуля важно: layout_width="0dp" и layout_height="0dp" это означает, что ширина и высота ограничены
  • layout_constraintDimensionRatio="H,1:1" создает желаемый эффект. Установив значение H, вы указываете, что высота должна быть ограничена. Соотношение 1:1 – это соотношение.

Подробные пояснения см. на вне сайта.

person user10475643    schedule 13.09.2019

Еще раз рекомендую относительно недавние «процентные» раскладки. Используя зависимость 'com.android.support:percent:25.2.0', вы можете сделать что-то вроде этого:

<android.support.percent.PercentFrameLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content">
      <ImageView
         android:id="@+id/image"
         app:layout_widthPercent="100%"
         app:layout_aspectRatio="100%"
         android:padding="10dp"
         android:scaleType="centerCrop"
         android:cropToPadding="true"
         tools:background="#efdbed"
         />
   </android.support.percent.PercentFrameLayout>

Это, вероятно, намного быстрее, чем ConstraintLayout, хотя когда-нибудь нам, вероятно, будет все равно.

person androidguy    schedule 16.03.2017
comment
Начиная с версии 26.0.0, библиотека Percent Support устарела. Клиенты этого модуля должны перейти на новый виджет ConstraintLayout, который предоставляется как отдельный артефакт в SDK Manager. - person jpoppe; 09.11.2017

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

Внутри метода onCreateViewHolder я использовал ViewTreeObserver и GlobalLayoutListener, чтобы получить измеренную ширину макета. Макет имеет значение match_parent в атрибуте ширины. Любой мой вид ресайклера имеет макет в центре по горизонтали.

final View view = LayoutInflater.from(mActivity).inflate(R.layout.list_item_deals, parent, false);
    view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            int side = view.getMeasuredWidth();

            ViewGroup.LayoutParams lp = view.getLayoutParams();
            lp.width = side;
            lp.height = side;
            view.setLayoutParams(lp);
        }
    });

исходное изображение

person Community    schedule 22.12.2017

Мне не нравится выбранный ответ, поэтому позвольте мне предоставить свой: вместо того, чтобы обертывать весь макет элемента в SomeDammyLayoutWithFixedAspectRatio, вы можете взломать GridLayoutManager и переписать код внутри MeasureChild. Я заменил эти строки:

if (mOrientation == VERTICAL) {
        wSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode,
                horizontalInsets, lp.width, false);
        hSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), getHeightMode(),
                verticalInsets, lp.height, true);
    } else {
        hSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode,
                verticalInsets, lp.height, false);
        wSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), getWidthMode(),
                horizontalInsets, lp.width, true);
    }

to:

if (mOrientation == VERTICAL) {
        wSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode,
                horizontalInsets, lp.width, false);
        hSpec = wSpec;
    } else {
        hSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode,
                verticalInsets, lp.height, false);
        wSpec = hSpec;
    }

Кажется, это работает нормально.

Не поймите меня неправильно, это тоже довольно беспорядочно, но, по крайней мере, это решение не снижает производительность приложения за счет расширения иерархии представлений.

person Karma Maker    schedule 05.07.2017