Penempatan kolom Android Recyclerview GridLayoutManager


251

Bagaimana Anda mengatur spasi kolom dengan RecyclerView menggunakan GridLayoutManager? Mengatur margin / padding di dalam layout saya tidak berpengaruh.


Sudahkah Anda mencoba subclassing GridLayoutManagerdan overriding generateDefaultLayoutParams()serta kerabat?
CommonsWare

Saya belum, saya pikir akan ada metode saya hanya tidak melihat untuk mengatur jarak tampilan grid seperti. Saya akan mencobanya
hitch.united


Jawaban:


348

RecyclerViews mendukung konsep ItemDecoration : offset khusus dan menggambar di sekitar setiap elemen. Seperti yang terlihat dalam jawaban ini , Anda dapat menggunakan

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

Kemudian tambahkan melalui

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

3
Gunakan 'outRect.top = space' dan hapus 'outRect.bottom' jika Anda tidak ingin mengacaukan 'jika untuk posisi pertama'. ; -]
Amio.io

2
@zatziky - ya, jika Anda sudah menggunakan bantalan atas dan bawah sebagai bagian dari RecyclerView(dan penggunaan clipToPadding="false") Anda, maka Anda dapat merestrukturisasi hal-hal sedikit. Namun, jika Anda tidak melakukannya, Anda hanya akan memindahkan centang if menjadi yang terakhir (karena Anda masih menginginkan bantalan bawah pada item terakhir).
ianhanniballake

18
@ianhanniballake, sementara ini berfungsi saat menggunakan manajer tata letak bentang tunggal, gagal untuk pengelola tata letak multi-bentang.
Avinash R

4
Jika Anda melakukannya dengan GridLayoutManager - semua item pertama dari kolom ke-2, ke-3 ... akan tetap berada di atas (karena tidak ada ruang). Jadi saya pikir lebih baik melakukan .top = space / 2 dan .bottom = space / 2.
Yaroslav

1
Jawaban ini tidak menjawab pertanyaan awal. Penekanan pertanyaan ada pada GridLayoutManager . Jawabannya tidak akan berfungsi pada tata letak multi-kolom / baris
1919

428

Kode berikut berfungsi dengan baik, dan setiap kolom memiliki lebar yang sama:

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

Pemakaian

1. tidak ada tepi

masukkan deskripsi gambar di sini

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

2. dengan tepi

masukkan deskripsi gambar di sini

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

12
Bekerja kecuali jika Anda memiliki item yang memiliki berbagai bentang, seperti header.
Matius

8
Jawaban bagus; satu tip: spasi di px (sehingga Anda dapat mengkonversi dp ke px menggunakan Math.round (someDpValue * getResources (). getDisplayMetrics (). density))
Kevin Lee

1
Ini berfungsi dengan baik tetapi saya mengalami masalah, saya menggunakan GridLayoutManager dengan spanCount 2 (default) tetapi pengguna dapat mengubah spanCount sehingga ketika spanCount berubah dari posisi default ada padding yang jauh lebih terlihat pada beberapa posisi seperti jika spanCount akan 3 daripada padding / margin pada 2,3 8,9 12,13 dll
Haris Qureshi

2
Bagus sekali! Tapi saya punya beberapa masalah dengan StaggeredGridLayoutManager. imgur.com/XVutH5u, margin horisontal terkadang berbeda.
Ufkoku

2
Ini tidak berfungsi ketika tata letak rtl (untuk 2 kolom atau lebih). Saat ini ruang antar kolom tidak benar ketika dalam mode rtl. Anda perlu mengganti: outRect.left dengan outRect.right ketika di rtl.
Masoud Mohammadi

83

Berikut ini adalah solusi sederhana langkah-demi-langkah jika Anda ingin jarak yang sama di sekitar item dan ukuran item yang sama.

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

Penerapan

Dalam kode sumber Anda, tambahkan ItemOffsetDecorationke RecyclerView. nilai offset Item Anda harus setengah ukuran dari nilai aktual yang ingin Anda tambahkan sebagai ruang antar item.

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

