Android Recyclerview GridLayoutManager 열 간격


251

GridLayoutManager를 사용하여 RecyclerView로 열 간격을 어떻게 설정합니까? 레이아웃 내에서 여백 / 패딩을 설정해도 효과가 없습니다.


서브 클래 싱 GridLayoutManager, 재정의 generateDefaultLayoutParams()및 친족 을 시도 했습니까 ?
CommonsWare

나는 간격을 같은 격자보기로 설정하지 않는 방법이있을 것이라고 생각하지 않았습니다. 나는 그것을 시도 할 것이다
hitch.united


답변:


348

RecyclerViews는 ItemDecoration 의 개념을 지원합니다 . 각 요소 주위에 특수 오프셋 및 그리기. 에서 보는 바와 같이 이 응답 , 당신은 사용할 수 있습니다

public class SpacesItemDecoration extends RecyclerView.ItemDecoration {
  private int space;

  public SpacesItemDecoration(int space) {
    this.space = space;
  }

  @Override
  public void getItemOffsets(Rect outRect, View view, 
      RecyclerView parent, RecyclerView.State state) {
    outRect.left = space;
    outRect.right = space;
    outRect.bottom = space;

    // Add top margin only for the first item to avoid double space between items
    if (parent.getChildLayoutPosition(view) == 0) {
        outRect.top = space;
    } else {
        outRect.top = 0;
    }
  }
}

그런 다음 통해 추가하십시오

mRecyclerView = (RecyclerView) rootView.findViewById(R.id.my_recycler_view);
int spacingInPixels = getResources().getDimensionPixelSize(R.dimen.spacing);
mRecyclerView.addItemDecoration(new SpacesItemDecoration(spacingInPixels));

3
'첫 번째 위치에 대한 경우'를 엉망으로 만들지 않으려면 'outRect.top = space'를 사용하고 'outRect.bottom'을 제거하십시오. ;-]
Amio.io

2
@zatziky-yep, 이미 상단 및 하단 패딩을 RecyclerView(및 사용 clipToPadding="false")의 일부로 사용하는 경우 사물을 약간 재구성 할 수 있습니다. 그러나 그렇지 않으면 if 체크를 마지막으로 이동합니다 (마지막 항목의 맨 아래 패딩을 원할 것입니다).
ianhanniballake

18
@ianhanniballake는 단일 범위 레이아웃 관리자를 사용할 때 작동하지만 다중 범위 레이아웃 관리자에서는 실패합니다.
Avinash R

4
GridLayoutManager를 사용하여 이렇게하면 두 번째, 세 번째 ... n 번째 열의 모든 첫 번째 항목이 맨 위에 고정됩니다 (공간이 없기 때문에). 그래서 나는 .top = space / 2와 .bottom = space / 2를하는 것이 낫다고 생각합니다.
Yaroslav

1
이 답변은 원래 질문에 대한 답변이 아닙니다. 질문의 강조점은입니다 GridLayoutManager . 대답은 다중 열 / 행 레이아웃에서 작동하지 않습니다
HCH

428

다음 코드는 잘 작동하며 각 열의 너비는 동일합니다.

public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {

    private int spanCount;
    private int spacing;
    private boolean includeEdge;

    public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) {
        this.spanCount = spanCount;
        this.spacing = spacing;
        this.includeEdge = includeEdge;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        int position = parent.getChildAdapterPosition(view); // item position
        int column = position % spanCount; // item column

        if (includeEdge) {
            outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
            outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)

            if (position < spanCount) { // top edge
                outRect.top = spacing;
            }
            outRect.bottom = spacing; // item bottom
        } else {
            outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)
            outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f /    spanCount) * spacing)
            if (position >= spanCount) {
                outRect.top = spacing; // item top
            }
        }
    }
}

용법

1. 가장자리 없음

여기에 이미지 설명을 입력하십시오

int spanCount = 3; // 3 columns
int spacing = 50; // 50px
boolean includeEdge = false;
recyclerView.addItemDecoration(new GridSpacingItemDecoration(spanCount, spacing, includeEdge));

2. 가장자리

여기에 이미지 설명을 입력하십시오

int spanCount = 3; // 3 columns
int spacing = 50; // 50px
boolean includeEdge = true;
recyclerView.addItemDecoration(new GridSpacingItemDecoration(spanCount, spacing, includeEdge));

12
헤더와 같이 다양한 범위를 가진 항목이 없으면 작동합니다.
Matthew

8
큰 답변; 팁 : 간격은 px입니다 (따라서 Math.round (someDpValue * getResources (). getDisplayMetrics (). density)를 사용하여 dp를 px로 변환 할 수 있음)
Kevin Lee

1
잘 작동하지만 문제가 있습니다. spanCount가 2 (기본값) 인 GridLayoutManager를 사용하고 있지만 spanCount를 변경할 수 있으므로 spanCount가 기본 위치에서 변경 될 때 spanCount가 3보다 큰 경우와 같이 일부 위치에서 훨씬 더 눈에 띄는 패딩이 있습니다 2,3 8,9 12,13 등의 여백 / 여백
Haris Qureshi

2
잘 작동합니다! 그러나 StaggeredGridLayoutManager에 문제가 있습니다. imgur.com/XVutH5u 가로 여백은 때때로 다릅니다.
Ufkoku

