Квадратная компоновка GridLayoutManager для RecyclerView

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

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

вместо

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

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

Есть идеи?

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

 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-макете для адаптера, я просто ссылался на пользовательский вид, как показано ниже. Хотя, вы можете сделать это также программно.

     

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

Эта проблема устраняет конструкцию ограничений. Используйте app:layout_constraintDimensionRatio="H,1:1"

recyclerview_grid_layout.xml

    

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

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

Пожалуйста, попробуйте это расширение 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

         

Начиная 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:

       

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

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

    

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

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

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

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

ссылочное изображение

Мне не нравится выбранный ответ, поэтому позвольте мне предоставить мой: вместо того, чтобы обернуть весь макет элемента в 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); } 

чтобы:

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

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

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

  • Как нажать на элемент внутри RecyclerView в Espresso
  • Как правильно выделить выделенный элемент в RecyclerView?
  • Как реализовать мульти-выбор в RecyclerView?
  • Recyclerview мучительно медленно загружает кешированные изображения из Picasso
  • Как обновить / обновить определенный элемент в RecyclerView
  • Как фильтровать RecyclerView с помощью SearchView
  • Давайте будем гением компьютера.