Juga, tetapkan nilai offset item sebagai padding-nya RecyclerView, dan tentukan 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"/>

Sempurna, sederhana dan efektif.
B.shruti

28

Coba ini. Ini akan menjaga jarak yang sama di sekitar. Bekerja dengan List, Grid, dan StaggeredGrid.

Diedit

Kode yang diperbarui harus menangani sebagian besar kasus sudut dengan bentang, orientasi, dll. Perhatikan bahwa jika menggunakan setSpanSizeLookup () dengan GridLayoutManager, pengaturan setSpanIndexCacheEnabled () disarankan untuk alasan kinerja.

Catatan, tampaknya dengan StaggeredGrid, tampaknya ada bug di mana indeks anak-anak menjadi aneh dan sulit dilacak sehingga kode di bawah ini mungkin tidak bekerja dengan baik dengan 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);
  }
}

Semoga ini bisa membantu.


1
Saya mendapatkan bentang ganda setelah item baris pertama. Ini terjadi karena parent.getChildCount () mengembalikan 1 untuk item pertama, 2 untuk kedua dan seterusnya. Jadi, saya sarankan tambahkan spasi ke item dari tepi atas seperti: outRect.top = childIndex <spanCount? spacingInPixels: 0; Dan tambahkan ruang bawah untuk setiap item: outRect.bottom = spacingInPixels;
IvanP

Pada saat bergulir RecyclerView, spasi berubah.
Yugesh

3
Saya pikir parent.getChildCount () harus diubah menjadi "parent.getLayoutManager (). GetItemCount ()". Juga, fungsi isBottomEdge perlu diubah menjadi "return childIndex> = childCount - spanCount + spanIndex". Setelah mengubah ini, saya mendapat spasi yang sama. Tetapi harap dicatat bahwa solusi ini tidak memberi saya ukuran item yang sama jika jumlah bentang lebih besar dari 2 karena nilai offset berbeda tergantung pada posisi.
yqritc

1
@yqritc terima kasih atas nocticing parent.getChildCount (). Saya telah memperbarui jawaban saya untuk menggunakan parent.getLayoutManager (). GetItemCount ()
Pirdad Sakhizada

2
Ini bekerja sangat baik di luar kotak bahkan dengan rentang variabel, selamat dan terima kasih!
Pierre-Luc Paour

21

Kode berikut akan menangani StaggeredGridLayoutManager, GridLayoutManager, dan 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;
    }
}

Lalu gunakan itu

mRecyclerView.addItemDecoration(new SpacesItemDecoration(mMargin));

1
Ini yang paling sederhana. Satu hal penting adalah Anda juga harus menambahkan padding ke induk di xml. Dalam kasus saya, itu bekerja seperti itu. Terima kasih.
Samir

The SpaceItemDecorationbenar-benar menambah padding untuk orang tua (pandangan pendaur ulang).
Mark Hetherington

hanya halfSpacepadding muncul (ke kanan) ketika saya belum mengatur padding ke orang tua dalam xml
Samir

Itu hanya hilang di sisi kanan? Mungkin Anda memiliki setengah ruang yang disetel sebagai Padding kiri di sisi kiri sudah di xml dan kode ini hanya memeriksa apakah padding kiri diatur pada RecyclerView atau tidak.
Mark Hetherington

Yah saya tidak punya set padding di xml.
Samir

11

Berikut adalah solusi yang tidak memerlukan "spanCount" (jumlah kolom) yang saya gunakan karena saya menggunakan GridAutofitLayoutManager (menghitung jumlah kolom sesuai dengan ukuran sel yang diperlukan)

(berhati-hatilah karena ini hanya akan berfungsi di 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
                }
            }

        }

    }
}

Berikut adalah GridAutofitLayoutManager yang diminati:

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

Akhirnya:

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