2
레이아웃이 rtl (2 열 이상) 인 경우 작동하지 않습니다. 현재 rtl 모드에있을 때 열 사이의 공간이 올바르지 않습니다. rtl에있을 때 outRect.left를 outRect.right로 바꾸어야합니다.
Masoud Mohammadi

83

다음은 항목 주위에 동일한 간격과 동일한 항목 크기를 원하는 경우 단계별 간단한 솔루션입니다.

ItemOffset 장식

public class ItemOffsetDecoration extends RecyclerView.ItemDecoration {

    private int mItemOffset;

    public ItemOffsetDecoration(int itemOffset) {
        mItemOffset = itemOffset;
    }

    public ItemOffsetDecoration(@NonNull Context context, @DimenRes int itemOffsetId) {
        this(context.getResources().getDimensionPixelSize(itemOffsetId));
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
            RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        outRect.set(mItemOffset, mItemOffset, mItemOffset, mItemOffset);
    }
}

이행

소스 코드에서 추가 ItemOffsetDecoration당신에게 RecyclerView. 당신이 항목 사이의 공간으로 추가 할 실제 값의 절반 크기 여야 항목 오프셋 값.

mRecyclerView.setLayoutManager(new GridLayoutManager(context, NUM_COLUMNS);
ItemOffsetDecoration itemDecoration = new ItemOffsetDecoration(context, R.dimen.item_offset);
mRecyclerView.addItemDecoration(itemDecoration);

또한 항목 오프셋 값을의 패딩으로 설정하고을 RecyclerView지정하십시오 android:clipToPadding=false.

<android.support.v7.widget.RecyclerView
    android:id="@+id/recyclerview_grid"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipToPadding="false"
    android:padding="@dimen/item_offset"/>

완벽하고 간단합니다.
B.shruti

28

이 시도. 모든 곳에서 동일한 간격을 처리합니다. List, Grid 및 StaggeredGrid와 함께 작동합니다.

편집

업데이트 된 코드는 범위, 방향 등이있는 대부분의 코너 경우를 처리해야합니다. GridLayoutManager와 함께 setSpanSizeLookup ()을 사용하는 경우 성능상의 이유로 setSpanIndexCacheEnabled ()를 설정하는 것이 좋습니다.

StaggeredGrid의 경우 하위 인덱스가 엉뚱하고 추적하기 어려운 버그가있는 것 같습니다. 아래 코드는 StaggeredGridLayoutManager에서 잘 작동하지 않을 수 있습니다.

public class ListSpacingDecoration extends RecyclerView.ItemDecoration {

  private static final int VERTICAL = OrientationHelper.VERTICAL;

  private int orientation = -1;
  private int spanCount = -1;
  private int spacing;
  private int halfSpacing;


  public ListSpacingDecoration(Context context, @DimenRes int spacingDimen) {

    spacing = context.getResources().getDimensionPixelSize(spacingDimen);
    halfSpacing = spacing / 2;
  }

  public ListSpacingDecoration(int spacingPx) {

    spacing = spacingPx;
    halfSpacing = spacing / 2;
  }

  @Override
  public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {

    super.getItemOffsets(outRect, view, parent, state);

    if (orientation == -1) {
        orientation = getOrientation(parent);
    }

    if (spanCount == -1) {
        spanCount = getTotalSpan(parent);
    }

    int childCount = parent.getLayoutManager().getItemCount();
    int childIndex = parent.getChildAdapterPosition(view);

    int itemSpanSize = getItemSpanSize(parent, childIndex);
    int spanIndex = getItemSpanIndex(parent, childIndex);

    /* INVALID SPAN */
    if (spanCount < 1) return;

    setSpacings(outRect, parent, childCount, childIndex, itemSpanSize, spanIndex);
  }

  protected void setSpacings(Rect outRect, RecyclerView parent, int childCount, int childIndex, int itemSpanSize, int spanIndex) {

    outRect.top = halfSpacing;
    outRect.bottom = halfSpacing;
    outRect.left = halfSpacing;
    outRect.right = halfSpacing;

    if (isTopEdge(parent, childCount, childIndex, itemSpanSize, spanIndex)) {
        outRect.top = spacing;
    }

    if (isLeftEdge(parent, childCount, childIndex, itemSpanSize, spanIndex)) {
        outRect.left = spacing;
    }

    if (isRightEdge(parent, childCount, childIndex, itemSpanSize, spanIndex)) {
        outRect.right = spacing;
    }

    if (isBottomEdge(parent, childCount, childIndex, itemSpanSize, spanIndex)) {
        outRect.bottom = spacing;
    }
  }

  @SuppressWarnings("all")
  protected int getTotalSpan(RecyclerView parent) {

    RecyclerView.LayoutManager mgr = parent.getLayoutManager();
    if (mgr instanceof GridLayoutManager) {
        return ((GridLayoutManager) mgr).getSpanCount();
    } else if (mgr instanceof StaggeredGridLayoutManager) {
        return ((StaggeredGridLayoutManager) mgr).getSpanCount();
    } else if (mgr instanceof LinearLayoutManager) {
        return 1;
    }

    return -1;
  }

  @SuppressWarnings("all")
  protected int getItemSpanSize(RecyclerView parent, int childIndex) {

    RecyclerView.LayoutManager mgr = parent.getLayoutManager();
    if (mgr instanceof GridLayoutManager) {
        return ((GridLayoutManager) mgr).getSpanSizeLookup().getSpanSize(childIndex);
    } else if (mgr instanceof StaggeredGridLayoutManager) {
        return 1;
    } else if (mgr instanceof LinearLayoutManager) {
        return 1;
    }

    return -1;
  }

  @SuppressWarnings("all")
  protected int getItemSpanIndex(RecyclerView parent, int childIndex) {

    RecyclerView.LayoutManager mgr = parent.getLayoutManager();
    if (mgr instanceof GridLayoutManager) {
        return ((GridLayoutManager) mgr).getSpanSizeLookup().getSpanIndex(childIndex, spanCount);
    } else if (mgr instanceof StaggeredGridLayoutManager) {
        return childIndex % spanCount;
    } else if (mgr instanceof LinearLayoutManager) {
        return 0;
    }

    return -1;
  }

  @SuppressWarnings("all")
  protected int getOrientation(RecyclerView parent) {

    RecyclerView.LayoutManager mgr = parent.getLayoutManager();
    if (mgr instanceof LinearLayoutManager) {
        return ((LinearLayoutManager) mgr).getOrientation();
    } else if (mgr instanceof GridLayoutManager) {
        return ((GridLayoutManager) mgr).getOrientation();
    } else if (mgr instanceof StaggeredGridLayoutManager) {
        return ((StaggeredGridLayoutManager) mgr).getOrientation();
    }

    return VERTICAL;
  }

  protected boolean isLeftEdge(RecyclerView parent, int childCount, int childIndex, int itemSpanSize, int spanIndex) {

    if (orientation == VERTICAL) {

        return spanIndex == 0;

    } else {

        return (childIndex == 0) || isFirstItemEdgeValid((childIndex < spanCount), parent, childIndex);
    }
  }

  protected boolean isRightEdge(RecyclerView parent, int childCount, int childIndex, int itemSpanSize, int spanIndex) {

    if (orientation == VERTICAL) {

        return (spanIndex + itemSpanSize) == spanCount;

    } else {

        return isLastItemEdgeValid((childIndex >= childCount - spanCount), parent, childCount, childIndex, spanIndex);
    }
  }

  protected boolean isTopEdge(RecyclerView parent, int childCount, int childIndex, int itemSpanSize, int spanIndex) {

    if (orientation == VERTICAL) {

        return (childIndex == 0) || isFirstItemEdgeValid((childIndex < spanCount), parent, childIndex);

    } else {

        return spanIndex == 0;
    }
  }

  protected boolean isBottomEdge(RecyclerView parent, int childCount, int childIndex, int itemSpanSize, int spanIndex) {

    if (orientation == VERTICAL) {

        return isLastItemEdgeValid((childIndex >= childCount - spanCount), parent, childCount, childIndex, spanIndex);

    } else {

        return (spanIndex + itemSpanSize) == spanCount;
    }
  }

  protected boolean isFirstItemEdgeValid(boolean isOneOfFirstItems, RecyclerView parent, int childIndex) {

    int totalSpanArea = 0;
    if (isOneOfFirstItems) {
        for (int i = childIndex; i >= 0; i--) {
            totalSpanArea = totalSpanArea + getItemSpanSize(parent, i);
        }
    }

    return isOneOfFirstItems && totalSpanArea <= spanCount;
  }

  protected boolean isLastItemEdgeValid(boolean isOneOfLastItems, RecyclerView parent, int childCount, int childIndex, int spanIndex) {

    int totalSpanRemaining = 0;
    if (isOneOfLastItems) {
        for (int i = childIndex; i < childCount; i++) {
            totalSpanRemaining = totalSpanRemaining + getItemSpanSize(parent, i);
        }
    }

    return isOneOfLastItems && (totalSpanRemaining <= spanCount - spanIndex);
  }
}

도움이 되길 바랍니다.


1
항목의 첫 번째 줄 바로 뒤에 이중 범위가 있습니다. parent.getChildCount ()는 첫 번째 항목에 대해 1, 두 번째에 대해 2 등을 반환하기 때문에 발생합니다. 따라서 상단 가장자리 항목에 공간을 추가하는 것이 좋습니다. outRect.top = childIndex <spanCount? spacingInPixels : 0; 그리고 각 항목의 하단 공간을 추가하십시오. outRect.bottom = spacingInPixels;
IvanP

RecyclerView를 스크롤 할 때 간격이 변경되었습니다.
Yugesh

3
parent.getChildCount ()를 "parent.getLayoutManager (). getItemCount ()"로 변경해야한다고 생각합니다. 또한 isBottomEdge 함수를 "return childIndex> = childCount-spanCount + spanIndex"로 변경해야합니다. 이것들을 바꾼 후에는 같은 간격을 얻었습니다. 그러나 위치에 따라 오프셋 값이 다르기 때문에 범위 개수가 2보다 큰 경우이 솔루션은 동일한 항목 크기를 제공하지 않습니다.
yqritc

1
@yqritc 감사합니다 parent.getChildCount (). parent.getLayoutManager (). getItemCount ()를 사용하도록 답변을 업데이트했습니다.
Pirdad Sakhizada

2
가변 스팬, 축하 및 감사에도 불구하고 즉시 사용할 수 있습니다.
Pierre-Luc Paour

21

다음 코드는 StaggeredGridLayoutManager, GridLayoutManager 및 LinearLayoutManager를 처리합니다.

public class SpacesItemDecoration extends RecyclerView.ItemDecoration {

    private int halfSpace;

    public SpacesItemDecoration(int space) {
        this.halfSpace = space / 2;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {

        if (parent.getPaddingLeft() != halfSpace) {
            parent.setPadding(halfSpace, halfSpace, halfSpace, halfSpace);
            parent.setClipToPadding(false);
        }

        outRect.top = halfSpace;
        outRect.bottom = halfSpace;
        outRect.left = halfSpace;
        outRect.right = halfSpace;
    }
}

그런 다음 사용하십시오

mRecyclerView.addItemDecoration(new SpacesItemDecoration(mMargin));

1
이것은 가장 간단한 것입니다. 한 가지 중요한 점은 xml에서 부모에 패딩을 추가해야한다는 것입니다. 제 경우에는 그렇게 작동합니다. 감사.
Samir

SpaceItemDecoration실제로 부모 (리사이클보기)에 패딩을 추가합니다.
Mark Hetherington

halfSpacexml에서 패딩을 부모로 설정하지 않았을 때 패딩 만 나타남 (오른쪽)
Samir

오른쪽에만 없어 졌습니까? xml에서 이미 왼쪽에 leftPadding으로 절반의 공간이 설정되어있을 수 있으며이 코드는 왼쪽 패딩이 RecyclerView에 설정되어 있는지 여부 만 확인합니다.
Mark Hetherington

글쎄, XML에 패딩 세트가 없습니다.
Samir

11

다음은 "spanCount" (열 수)가 필요없는 솔루션입니다. GridAutofitLayoutManager를 사용하기 때문에 사용 합니다 (필수 셀 크기에 따라 열 수를 계산).

(이것은 GridLayoutManager 에서만 작동합니다 )

public class GridSpacesItemDecoration extends RecyclerView.ItemDecoration {
    private final boolean includeEdge;
    private int spacing;


    public GridSpacesItemDecoration(int spacing, boolean includeEdge) {
        this.spacing = spacing;
        this.includeEdge = includeEdge;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        if (parent.getLayoutManager() instanceof GridLayoutManager) {
            GridLayoutManager layoutManager = (GridLayoutManager)parent.getLayoutManager();
            int spanCount = layoutManager.getSpanCount();
            int position = parent.getChildAdapterPosition(view); // item position
            int column = position % spanCount; // item column

            if (includeEdge) {
                outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
                outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)

                if (position < spanCount) { // top edge
                    outRect.top = spacing;
                }
                outRect.bottom = spacing; // item bottom
            } else {
                outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)
                outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f /    spanCount) * spacing)
                if (position >= spanCount) {
                    outRect.top = spacing; // item top
                }
            }

        }

    }
}

