Android Recyclerview GridLayoutManager sütun aralığı


251

Bir GridLayoutManager kullanarak RecyclerView ile sütun aralığını nasıl ayarlarsınız? Düzenimin içindeki kenar boşluğunu / dolguyu ayarlamanın bir etkisi yoktur.


Alt sınıflamayı GridLayoutManagerve geçersiz kılmayı generateDefaultLayoutParams()ve akrabaları denediniz mi?
CommonsWare

Ben değil gibi, ben sadece benzer ızgara görünümü boşluk ayarlamak için görmedim bir yöntem olacağını düşündüm. Bunu deneyeceğim
hitch.united


Yanıtlar:


348

RecyclerViews, ItemDecoration kavramını destekler : her bir eleman için özel ofsetler ve çizim. Bu cevapta görüldüğü gibi ,

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

Sonra üzerinden ekle

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

3
'OutRect.top = boşluk' kullanın ve 'ilk konum için ise' ile uğraşmak istemiyorsanız 'outRect.bottom' öğesini kaldırın. ; -]
Amio.io

2
@zatziky - evet, zaten üst ve alt dolguları RecyclerView(ve kullanımınızın clipToPadding="false") parçası olarak kullanıyorsanız , işleri biraz yeniden yapılandırabilirsiniz. Ancak bunu yapmazsanız, if işaretini son kez olacak şekilde taşıyordunuz (yine de son öğede alt dolgu olmasını istediğiniz gibi).
ianhanniballake

18
@ianhanniballake, bu tek aralıklı düzen yöneticisi kullanılırken çalışırken, çok aralıklı düzen yöneticisi için başarısız olur.
Avinash R

4
GridLayoutManager ile bu şekilde yaparsanız - 2., 3. ... n sütununun tüm ilk öğeleri üste yapışacaktır (çünkü boşluk yoktur). Bu yüzden .top = boşluk / 2 ve .bottom = boşluk / 2 yapmak daha iyi olduğunu düşünüyorum
Yaroslav

1
Bu cevap asıl soruya cevap vermiyor. Sorunun vurgusu devam ediyor GridLayoutManager . Yanıt çok sütunlu / satır düzenlerinde çalışmaz
hch

428

Aşağıdaki kod iyi çalışır ve her sütun aynı genişliğe sahiptir:

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

kullanım

1. kenar yok

resim açıklamasını buraya girin

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

2. kenar ile

resim açıklamasını buraya girin

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

12
Başlıklar gibi çeşitli açıklıklara sahip öğeleriniz yoksa çalışır.
Matthew

