[android] RecyclerView GridLayoutManager : 스팬 수를 자동 감지하는 방법은 무엇입니까?

새로운 GridLayoutManager 사용 : https://developer.android.com/reference/android/support/v7/widget/GridLayoutManager.html

명시적인 스팬 카운트가 필요하므로 이제 문제는 다음과 같습니다. 행당 얼마나 많은 “스팬”이 맞는지 어떻게 알 수 있습니까? 이것은 결국 그리드입니다. 측정 된 너비를 기준으로 RecyclerView가 맞을 수있는만큼의 범위가 있어야합니다.

old를 사용하면 GridView“columnWidth”속성을 설정하기 만하면 맞는 열 수를 자동으로 감지합니다. 이것은 기본적으로 RecyclerView에 대해 복제하려는 것입니다.

  • OnLayoutChangeListener를 RecyclerView
  • 이 콜백에서 단일 ‘그리드 항목’을 팽창시키고 측정합니다.
  • spanCount = recyclerViewWidth / singleItemWidth;

이것은 매우 일반적인 행동처럼 보이므로 내가 보지 못하는 더 간단한 방법이 있습니까?



답변

Personaly 나는 이것을 위해 RecyclerView를 하위 클래스로 만들고 싶지 않습니다. 왜냐하면 저에게는 GridLayoutManager의 책임이 스팬 수를 감지하는 것 같기 때문입니다. 따라서 RecyclerView 및 GridLayoutManager에 대한 일부 Android 소스 코드를 파고 난 후 작업을 수행하는 자체 클래스 확장 GridLayoutManager를 작성했습니다.

public class GridAutofitLayoutManager extends GridLayoutManager
{
    private int columnWidth;
    private boolean isColumnWidthChanged = true;
    private int lastWidth;
    private int lastHeight;

    public GridAutofitLayoutManager(@NonNull final Context context, final int columnWidth) {
        /* Initially set spanCount to 1, will be changed automatically later. */
        super(context, 1);
        setColumnWidth(checkedColumnWidth(context, columnWidth));
    }

    public GridAutofitLayoutManager(
        @NonNull final Context context,
        final int columnWidth,
        final int orientation,
        final boolean reverseLayout) {

        /* Initially set spanCount to 1, will be changed automatically later. */
        super(context, 1, orientation, reverseLayout);
        setColumnWidth(checkedColumnWidth(context, columnWidth));
    }

    private int checkedColumnWidth(@NonNull final Context context, final int columnWidth) {
        if (columnWidth <= 0) {
            /* Set default columnWidth value (48dp here). It is better to move this constant
            to static constant on top, but we need context to convert it to dp, so can't really
            do so. */
            columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
                    context.getResources().getDisplayMetrics());
        }
        return columnWidth;
    }

    public void setColumnWidth(final int newColumnWidth) {
        if (newColumnWidth > 0 && newColumnWidth != columnWidth) {
            columnWidth = newColumnWidth;
            isColumnWidthChanged = true;
        }
    }

    @Override
    public void onLayoutChildren(@NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) {
        final int width = getWidth();
        final int height = getHeight();
        if (columnWidth > 0 && width > 0 && height > 0 && (isColumnWidthChanged || lastWidth != width || lastHeight != height)) {
            final int totalSpace;
            if (getOrientation() == VERTICAL) {
                totalSpace = width - getPaddingRight() - getPaddingLeft();
            } else {
                totalSpace = height - getPaddingTop() - getPaddingBottom();
            }
            final int spanCount = Math.max(1, totalSpace / columnWidth);
            setSpanCount(spanCount);
            isColumnWidthChanged = false;
        }
        lastWidth = width;
        lastHeight = height;
        super.onLayoutChildren(recycler, state);
    }
}

onLayoutChildren에서 스팬 수를 설정하기로 선택한 이유를 실제로 기억하지 못합니다.이 클래스는 얼마 전에 작성했습니다. 그러나 요점은 뷰가 측정 된 후에 그렇게해야한다는 것입니다. 높이와 너비를 얻을 수 있습니다.

편집 1 : 코드의 오류를 수정하여 스팬 수를 잘못 설정했습니다. 솔루션 을보고하고 제안 주신 @Elyees Abouda 사용자 에게 감사드립니다 .

편집 2 : 일부 작은 리팩토링 및 수동 방향 변경 처리로 가장자리 케이스를 수정합니다. 보고하고 해결책을 제안 주신 @tatarize 사용자 에게 감사드립니다 .


답변

뷰 트리 관찰자를 사용하여 한 번 렌더링 된 recylcerview의 너비를 얻은 다음 리소스에서 내 카드 뷰의 고정 치수를 가져온 다음 계산을 수행 한 후 스팬 수를 설정합니다. 표시하는 항목이 고정 너비 인 경우에만 실제로 적용 할 수 있습니다. 이것은 화면 크기 나 방향에 관계없이 자동으로 그리드를 채우는 데 도움이되었습니다.

mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(
            new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    mRecyclerView.getViewTreeObserver().removeOnGLobalLayoutListener(this);
                    int viewWidth = mRecyclerView.getMeasuredWidth();
                    float cardViewWidth = getActivity().getResources().getDimension(R.dimen.cardview_layout_width);
                    int newSpanCount = (int) Math.floor(viewWidth / cardViewWidth);
                    mLayoutManager.setSpanCount(newSpanCount);
                    mLayoutManager.requestLayout();
                }
            });


답변

글쎄, 이것은 내가 사용한 것입니다. 상당히 기본적이지만 나를 위해 일을 수행합니다. 이 코드는 기본적으로 화면 너비를 딥으로 얻은 다음 300으로 나눕니다 (또는 어댑터 레이아웃에 사용하는 너비). 따라서 300-500 폭의 딥 너비를 가진 더 작은 휴대폰은 하나의 열만 표시하고 태블릿은 2-3 개의 열 등을 표시합니다. 내가 볼 수있는 한 간단하고 소란스럽지 않고 단점이 없습니다.

Display display = getActivity().getWindowManager().getDefaultDisplay();
DisplayMetrics outMetrics = new DisplayMetrics();
display.getMetrics(outMetrics);

float density  = getResources().getDisplayMetrics().density;
float dpWidth  = outMetrics.widthPixels / density;
int columns = Math.round(dpWidth/300);
mLayoutManager = new GridLayoutManager(getActivity(),columns);
mRecyclerView.setLayoutManager(mLayoutManager);


답변

RecyclerView를 확장하고 onMeasure 메서드를 덮어 썼습니다.

가능한 한 빨리 항목 너비 (멤버 변수)를 기본값 1로 설정했습니다. 이는 구성 변경시 업데이트됩니다. 이제 세로, 가로, 휴대 전화 / 태블릿 등에 들어갈 수있는만큼 많은 행이 있습니다.

@Override
protected void onMeasure(int widthSpec, int heightSpec) {
    super.onMeasure(widthSpec, heightSpec);
    int width = MeasureSpec.getSize(widthSpec);
    if(width != 0){
        int spans = width / mItemWidth;
        if(spans > 0){
            mLayoutManager.setSpanCount(spans);
        }
    }
}


답변

나는 누군가가 내 경우와 같이 이상한 열 너비를 얻는 경우를 대비하여 이것을 게시하고 있습니다.

저의 평판이 낮기 때문에 @ s-marks 의 답변 에 대해 언급 할 수 없습니다 . 그의 솔루션 솔루션을 적용 했지만 이상한 열 너비를 얻었으므로 다음과 같이 checkedColumnWidth 함수를 수정 했습니다 .

private int checkedColumnWidth(Context context, int columnWidth)
{
    if (columnWidth <= 0)
    {
        /* Set default columnWidth value (48dp here). It is better to move this constant
        to static constant on top, but we need context to convert it to dp, so can't really
        do so. */
        columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
                context.getResources().getDisplayMetrics());
    }

    else
    {
        columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, columnWidth,
                context.getResources().getDisplayMetrics());
    }
    return columnWidth;
}

주어진 열 너비를 DP로 변환하여 문제를 해결했습니다.


답변

s-marks 의 대답에 대한 방향 변경을 수용하기 위해 너비 변경 (열 너비가 아닌 getWidth ()의 너비)에 대한 검사를 추가했습니다.

private boolean mWidthChanged = true;
private int mWidth;


@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)
{
    int width = getWidth();
    int height = getHeight();

    if (width != mWidth) {
        mWidthChanged = true;
        mWidth = width;
    }

    if (mColumnWidthChanged && mColumnWidth > 0 && width > 0 && height > 0
            || mWidthChanged)
    {
        int totalSpace;
        if (getOrientation() == VERTICAL)
        {
            totalSpace = width - getPaddingRight() - getPaddingLeft();
        }
        else
        {
            totalSpace = height - getPaddingTop() - getPaddingBottom();
        }
        int spanCount = Math.max(1, totalSpace / mColumnWidth);
        setSpanCount(spanCount);
        mColumnWidthChanged = false;
        mWidthChanged = false;
    }
    super.onLayoutChildren(recycler, state);
}


답변

upvoted 솔루션은 괜찮지 만 들어오는 값을 픽셀로 처리하므로 테스트를 위해 값을 하드 코딩하고 dp를 가정하는 경우 문제가 발생할 수 있습니다. 가장 쉬운 방법은 열 너비를 차원에 넣고 GridAutofitLayoutManager를 구성 할 때 읽는 것입니다. 그러면 dp가 올바른 픽셀 값으로 자동 변환됩니다.

new GridAutofitLayoutManager(getActivity(), (int)getActivity().getResources().getDimension(R.dimen.card_width))