다음은 누구나 관심 이있는 GridAutofitLayoutManager 입니다.

public class GridAutofitLayoutManager extends GridLayoutManager {
    private int mColumnWidth;
    private boolean mColumnWidthChanged = true;

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

    public GridAutofitLayoutManager(Context context,int unit, int columnWidth)
    {
        /* Initially set spanCount to 1, will be changed automatically later. */
        super(context, 1);
        int pixColumnWidth = (int) TypedValue.applyDimension(unit, columnWidth, context.getResources().getDisplayMetrics());
        setColumnWidth(checkedColumnWidth(context, pixColumnWidth));
    }

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

    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());
        }
        return columnWidth;
    }

    public void setColumnWidth(int newColumnWidth)
    {
        if (newColumnWidth > 0 && newColumnWidth != mColumnWidth)
        {
            mColumnWidth = newColumnWidth;
            mColumnWidthChanged = true;
        }
    }

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)
    {
        int width = getWidth();
        int height = getHeight();
        if (mColumnWidthChanged && mColumnWidth > 0 && width > 0 && height > 0)
        {
            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;
        }
        super.onLayoutChildren(recycler, state);
    }
}

드디어:

mDevicePhotosView.setLayoutManager(new GridAutofitLayoutManager(getContext(), getResources().getDimensionPixelSize(R.dimen.item_size)));
mDevicePhotosView.addItemDecoration(new GridSpacesItemDecoration(Util.dpToPx(getContext(), 2),true));