8
Mükemmel cevap; bir ipucu: boşluk piksel cinsindendir (böylece Math.round'u kullanarak dp'yi px'e dönüştürebilirsiniz (someDpValue * getResources (). getDisplayMetrics (). density))
Kevin Lee

1
Onun iyi çalışıyor ama bir sorun yaşıyorum, ben spanCount 2 (varsayılan) ile GridLayoutManager kullanıyorum ama spanCount varsayılan konumdan değiştiğinde bazı pozisyonlarda çok daha görünür dolgu varsa kullanıcı spanCount değiştirebilirsiniz 2,3 8,9 12,13 vb.'de dolgu / marj
Haris Qureshi

2
Harika çalışıyor! Ancak StaggeredGridLayoutManager ile ilgili bazı problemlerim var. imgur.com/XVutH5u yatay kenar boşlukları bazen farklılık gösterir.
Ufkoku

2
Düzen rtl olduğunda (2 veya daha fazla sütun için) bu çalışmaz. şu anda rtl modunda sütunlar arasındaki boşluk doğru değil. rtl'deyken: outRect.left öğesini outRect.right ile değiştirmeniz gerekir.
Mesud Mohammadi

83

Öğeler arasında eşit boşluk ve eşit öğe boyutları istiyorsanız, adım adım basit çözüm aşağıdadır.

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

uygulama

Kaynak kodunda, eklemek ItemOffsetDecorationiçin için RecyclerView. öğeleri arasındaki boşluk olarak eklemek istediğiniz gerçek değerin yarısı boyutta olmalıdır Öğe ofset değeri.

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

Ayrıca, öğe ofset değerini onun için dolgu olarak ayarlayın RecyclerViewve belirtin 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"/>

Mükemmel, basit ve etkilidir.
B.shruti

28

Bunu dene. Her yerde eşit aralıklarla ilgilenir. List, Grid ve StaggeredGrid ile birlikte çalışır.

Düzenlenen

Güncellenen kod, genişlik, yönlendirme vb. İle köşe vakalarının çoğunu işlemelidir. GridLayoutManager ile setSpanSizeLookup () kullanılırsa, performans nedenleriyle setSpanIndexCacheEnabled () ayarının önerildiğini unutmayın.

StaggeredGrid ile, çocukların endeksinin tuhaf ve izlenmesi zor olan bir hata var gibi görünüyor, bu nedenle aşağıdaki kod StaggeredGridLayoutManager ile çok iyi çalışmayabilir.

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

Umarım yardımcı olur.


1
Öğelerin ilk satırından hemen sonra çift açıklık var. Parent.getChildCount () öğesi ilk öğe için 1, ikinci için 2 ve benzeri döndürdüğü için olur. Bu yüzden, üst kenardaki öğelere boşluk eklemenizi öneririm: outRect.top = childIndex <spanCount? spacingInPixels: 0; Ve her öğe için alt boşluk ekleyin: outRect.bottom = spacingInPixels;
IvanP

RecyclerView kaydırma sırasında boşluk değişti.
Yugesh

3
Ben parent.getChildCount () "parent.getLayoutManager (). GetItemCount ()" olarak değiştirilmesi gerektiğini düşünüyorum. Ayrıca, isBottomEdge işlevinin "return childIndex> = childCount - spanCount + spanIndex" olarak değiştirilmesi gerekir. Bunları değiştirdikten sonra eşit boşluk bıraktım. Ancak, aralık sayısı 2'den büyükse bu çözümün bana eşit öğe boyutları vermediğini unutmayın, çünkü ofset değeri konuma bağlı olarak farklıdır.
yqritc

1
@ yqritc nocticing parent.getChildCount () için teşekkürler. Parent.getLayoutManager () kullanmak için cevabımı güncelledim. GetItemCount ()
Pirdad Sakhizada

2
Bu, değişken açıklıklar, tebrikler ve teşekkürler ile bile kutudan çok iyi çalıştı!
Pierre-Luc Paour

21

Aşağıdaki kod StaggeredGridLayoutManager, GridLayoutManager ve LinearLayoutManager'ı işleyecektir.

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

Sonra kullan

mRecyclerView.addItemDecoration(new SpacesItemDecoration(mMargin));

1
Bu en basit olanı. Önemli bir şey de xml içindeki ebeveyne dolgu eklemeniz gerektiğidir. Benim durumumda, bu şekilde çalışır. Teşekkürler.
Samir

SpaceItemDecorationAslında ebeveyn (recycler görünümü) için doldurma ekler.
Mark Hetherington

halfSpacexml'de ebeveyne dolgu yapmadığımda sadece dolgu (sağ tarafta) göründü
Samir

Sadece sağ tarafta mı eksikti? Zaten xml'de sol tarafta leftPadding olarak ayarlanmış yarım alanınız olabilir ve bu kod yalnızca sol dolgunun RecyclerView'da ayarlanıp ayarlanmadığını kontrol eder.
Mark Hetherington

Ben xml set herhangi bir dolgu yok.
Samir

11

İşte "spanCount" (sütun sayısı) gerektirmeyen bir çözüm çünkü GridAutofitLayoutManager kullandığım için (gerekli hücre boyutuna göre sütun sayısını hesaplar)

(bunun yalnızca GridLayoutManager'da çalışacağına dikkat edin )

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

        }

    }
}

İşte GridAutofitLayoutManager ilgilenen herkes:

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

En sonunda:

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

Selam. Bu harika çalışıyor ama çözümünüzle birlikte bir başlık kullanıyorum. Tam genişlikte başlık elde etmeyi önerebilir misiniz?
Ajeet