Hai. Ini berfungsi luar biasa tetapi saya menggunakan header dengan solusi Anda. Bisakah Anda menyarankan bagaimana bisa mencapai header lebar penuh?
Ajeet

silakan Anda dapat memeriksa posisi dengan manajer tata letak sebagai berikut layoutManager.getPosition(view)setelah itu memeriksa apakah posisinya nol yang akan menjadi tajuk Anda .. juga, cara ini akan memungkinkan Anda untuk menambahkan tajuk lain di posisi apa pun yang Anda inginkan :)
Mina Samir

9

Hanya ada satu solusi mudah, yang dapat Anda ingat dan terapkan di mana pun dibutuhkan. Tidak ada bug, tidak ada perhitungan gila. Beri margin pada tata letak kartu / item dan masukkan ukuran yang sama dengan padding ke 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"/>

MEMPERBARUI: masukkan deskripsi gambar di sini


Ini bekerja dengan baik! bisakah Anda menguraikan masalah ini?
elyar abad

Terima kasih banyak! Saya mencari beberapa alasan teknis mengapa kerja sama antara pad pendaur ulang dan margin barang diperlukan. Bagaimanapun Anda melakukan begitu banyak untuk saya. . .
elyar abad

7

Jika Anda ingin MEMPERBAIKI ukuran RecyclerViewitem Anda di semua perangkat. Anda bisa melakukan ini

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>

Aktivitas

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

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini


apakah ini akan bekerja sesuai dengan ukuran layar berarti caranya ditampilkan di layar 5 inci, mereka juga terlihat sama pada ukuran layar lainnya?
Sunil

ukuran item akan diperbaiki tetapi ruang antara item mungkin berbeda, Anda dapat melihat juga 2 gambar di atas untuk dipahami
Phan Van Linh

Mereka terlihat berbeda pada ukuran layar yang berbeda. Bagaimanapun juga, tetapi berfungsi, terima kasih
Sunil

6

Jawaban yang dipilih hampir sempurna, tetapi tergantung pada ruang, lebar item bisa tidak sama. (Dalam kasus saya itu sangat penting). Jadi saya berakhir dengan kode ini yang menambah sedikit ruang, jadi semuanya memiliki lebar yang sama.

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

}

Saya akan menambahkan baris berikut, jika seseorang seperti saya menggunakan GridLayoutManager dengan spanCount = 1 columnCount == 1 -> { outRect.left = space outRect.right = space }
massivemadness

5

Salinan @edwardaa memberikan kode dan saya membuatnya sempurna untuk mendukung 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;
        }
    }
}


4

Jawaban di atas telah memperjelas cara untuk mengatur penanganan margin GridLayoutManager dan LinearLayoutManager.

Tetapi untuk StaggeredGridLayoutManager, jawaban Pirdad Sakhizada mengatakan: "Ini mungkin tidak bekerja dengan baik dengan StaggeredGridLayoutManager". Seharusnya masalah tentang IndexOfSpan.

Anda bisa mendapatkannya dengan cara ini:

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

Sedikit berbeda dari jawaban edwardaa, perbedaannya adalah bagaimana kolom ditentukan, karena dalam kasus seperti item dengan berbagai ketinggian, kolom tidak dapat ditentukan hanya dengan% 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

Berikut ini modifikasi saya SpacesItemDecorationyang dapat mengambil numOfColums dan ruang yang sama di atas, bawah, kiri dan kanan .

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;
        }*/
    }
}

dan gunakan kode di bawah ini pada logika Anda.

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

2

Ada solusi yang sangat sederhana namun fleksibel untuk masalah ini dengan hanya menggunakan XML yang berfungsi pada setiap LayoutManager.

Asumsikan Anda menginginkan spasi X yang sama (misalnya 8dp).

  1. Bungkus item CardView Anda di Layout lain

  2. Berikan Layout luar padding X / 2 (4dp)

  3. Jadikan latar belakang Tata Letak transparan

...

<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. Berikan RecyclerView Anda padding X / 2 (4dp)

...

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