안녕하세요. 이것은 훌륭하게 작동하지만 솔루션에 헤더를 사용하고 있습니다. 전각 헤더를 얻는 방법을 제안 할 수 있습니까?
Ajeet

친절하게 당신은 layoutManager.getPosition(view)당신의 헤더가 될 위치가 0인지 여부를 확인한 후 다음과 같이 레이아웃 관리자로 위치 를 확인할 수 있습니다 .. 또한이 방법으로 원하는 위치에 다른 헤더를 추가 할 수 있습니다 :)
Mina Samir

9

필요할 때마다 기억하고 구현할 수있는 하나의 쉬운 솔루션이 있습니다. 버그도없고 계산도 할 ​​수 없습니다. 카드 / 아이템 레이아웃에 여백을 넣고 RecyclerView에 패딩과 같은 크기를 넣으십시오.

item_layout.xml

<CardView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:margin="10dp">

activity_layout.xml

<RecyclerView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp"/>

최신 정보: 여기에 이미지 설명을 입력하십시오


잘 작동합니다! 이 문제에 대해 자세히 설명해 주시겠습니까?
elyar abad

정말 고맙습니다! 리사이클 패딩과 아이템 마진 사이의 이러한 협력이 필요한 기술적 인 이유를 찾고있었습니다. 당신이 나를 위해 그렇게 많이 했어. . .
elyar abad

