Android Recyclerview GridLayoutManagerの列間隔


250

GridLayoutManagerを使用してRecyclerViewで列間隔を設定するにはどうすればよいですか?レイアウト内のマージン/パディングを設定しても効果がありません。


あなたはサブクラス化GridLayoutManagerとオーバーライドを試みましたgenerateDefaultLayoutParams()か?
CommonsWare、2015

私はそうではありませんでした。グリッドビューのような間隔を設定するために、私が見ていなかった方法があると思いました。私はそれを試します
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 2015

2
@zatziky-ええ、すでに上部と下部のパディングを自分の一部として使用している場合RecyclerView(そしてを使用している場合clipToPadding="false")、少し再構成できます。ただし、そうでない場合は、ifチェックを最後に移動するだけです(最後の項目の下部のパディングが必要なため)。
ianhanniballake 2015

18
@ianhanniballake、これはシングルスパンレイアウトマネージャーを使用している場合は機能しますが、マルチスパンレイアウトマネージャーでは失敗します。
Avinash R

4
GridLayoutManagerでこのようにすると、2番目、3番目、n番目の列の最初のすべての項目が上に固定されます(スペースがないため)。私はそれが=スペース/ 2と.bottom =スペース/ 2 .top行う方が良いと思いだから
ヤロスラフ

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
ヘッダーなど、さまざまなスパンを持つアイテムがない限り、機能します。
マシュー

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の水平マージンが異なる場合があります。
ウフコク2016年

2
レイアウトがrtl(2列以上)の場合、これは機能しません。現在、RTLモードの場合、列間のスペースは正しくありません。rtlにある場合、outRect.leftをoutRect.rightに置き換える必要があります。
Masoud Mohammadi

83

以下は、アイテムの周りに等間隔で、アイテムサイズを等しくしたい場合の段階的なシンプルなソリューションです。

ItemOffsetDecoration

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番目のアイテムに対して2を返すために発生します。したがって、次のように上端のアイテムにスペースを追加することをお勧めします:outRect.top = childIndex <spanCount?spacingInPixels:0; そして、各アイテムの下のスペースを追加します:outRect.bottom = spacingInPixels;
IvanP 2015年

RecyclerViewのスクロール時に間隔が変わりました。
Yugesh

3
parent.getChildCount()を "parent.getLayoutManager()。getItemCount()"に変更する必要があると思います。また、isBottomEdge関数を「return childIndex> = childCount-spanCount + spanIndex」に変更する必要があります。これらを変更した後、等間隔になりました。ただし、位置によってオフセット値が異なるため、スパンカウントが2より大きい場合、このソリューションでは等しいアイテムサイズが得られないことに注意してください。
yqritc 2015年

1
@yqritc nocticing parent.getChildCount()に感謝します。parent.getLayoutManager()。getItemCount()を使用するように回答を更新しました
Pirdad Sakhizada

2
これは、可変スパンでも、箱から出してうまく機能しました。おめでとうございます。ありがとうございます。
Pierre-Luc Paour 2017

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
これは最も単純なものです。重要なことの1つは、xmlの親にパディングを追加することです。私の場合、それはそのように機能します。ありがとう。
Samir 2015

SpaceItemDecoration実際にパディングを親(リサイクラービュー)に追加します。
Mark Hetherington、2015

halfSpacexmlでパディングを親に設定しなかった場合、パディングのみが表示されました(右側に)
Samir

右側のみ欠けていましたか?半分のスペースが左側のleftPaddingとして既にxmlに設定されている可能性があります。このコードは、左側のパディングがRecyclerViewに設定されているかどうかのみをチェックします。
Mark Hetherington、2015

さて、私は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 2017年

親切に、次のようlayoutManager.getPosition(view)にレイアウトマネージャーで位置を確認できます。その後、ヘッダーになる位置がゼロであるかどうかを確認します。また、この方法により、任意の位置に別のヘッダーを追加できます。)
Mina Samir

9

簡単な解決策は1つだけで、必要な場所に覚えて実装できます。バグもおかしな計算もありません。カード/アイテムのレイアウトにマージンを入れ、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インチ画面での表示方法を意味します。他の画面サイズでも同じように見えますか?
スニル

アイテムのサイズは固定されますが、アイテム間のスペースは異なる場合があります。理解するには上記の2つの画像も表示されます
ファンファンリン

画面のサイズが違えば、見た目も異なります。ただし、作業中です。ありがとう
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
            }
        }
    }

}

私のような人がspanLayout = 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

SpacesItemDecorationこれがnumOfColumsスペースを上、下、左、右に均等に取ることができる私の変更です。

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の等間隔(たとえば、8 dp)が必要だとします。

  1. CardViewアイテムを別のレイアウトでラップする

  2. 外側のレイアウトにX / 2(4 dp)のパディングを与える

  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(4 dp)のパディングを与える

...

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

以上です。Xの完全な間隔(8 dp)があります。


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

この質問に対する回答は、本来あるべきものよりも複雑に見えます。これが私の見解です。

グリッドアイテム間の間隔を1 dpにしたいとします。以下をせよ:

  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のユーザーが、ここで最もなどの回答の多くはコードの下で1つの計算項目欄を投票し、注意してください:

int column = position % spanCount

これは、1/3/3/5 / ..アイテムが常に左側に配置され、2nd / 4th / 6th / ..アイテムが常に右側に配置されることを前提としています。この仮定は常に正しいですか?番号。

1つ目のアイテムの高さが100 dpで2つ目のアイテムが50 dpしかないとしましょう。3つ目のアイテムはどこにありますか。


0

最終的には、GridLayoutManagerとHeaderViewを使用して、RecyclerViewでも同じようにしました。

以下のコードでは、すべてのアイテムの間に4 dpのスペースを設定します(すべてのアイテムの周りに2 dp、リサイクラービュー全体の周りに2 dpのパディング)。

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()新しいItemデコレーションを設定する前に呼び出すことを忘れないでください。そうでない場合、間隔の新しい計算は正しくありません。


このようなもの。

        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でヘッダーを使用している場合は、グリッド間の間隔にkotlinで記述された次のコードを使用します。

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

注意すべきもう1つの点は、

合計間隔と合計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.