Квадратная компоновка GridLayoutManager для RecyclerView
Я пытаюсь сделать сетку-макет с квадратными изображениями. Я думал, что должно быть возможно управлять GridLayoutManager
, манипулируя onMeasure
чтобы сделать
super.onMeasure(recycler, state, widthSpec, widthSpec);
вместо
super.onMeasure(recycler, state, widthSpec, heightSpec);
но, к сожалению, это не сработало.
- Как разместить RecyclerView внутри NestedScrollView?
- RecyclerView элементы с большим пустым пространством после 23.2.0
- Как правильно выделить выделенный элемент в RecyclerView?
- Перетаскивание элементов в RecyclerView с помощью GridLayoutManager
- Recyclerview внутри ScrollView не прокручивается плавно
Есть идеи?
- Recyclerview внутри прокрутки вложенной прокрутки, но не ускоряет прокрутку, как обычно Recyclerview или Nested Scrollview
- RecyclerView GridLayoutManager: как автоматически определить количество проходов?
- Recycle-view раздувает разные строки: - Получение исключения при привязке данных
- Что лучше? notifyDataSetChanged или notifyItemChanged в цикле?
- Как добавить recyclerView внутри другого recyclerView
- Как нажать на элемент внутри RecyclerView в Espresso
- Как фильтровать RecyclerView с помощью SearchView
- Как добавить разделители и пробелы между элементами в RecyclerView?
Чтобы иметь квадратные элементы в моем 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; }
Кажется, он работает нормально.
Не поймите меня неправильно, это тоже довольно грязно, но по крайней мере это решение не повредит производительности приложения, расширяя иерархию представлений