7

당신이 할 경우 고정 당신의 크기를 RecyclerView모든 장치에서 항목을 선택합니다. 당신은 이렇게 할 수 있습니다

public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {

    private int mSpanCount;
    private float mItemSize;

    public GridSpacingItemDecoration(int spanCount, int itemSize) {
        this.mSpanCount = spanCount;
        mItemSize = itemSize;
    }

    @Override
    public void getItemOffsets(final Rect outRect, final View view, RecyclerView parent,
            RecyclerView.State state) {
        final int position = parent.getChildLayoutPosition(view);
        final int column = position % mSpanCount;
        final int parentWidth = parent.getWidth();
        int spacing = (int) (parentWidth - (mItemSize * mSpanCount)) / (mSpanCount + 1);
        outRect.left = spacing - column * spacing / mSpanCount;
        outRect.right = (column + 1) * spacing / mSpanCount;

        if (position < mSpanCount) {
            outRect.top = spacing;
        }
        outRect.bottom = spacing;
    }
}

recyclerview_item.xml

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="@dimen/recycler_view_item_width" 
    ...
    >
    ...
</LinearLayout>

dimens.xml

 <dimen name="recycler_view_item_width">60dp</dimen>

활동

int numberOfColumns = 3;
mRecyclerView.setLayoutManager(new GridLayoutManager(this, numberOfColumns));
mRecyclerView.setAdapter(...);
mRecyclerView.addItemDecoration(new GridSpacingItemDecoration(3,
        getResources().getDimensionPixelSize(R.dimen.recycler_view_item_width)));

여기에 이미지 설명을 입력하십시오 여기에 이미지 설명을 입력하십시오


화면 크기에 따라 작동합니까 5 인치 화면에 표시되는 방식을 의미하며 다른 화면 크기에서도 동일하게 표시됩니까?
Sunil

항목의 크기는 고정되지만 항목 사이의 공간이 다를 수 있습니다. 이해하기에는 위의 두 이미지도 볼 수 있습니다
Phan Van Linh

화면 크기에 따라 다르게 보입니다. 어떤 방식 으로든 감사합니다
Sunil

6

선택한 답변은 거의 완벽하지만 공간에 따라 항목 너비가 같을 수 없습니다. (제 경우에는 중요했습니다). 그래서이 코드로 공간을 조금 늘려서 항목의 너비가 모두 같습니다.

   class GridSpacingItemDecoration(private val columnCount: Int, @Px preferredSpace: Int, private val includeEdge: Boolean): RecyclerView.ItemDecoration() {

    /**
     * In this algorithm space should divide by 3 without remnant or width of items can have a difference
     * and we want them to be exactly the same
     */
    private val space = if (preferredSpace % 3 == 0) preferredSpace else (preferredSpace + (3 - preferredSpace % 3))

    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State?) {
        val position = parent.getChildAdapterPosition(view)

        if (includeEdge) {

            when {
                position % columnCount == 0 -> {
                    outRect.left = space
                    outRect.right = space / 3
                }
                position % columnCount == columnCount - 1 -> {
                    outRect.right = space
                    outRect.left = space / 3
                }
                else -> {
                    outRect.left = space * 2 / 3
                    outRect.right = space * 2 / 3
                }
            }

            if (position < columnCount) {
                outRect.top = space
            }

            outRect.bottom = space

        } else {

            when {
                position % columnCount == 0 -> outRect.right = space * 2 / 3
                position % columnCount == columnCount - 1 -> outRect.left = space * 2 / 3
                else -> {
                    outRect.left = space / 3
                    outRect.right = space / 3
                }
            }

            if (position >= columnCount) {
                outRect.top = space
            }
        }
    }

}

나 같은 사람이 spanCount = 1 GridLayoutManager를 사용하는 경우 나, 다음 줄을 추가합니다 columnCount == 1 -> { outRect.left = space outRect.right = space }
massivemadness

5

@edwardaa가 제공 한 코드를 복사했으며 RTL을 완벽하게 지원합니다.

public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
    private int spanCount;
    private int spacing;
    private boolean includeEdge;
    private int headerNum;
    private boolean isRtl = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL;

    public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge, int headerNum) {
        this.spanCount = spanCount;
        this.spacing = spacing;
        this.includeEdge = includeEdge;
        this.headerNum = headerNum;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        int position = parent.getChildAdapterPosition(view) - headerNum; // item position
        if (position >= 0) {
            int column = position % spanCount; // item column
            if(isRtl) {
                column = spanCount - 1 - column;
            }
            if (includeEdge) {
                outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
                outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)

                if (position < spanCount) { // top edge
                    outRect.top = spacing;
                }
                outRect.bottom = spacing; // item bottom
            } else {
                outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)
                outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f /    spanCount) * spacing)
                if (position >= spanCount) {
                    outRect.top = spacing; // item top
                }
            }
        } else {
            outRect.left = 0;
            outRect.right = 0;
            outRect.top = 0;
            outRect.bottom = 0;
        }
    }
}

