ProgramingTip

RecyclerView 용 GridLayoutManager의 정사각형 레이아웃

bestdevel 2020. 12. 2. 21:38
반응형

RecyclerView 용 GridLayoutManager의 정사각형 레이아웃


정사각형 이미지로 그리드 레이아웃을 만들려고합니다. GridLayoutManager조작 onMeasure통해 조작 이 가능해야 생각했습니다 .

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

대신에

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

하지만 안타깝게도 작동하지 않습니다.

어떤 아이디어?


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.


제약 레이아웃은이 문제를 해결합니다. 사용하다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>

다른 사람이보기를 다르게 조정하려는 경우-다음과 같이합니다.

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

API 26 (Support Library 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" 여기서 핵심 속성입니다.


이 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>

다시 한 번은 최근의 '퍼센트'레이아웃을 권장합니다. 언어를 사용하면 '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보다 훨씬 빠르지 만 언젠가는 더 이상 신경 쓰지 않을 것입니다.


선택한 답변이 마음에 들지 않으므로 내 제공하겠습니다. 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;
    }

잘 작동하는 것 같습니다.

오해하지 마세요. 이것도 꽤 지저분하지만 적어도이 솔루션은 뷰 계층을 확장하여 앱 성능을 손상시키지 않습니다.


비슷한 문제가 있었고 리사이클 러 뷰의 그리드에서 정사각형이 될 뷰를 부풀려 야했습니다. 아래는 내 방식입니다.

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

참조 이미지


androidx 용 ConstraintLayout에 대한 작은 업데이트입니다.

build.gradle에 다음 줄을 포함합니다.

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

정사각형 CardViews가있는 GridLayoutManager로 RecycleView를 얻고 싶었고 항목에 대해 이러한 레이아웃을 사용했습니다.

<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

  • 크기를 0으로 제한하는 것이 중요합니다. layout_width = "0dp"및 layout_height = "0dp"는 너비와 높이가 제한됨을 의미합니다.
  • layout_constraintDimensionRatio = "H, 1 : 1"은 H를 설정하여 높이가 1 : 1이 비율로 제한되도록 정의하여 원하는 효과를 만듭니다.

오프 사이트 에 대한 자세한 설명을 참조하십시오 .

참고 URL : https://stackoverflow.com/questions/26566954/square-layout-on-gridlayoutmanager-for-recyclerview

반응형