Квадратная компоновка GridLayoutManager для RecyclerView
Я пытаюсь сделать сетку-макет с квадратными изображениями. Я думал, что должно быть возможно управлять GridLayoutManager
, манипулируя onMeasure
, чтобы сделать
super.onMeasure(recycler, state, widthSpec, widthSpec);
вместо
super.onMeasure(recycler, state, widthSpec, heightSpec);
но, к сожалению, это не сработало.
Любые идеи?
Ответы
Ответ 1
Чтобы иметь квадратные элементы в моем 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-макете для адаптера я просто ссылался на пользовательский вид, как показано ниже. Хотя вы также можете делать это программно.
<?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
).
Ответ 2
Если кто-то захочет масштабировать вид по-другому - вот как это делается:
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);
}
Ответ 3
Конструкция ограничений решает эту проблему. Используйте app:layout_constraintDimensionRatio="H,1: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="match_parent"
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>
Ответ 4
Пожалуйста, попробуйте это расширение 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>
Ответ 5
Еще раз рекомендую сравнительно недавние "процентные" макеты. Используя зависимость '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, хотя когда-нибудь нам, вероятно, все равно не понравится.
Ответ 6
Начиная 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"
является ключевым атрибутом здесь
Ответ 7
У меня была аналогичная проблема, и мне пришлось раздуть представление, которое было бы квадратным в сетке просмотра 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);
}
});
эталонное изображение
Ответ 8
Мне не нравится выбранный ответ, поэтому позвольте мне предоставить мой: вместо того, чтобы обернуть весь макет элемента в 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;
}
Кажется, что все нормально.
Не поймите меня неправильно, это тоже довольно грязно, но по крайней мере это решение не повредит производительности приложения, расширяя иерархию представлений