에서 코드를 복사 할 수있는 모든 일 gist.github.com/xingstarx/f2525ef32b04a5e67fecc5c0b5c4b939
Xingxing

4

위의 답변은 여백 처리 GridLayoutManager 및 LinearLayoutManager를 설정하는 방법을 명확히했습니다.

그러나 StaggeredGridLayoutManager의 경우 Pirdad Sakhizada의 답변은 "StaggeredGridLayoutManager와는 잘 작동하지 않을 수 있습니다"라고 말합니다. IndexOfSpan에 대한 문제 여야합니다.

이 방법으로 얻을 수 있습니다 :

private static class MyItemDecoration extends RecyclerView.ItemDecoration {
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        int index = ((StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanIndex();
    }
}

4
public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {

    private int spanCount;
    private int spacing;
    private boolean includeEdge;

    public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) {
        this.spanCount = spanCount;
        this.spacing = spacing;
        this.includeEdge = includeEdge;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
        int column = params.getSpanIndex();

        if (includeEdge) {
            outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
            outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)

            if (position < spanCount) { // top edge
                outRect.top = spacing;
            }
            outRect.bottom = spacing; // item bottom
        } else {
            outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)
            outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f /    spanCount) * spacing)
            if (position >= spanCount) {
                outRect.top = spacing; // item top
            }
        }
    }
}

edwardaa의 답변과 약간 다른 점은 다양한 높이의 항목과 같은 경우 열을 단순히 %로 결정할 수 없기 때문에 열이 결정되는 방식입니다. spanCount


4
class VerticalGridSpacingDecoration(private val spacing: Int) : RecyclerView.ItemDecoration() {

  override fun getItemOffsets(
    outRect: Rect,
    view: View,
    parent: RecyclerView,
    state: State
  ) {
    val layoutManager = parent.layoutManager as? GridLayoutManager
    if (layoutManager == null || layoutManager.orientation != VERTICAL) {
      return super.getItemOffsets(outRect, view, parent, state)
    }

    val spanCount = layoutManager.spanCount
    val position = parent.getChildAdapterPosition(view)
    val column = position % spanCount
    with(outRect) {
      left = if (column == 0) 0 else spacing / 2
      right = if (column == spanCount.dec()) 0 else spacing / 2
      top = if (position < spanCount) 0 else spacing
    }
  }
}

3

여기 내 수정은 numOfColums공백을 위, 아래, 왼쪽 및 오른쪽에 동일하게SpacesItemDecoration 사용할 수 있습니다 .

public class SpacesItemDecoration extends RecyclerView.ItemDecoration {
    private int space;
    private int mNumCol;

    public SpacesItemDecoration(int space, int numCol) {
        this.space = space;
        this.mNumCol=numCol;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view,
                               RecyclerView parent, RecyclerView.State state) {

        //outRect.right = space;
        outRect.bottom = space;
        //outRect.left = space;

        //Log.d("ttt", "item position" + parent.getChildLayoutPosition(view));
        int position=parent.getChildLayoutPosition(view);

        if(mNumCol<=2) {
            if (position == 0) {
                outRect.left = space;
                outRect.right = space / 2;
            } else {
                if ((position % mNumCol) != 0) {
                    outRect.left = space / 2;
                    outRect.right = space;
                } else {
                    outRect.left = space;
                    outRect.right = space / 2;
                }
            }
        }else{
            if (position == 0) {
                outRect.left = space;
                outRect.right = space / 2;
            } else {
                if ((position % mNumCol) == 0) {
                    outRect.left = space;
                    outRect.right = space/2;
                } else if((position % mNumCol) == (mNumCol-1)){
                    outRect.left = space/2;
                    outRect.right = space;
                }else{
                    outRect.left=space/2;
                    outRect.right=space/2;
                }
            }

        }

        if(position<mNumCol){
            outRect.top=space;
        }else{
            outRect.top=0;
        }
        // Add top margin only for the first item to avoid double space between items
        /*
        if (parent.getChildLayoutPosition(view) == 0 ) {

        } else {
            outRect.top = 0;
        }*/
    }
}

논리에서 아래 코드를 사용하십시오.

recyclerView.addItemDecoration(new SpacesItemDecoration(spacingInPixels, numCol));

2

모든 LayoutManager에서 작동하는 XML 만 사용하여이 문제에 대한 매우 간단하면서도 유연한 솔루션이 있습니다.

같은 간격의 X를 원한다고 가정합니다 (예 : 8dp).

  1. CardView 항목을 다른 레이아웃으로 포장

  2. 외부 레이아웃에 X / 2 (4dp)의 패딩을 제공하십시오.

  3. 외부 레이아웃 배경을 투명하게 만들기

...

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:background="@android:color/transparent"
    android:padding="4dip">

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </android.support.v7.widget.CardView>

</LinearLayout>
  1. RecyclerView에 X / 2 (4dp) 패딩 제공

...

<android.support.v7.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="4dp" />

그리고 그게 다야. X (8dp)의 완벽한 간격이 있습니다.


2

staggeredLayoutManager에 문제가있는 사람들 (예 : https://imgur.com/XVutH5u )

recyclerView의 방법 :

getChildAdapterPosition(view)
getChildLayoutPosition(view)

때때로 -1을 인덱스로 반환하므로 itemDecor 설정에 문제가 발생할 수 있습니다. 내 솔루션은 더 이상 사용되지 않는 ItemDecoration의 메소드를 대체하는 것입니다.

public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent)