nazik pozisyonu ile aşağıdaki gibi pozisyon yöneticisi kontrol edebilirsiniz pozisyon layoutManager.getPosition(view)sonra sizin başlık olacak pozisyon sıfır olup olmadığını kontrol .. Ayrıca, bu şekilde istediğiniz herhangi bir pozisyonda başka bir başlık eklemek için izin verecektir :)
Mina Samir

9

İhtiyacınız olan her yerde hatırlayabileceğiniz ve uygulayabileceğiniz tek bir kolay çözüm var. Hata yok, çılgın hesaplamalar yok. Karta / öğe düzenine kenar boşluğu koyun ve RecyclerView'e dolgu ile aynı boyutta koyun:

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"/>

GÜNCELLEME: resim açıklamasını buraya girin


İyi çalışıyor! Bu konuyu biraz açıklayabilir misiniz lütfen?
elyar abad

Çok teşekkür ederim! Geri dönüştürücünün dolgusu ile ürünün marjı arasında böyle bir işbirliğinin gerekli olmasının bazı teknik nedenlerini arıyordum. Benim için çok şey yaptın. . .
elyar abad

7

Öğenizin boyutunu tüm cihazlarda DÜZELTME istiyorsanız RecyclerView. Bunu yapabilirsin

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>

Aktivite

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

resim açıklamasını buraya girin resim açıklamasını buraya girin


Ekran boyutuna göre çalışacak mı, 5 inç ekranda gösterilen yol, diğer ekran boyutlarında da aynı görünüyor mu?
Sunil

öğenin boyutu sabit olacak, ancak öğe arasındaki boşluk farklı olabilir, anlamak için yukarıda 2 resim görebilirsiniz
Phan Van Linh

Farklı ekran boyutlarında farklı görünüyorlar. Herhangi bir yol ama çalışma teşekkür ederim
Sunil

6

Seçilen cevap neredeyse mükemmeldir, ancak alana bağlı olarak, öğelerin genişliği eşit olmayabilir. (Benim durumumda kritikti). Bu yüzden biraz yer artıran bu kod ile sona erdi, böylece öğeleri tüm aynı genişliktedir.

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

}

Benim gibi biri spanCount = 1columnCount == 1 -> { outRect.left = space outRect.right = space }
massivemadness

5

@Edwardaa sağlanan kod kopyalandı ve ben RTL desteklemek için mükemmel yapmak:

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

Yukarıdaki yanıtlar, kenar boşluğu işleme GridLayoutManager ve LinearLayoutManager'ı ayarlama yollarını açıklığa kavuşturmuştur.

Ancak StaggeredGridLayoutManager için Pirdad Sakhizada'nın cevabı şöyle diyor: "StaggeredGridLayoutManager ile çok iyi çalışmayabilir". IndexOfSpan ile ilgili sorun olmalı.

Bu şekilde alabilirsiniz:

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'nın cevabından biraz farklı olan fark, sütunun nasıl belirlendiğidir, çünkü çeşitli yüksekliklere sahip öğeler gibi durumlarda, sütun sadece% ile belirlenemez 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

İşte benim değişikliktir SpacesItemDecorationalabilir numOfColums ve sola ve sağa eşit üst, alt kısmında yer, .

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

ve mantığınızda aşağıdaki kodu kullanın.

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

2

Sadece her LayoutManager üzerinde çalışan XML kullanarak bu sorun için çok basit ama esnek bir çözüm var.

X'in eşit bir aralığını istediğinizi varsayalım (örneğin 8dp).

  1. CardView öğenizi başka bir Düzen'e sarın

  2. Dış Düzen'e X / 2 (4dp) dolgusu verin

  3. Dış Düzen arka planını şeffaf yapma

...

<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 cihazınıza X / 2 (4dp) dolgusu verin

...

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

ve bu kadar. X (8dp) aralığınız mükemmel.


2