dan itu saja. Anda memiliki jarak X (8dp) yang sempurna.


2

Bagi mereka yang memiliki masalah dengan staggeredLayoutManager (seperti https://imgur.com/XVutH5u )

metode recyclerView:

getChildAdapterPosition(view)
getChildLayoutPosition(view)

terkadang mengembalikan -1 sebagai indeks sehingga kita mungkin menghadapi masalah pengaturan itemDecor. Solusi saya adalah mengganti metode ItemDecoration yang sudah usang:

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

sebagai ganti dari newbie:

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

seperti ini:

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

Tampaknya bekerja untuk saya sejauh ini :)


Jawaban yang bagus! Berfungsi untuk semua kasus, termasuk GridLayoutManager yang tidak simetris di mana Anda memiliki item header di antara item. Terima kasih!
Shirane85

2

Jawaban untuk pertanyaan ini tampaknya lebih kompleks dari yang seharusnya. Inilah pendapat saya tentang ini.

Katakanlah Anda ingin spasi 1dp di antara item kisi. Lakukan hal berikut:

  1. Tambahkan padding 0,5dp untuk setiap item
  2. Tambahkan padding -0.5dp ke RecycleView
  3. Itu dia! :)

1

Ini akan bekerja RecyclerViewdengan header juga.

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

Apa itu headerNum?
Tim Kranen

1

Jawaban yqritc bekerja dengan baik untuk saya. Saya menggunakan Kotlin, jadi di sini ada padanannya.

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

yang lainnya sama.


1

Untuk pengguna StaggeredGridLayoutManager , berhati-hatilah, banyak jawaban di sini termasuk yang paling banyak dipilih menghitung kolom item dengan kode di bawah ini:

int column = position % spanCount

yang mengasumsikan item 1/3/5 / .. selalu berada di sisi kiri dan item 2/4/6 / .. selalu berada di sisi kanan. Apakah asumsi ini selalu benar? Tidak.

Katakanlah item pertama Anda 100dp tinggi dan 2 hanya 50dp, tebak di mana letak item ke-3 Anda, kiri atau kanan?


1

Saat menggunakan CardView untuk anak-anak masalah dengan spasi di antara item dapat diselesaikan dengan mengatur aplikasi: cardUseCompatPadding menjadi true.

Untuk margin yang lebih besar memperbesar elevasi item. CardElevation bersifat opsional (gunakan nilai default).

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

0

Saya akhirnya melakukannya seperti itu untuk RecyclerView saya dengan GridLayoutManager dan HeaderView .

Dalam kode di bawah ini saya menetapkan ruang 4dp di antara setiap item (2dp di sekitar setiap item dan 2dp di sekitar keseluruhan recyclerview).

layout.xml

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

fragmen / aktivitas

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

Untuk membuat solusi https://stackoverflow.com/a/29905000/1649371 (di atas) saya harus memodifikasi metode berikut (dan semua panggilan berikutnya)

@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

Jika Anda memiliki sakelar sakelar yang beralih di antara daftar ke kisi, jangan lupa menelepon recyclerView.removeItemDecoration()sebelum mengatur hiasan Item baru. Jika tidak, maka perhitungan baru untuk spasi akan salah.


Sesuatu seperti ini.

        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

Jika Anda menggunakan Header dengan GridLayoutManager gunakan kode ini ditulis dalam kotlin untuk spasi antar grid:

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

Dan lulus ItemDecorationuntuk recyclerviewsebagai

mIssueGridView.addItemDecoration(SpacesItemDecoration(10))

-1

terima kasih jawaban edwardaa https://stackoverflow.com/a/30701422/2227031

Poin lain yang perlu diperhatikan adalah:

jika jarak total dan jumlah itemWidth tidak sama dengan lebar layar, Anda juga perlu menyesuaikan itemWidth, misalnya, pada adaptor dengan metodeBindViewHolder

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

Versi Kotlin yang saya buat berdasarkan jawaban hebat dari edwardaa

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
  }

}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.