초보자 대신 :

public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)

이처럼 :

recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
            @Override
            public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
                TheAdapter.VH vh = (TheAdapter.VH) recyclerView.findViewHolderForAdapterPosition(itemPosition);
                View itemView = vh.itemView;    //itemView is the base view of viewHolder
                //or instead of the 2 lines above maybe it's possible to use  View itemView = layoutManager.findViewByPosition(itemPosition)  ... NOT TESTED

                StaggeredGridLayoutManager.LayoutParams itemLayoutParams = (StaggeredGridLayoutManager.LayoutParams) itemView.getLayoutParams();

                int spanIndex = itemLayoutParams.getSpanIndex();

                if (spanIndex == 0)
                    ...
                else
                    ...
            }
        });

지금까지 나를 위해 일하는 것 같습니다 :)


큰 대답 남자! 항목 사이에 헤더 항목이있는 대칭 "일반"GridLayoutManager를 포함하여 모든 경우에 작동합니다. 감사!
Shirane85

2

이 질문에 대한 답은 생각보다 복잡해 보입니다. 여기에 내가 걸릴 것입니다.

그리드 항목 사이에 1dp 간격을 원한다고 가정 해 봅시다. 다음을 수행하십시오.

  1. 의 패딩 추가 0.5dp을각 항목
  2. 의 패딩 추가 -0.5dp을 받는 RecycleView
  3. 그게 다야! :)

1

이것은 RecyclerView헤더에서도 작동 합니다.

public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {

    private int spanCount;
    private int spacing;
    private boolean includeEdge;
    private int headerNum;

    public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge, int headerNum) {
        this.spanCount = spanCount;
        this.spacing = spacing;
        this.includeEdge = includeEdge;
        this.headerNum = headerNum;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        int position = parent.getChildAdapterPosition(view) - headerNum; // item position

        if (position >= 0) {
            int column = position % spanCount; // item column

            if (includeEdge) {
                outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
                outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)

                if (position < spanCount) { // top edge
                    outRect.top = spacing;
                }
                outRect.bottom = spacing; // item bottom
            } else {
                outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)
                outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f /    spanCount) * spacing)
                if (position >= spanCount) {
                    outRect.top = spacing; // item top
                }
            }
        } else {
            outRect.left = 0;
            outRect.right = 0;
            outRect.top = 0;
            outRect.bottom = 0;
        }
    }
    }
}

headerNum은 무엇입니까?
Tim Kranen

1

yqritc의 답변이 완벽하게 작동했습니다. 그러나 Kotlin을 사용하고 있었으므로 여기에 해당합니다.

class ItemOffsetDecoration : RecyclerView.ItemDecoration  {

    // amount to add to padding
    private val _itemOffset: Int

    constructor(itemOffset: Int) {
        _itemOffset = itemOffset
    }

    constructor(@NonNull context: Context, @DimenRes itemOffsetId: Int){
       _itemOffset = context.resources.getDimensionPixelSize(itemOffsetId)
    }

    /**
     * Applies padding to all sides of the [Rect], which is the container for the view
     */
    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView,state: RecyclerView.State) {
        super.getItemOffsets(outRect, view, parent, state)
        outRect.set(_itemOffset, _itemOffset, _itemOffset, _itemOffset)
    }
}

다른 모든 것은 동일합니다.


1

들어 StaggeredGridLayoutManager의 사용자, 여기에 대부분 포함 답변을 많이 코드 아래에 하나의 계산 항목 열을 선정,주의 :

int column = position % spanCount

이는 1st / 3rd / 5th / .. 항목이 항상 왼쪽에 있고 2nd / 4th / 6th / .. 항목이 항상 오른쪽에 있다고 가정합니다. 이 가정은 항상 사실입니까? 아니.

첫 번째 항목의 높이가 100dp이고 두 번째의 항목이 50dp라고 가정 해보십시오. 세 번째 항목의 위치가 왼쪽 또는 오른쪽인지 추측하십시오.


0

GridLayoutManager 및 HeaderView 사용하여 RecyclerView에 대해 그렇게했습니다 .

아래 코드에서 모든 항목 사이에 4dp 간격을 설정했습니다 (모든 단일 항목 주위에 2dp 및 전체 recyclerview 주위에 2dp 패딩).

layout.xml

<android.support.v7.widget.RecyclerView
    android:id="@+id/recycleview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="2dp" />

조각 / 활동

GridLayoutManager manager = new GridLayoutManager(getContext(), 3);
recyclerView.setLayoutManager(manager);
int spacingInPixels = Utils.dpToPx(2);
recyclerView.addItemDecoration(new SpacesItemDecoration(spacingInPixels));

SpaceItemDecoration.java

public class SpacesItemDecoration extends RecyclerView.ItemDecoration {

    private int mSpacing;

    public SpacesItemDecoration(int spacing) {
        mSpacing = spacing;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView recyclerView, RecyclerView.State state) {
        outRect.left = mSpacing;
        outRect.top = mSpacing;
        outRect.right = mSpacing;
        outRect.bottom = mSpacing;
    }
}

Utils.java