StaggeredLayoutManager ile sorun yaşayanlar için ( https://imgur.com/XVutH5u gibi )

recyclerView yöntemleri:

getChildAdapterPosition(view)
getChildLayoutPosition(view)

bazen -1 olarak dizin olarak döner, böylece itemDecor ayarıyla ilgili sorunlarla karşılaşabiliriz. Benim çözümüm, kullanımdan kaldırılmış ItemDecoration'ın yöntemini geçersiz kılmaktır:

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

acemi yerine:

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

bunun gibi:

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

Şimdiye kadar benim için çalışıyor gibi görünüyor :)


Harika cevap adamım! Öğeler arasında bir başlık öğesinin bulunduğu simetrik olmayan "normal" GridLayoutManager dahil tüm durumlar için çalışır. Teşekkürler!
Shirane85

2

Bu sorunun cevapları olması gerekenden daha karmaşık görünüyor. İşte bunu benim almam.

Izgara öğeleri arasında 1dp boşluk olmasını istediğinizi varsayalım. Aşağıdakileri yapın:

  1. Bir dolgu ekleyin 0.5dp için her öğe
  2. Bir dolgu ekleyin -0.5dp için RecycleView
  3. Bu kadar! :)

1

Bu RecyclerViewbaşlık ile de çalışacaktır .

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 nedir?
Tim Kranen

1

yqritc'in yanıtı benim için mükemmel çalıştı. Kotlin kullanıyordum ama işte buna eşdeğer.

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

diğer her şey aynı.


1

İçin StaggeredGridLayoutManager kullanıcıları, burada en dahil cevapları sürü kodunun altına bir kez hesaplamanın madde sütun olarak, dikkatli olun:

int column = position % spanCount

1. / 3. / 5. / .. öğelerinin her zaman sol tarafta ve 2. / 4. / 6 / .. öğelerinin her zaman sağ tarafta bulunduğunu varsayar. Bu varsayım her zaman doğru mu? Hayır.

Diyelim ki 1. öğeniz 100dp yüksekliğinde ve 2. öğeniz yalnızca 50dp, tahmin edin 3. öğeniz solda veya sağda nerede?


0

GridLayoutManager ve HeaderView ile RecyclerView için böyle yaptım .

Aşağıdaki kodda her öğe arasında bir 4dp boşluk ayarladım (her bir öğenin etrafında 2dp ve tüm geri dönüşüm görünümü etrafında 2dp dolgu).

Layout.xml

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

fragmanı / aktivite

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

İçin yapılan https://stackoverflow.com/a/29905000/1649371 Çözelti çalışma (yukarıda) aşağıdaki yöntemler (ve daha sonraki tüm çağrıları) değiştirmek zorunda

@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

Liste arasında ızgaraya geçiş yapan bir geçiş anahtarınız varsa, recyclerView.removeItemDecoration()yeni bir Öğe dekorasyonu ayarlamadan önce aramayı unutmayın . Değilse, aralık için yeni hesaplamalar yanlış olur.


Böyle bir şey.

        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

Eğer kullanıyorsanız Başlığını ile GridLayoutManager yazılmış bu kodu kullanın KOTLIN ağları arasındaki aralığı için:

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

Ve ItemDecorationşu recyclerviewşekilde geç:

mIssueGridView.addItemDecoration(SpacesItemDecoration(10))

0

Çocuklar için CardView kullanırken öğeler arasında boşluk ile sorun app: cardUseCompatPadding true olarak ayarlanarak çözülebilir.

Daha büyük kenar boşlukları için öğe yüksekliğini büyütün. CardElevation isteğe bağlıdır (varsayılan değeri kullanın).

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

-1

teşekkürler edwardaa'nın cevabı https://stackoverflow.com/a/30701422/2227031

Dikkat edilmesi gereken bir diğer nokta şudur:

toplam boşluk ve toplam itemWidth ekran genişliğine eşit değilse, itemWidth değerini de ayarlamanız gerekir; örneğin, onBindViewHolder yöntemindeki bağdaştırıcıda

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'nın büyük cevabına dayanarak yaptığım bir Kotlin versiyonu

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
  }

}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.