public static int dpToPx(final float dp) {
    return Math.round(dp * (Resources.getSystem().getDisplayMetrics().xdpi / DisplayMetrics.DENSITY_DEFAULT));
}

0

로 만든 https://stackoverflow.com/a/29905000/1649371 솔루션 작업 (위) 나는 다음과 같은 방법 (모든 후속 호출)를 수정했다

@SuppressWarnings("all")
protected int getItemSpanSize(RecyclerView parent, View view, int childIndex) {

    RecyclerView.LayoutManager mgr = parent.getLayoutManager();
    if (mgr instanceof GridLayoutManager) {
        return ((GridLayoutManager) mgr).getSpanSizeLookup().getSpanSize(childIndex);
    } else if (mgr instanceof StaggeredGridLayoutManager) {
        return ((StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams()).isFullSpan() ? spanCount : 1;
    } else if (mgr instanceof LinearLayoutManager) {
        return 1;
    }

    return -1;
}

@SuppressWarnings("all")
protected int getItemSpanIndex(RecyclerView parent, View view, int childIndex) {

    RecyclerView.LayoutManager mgr = parent.getLayoutManager();
    if (mgr instanceof GridLayoutManager) {
        return ((GridLayoutManager) mgr).getSpanSizeLookup().getSpanIndex(childIndex, spanCount);
    } else if (mgr instanceof StaggeredGridLayoutManager) {
        return ((StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams()).getSpanIndex();
    } else if (mgr instanceof LinearLayoutManager) {
        return 0;
    }

    return -1;
}


0

목록을 그리드로 전환하는 전환 스위치가있는 경우 recyclerView.removeItemDecoration()새 항목 장식을 설정하기 전에 전화하는 것을 잊지 마십시오 . 그렇지 않으면 간격에 대한 새로운 계산이 올바르지 않습니다.


이 같은.

        recyclerView.removeItemDecoration(gridItemDecorator)
        recyclerView.removeItemDecoration(listItemDecorator)
        if (showAsList){
            recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
            recyclerView.addItemDecoration(listItemDecorator)
        }
        else{
            recyclerView.layoutManager = GridLayoutManager(this, spanCount)
            recyclerView.addItemDecoration(gridItemDecorator)
        }

0

당신이 사용하는 경우 헤더GridLayoutManager 작성이 코드를 사용 코 틀린를 그리드 사이의 간격을 위해 :

inner class SpacesItemDecoration(itemSpace: Int) : RecyclerView.ItemDecoration() {
    var space: Int = itemSpace

    override fun getItemOffsets(outRect: Rect?, view: View?, parent: RecyclerView?, state: RecyclerView.State?) {
        super.getItemOffsets(outRect, view, parent, state)
        val position = parent!!.getChildAdapterPosition(view)
        val viewType = parent.adapter.getItemViewType(position)
       //check to not to set any margin to header item 
        if (viewType == GridViewAdapter.TYPE_HEADER) {
            outRect!!.top = 0
            outRect.left = 0
            outRect.right = 0
            outRect.bottom = 0
        } else {
            outRect!!.left = space
            outRect.right = space
            outRect.bottom = space

            if (parent.getChildLayoutPosition(view) == 0) {
                outRect.top = space
            } else {
                outRect.top = 0
            }
        }
    }
    }

그리고 통과 ItemDecorationrecyclerview

mIssueGridView.addItemDecoration(SpacesItemDecoration(10))

0

항목 사이에 공백이있는 어린이 문제에 CardView를 사용하는 경우 app : cardUseCompatPadding을 true로 설정하여 해결할 수 있습니다.

여백이 클수록 항목 높이가 커집니다. CardElevation은 선택 사항입니다 (기본값 사용).

<androidx.cardview.widget.CardView
    xmlns:app="http://schemas.android.com/apk/res-auto"
    app:cardUseCompatPadding="true"
    app:cardElevation="2dp">

-1

고마워 edwardaa의 답변 https://stackoverflow.com/a/30701422/2227031

주목해야 할 또 다른 사항은 다음과 같습니다.

총 간격 및 총 itemWidth가 화면 너비와 같지 않으면 어댑터 onBindViewHolder 메소드에서 itemWidth도 조정해야합니다.

Utils.init(_mActivity);
int width = 0;
if (includeEdge) {
    width = ScreenUtils.getScreenWidth() - spacing * (spanCount + 1);
} else {
    width = ScreenUtils.getScreenWidth() - spacing * (spanCount - 1);
}
int itemWidth = width / spanCount;

ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) holder.imageViewAvatar.getLayoutParams();
// suppose the width and height are the same
layoutParams.width = itemWidth;
layoutParams.height = itemWidth;
holder.imageViewAvatar.setLayoutParams(layoutParams);

-1

edwardaa의 훌륭한 답변을 기반으로 한 Kotlin 버전

class RecyclerItemDecoration(private val spanCount: Int, private val spacing: Int) : RecyclerView.ItemDecoration() {

  override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {

    val spacing = Math.round(spacing * parent.context.resources.displayMetrics.density)
    val position = parent.getChildAdapterPosition(view)
    val column = position % spanCount

    outRect.left = spacing - column * spacing / spanCount
    outRect.right = (column + 1) * spacing / spanCount

    outRect.top = if (position < spanCount) spacing else 0
    outRect.bottom = spacing
  }

}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.