Spaltenabstand für Android Recyclerview GridLayoutManager


250

Wie legen Sie den Spaltenabstand mit einem RecyclerView mithilfe eines GridLayoutManager fest? Das Einstellen des Randes / der Polsterung in meinem Layout hat keine Auswirkung.


Haben Sie versucht, Unterklassen GridLayoutManagerund Überschreibungen generateDefaultLayoutParams()und Verwandte?
CommonsWare

Ich habe nicht, ich dachte, es hätte eine Methode gegeben, die ich einfach nicht gesehen habe, um den Abstand auf die gleiche Gitteransicht einzustellen. Ich werde das versuchen
hitch.united


Antworten:


348

RecyclerViews unterstützen das Konzept von ItemDecoration : spezielle Offsets und Zeichnen um jedes Element. Wie in dieser Antwort zu sehen , können Sie verwenden

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

Dann fügen Sie es über hinzu

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

3
Verwenden Sie 'outRect.top = space' und entfernen Sie 'outRect.bottom', wenn Sie sich nicht mit dem 'if for the first position' herumschlagen möchten. ; -]
Amio.io

2
@zatziky - yep, wenn Sie bereits obere und untere Polsterung als Teil Ihrer RecyclerView(und Verwendung clipToPadding="false") verwenden, können Sie die Dinge leicht umstrukturieren. Wenn Sie dies jedoch nicht tun, verschieben Sie den if-Check nur auf das letzte Mal (da Sie immer noch die untere Polsterung des letzten Elements wünschen).
Ianhanniballake

18
@ianhanniballake Während dies bei Verwendung eines Layout-Managers mit nur einem Bereich funktioniert, schlägt dies für den Layout-Manager mit mehreren Bereichen fehl.
Avinash R

4
Wenn Sie dies mit GridLayoutManager auf diese Weise tun, bleiben alle ersten Elemente der 2., 3. ... n-ten Spalte oben (da kein Leerzeichen vorhanden ist). Ich denke, es ist besser, .top = space / 2 und .bottom = space / 2 zu machen.
Jaroslaw

1
Diese Antwort beantwortet nicht die ursprüngliche Frage. Der Schwerpunkt der Frage liegt auf GridLayoutManager . Die Antwort funktioniert nicht bei mehrspaltigen /
Zeilenlayouts

428

Der folgende Code funktioniert gut und jede Spalte hat die gleiche Breite:

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

Verwendung

1. keine Kante

Geben Sie hier die Bildbeschreibung ein

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

2. mit Kante

Geben Sie hier die Bildbeschreibung ein

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

12
Funktioniert, es sei denn, Sie haben Elemente mit verschiedenen Bereichen, z. B. Überschriften.
Matthew

8
Gute Antwort; Ein Tipp: Der Abstand ist in px (Sie können also dp in px mit Math.round konvertieren (someDpValue * getResources (). getDisplayMetrics (). Dichte))
Kevin Lee

1
Es funktioniert gut, aber ich habe ein Problem. Ich verwende GridLayoutManager mit spanCount von 2 (Standard), aber der Benutzer kann spanCount ändern. Wenn sich spanCount von der Standardposition ändert, ist an einigen Positionen eine deutlich sichtbarere Auffüllung zu sehen, z. B. wenn spanCount 3 als Polsterung / Rand auf 2,3 8,9 12,13 usw.
Haris Qureshi

2
Funktioniert super! Ich habe jedoch einige Probleme mit StaggeredGridLayoutManager. Die horizontalen Ränder von imgur.com/XVutH5u unterscheiden sich manchmal.
Ufkoku

2
Dies funktioniert nicht, wenn das Layout RTL ist (für 2 Spalten oder mehr). Derzeit ist der Abstand zwischen den Spalten im RTL-Modus nicht richtig. Sie müssen ersetzen: outRect.left durch outRect.right, wenn es in rtl ist.
Masoud Mohammadi

83

Das Folgende ist die schrittweise einfache Lösung, wenn Sie den gleichen Abstand um Artikel und gleiche Artikelgrößen wünschen.

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

Implementierung

In Ihrem Quellcode sollte das Hinzufügen ItemOffsetDecorationzu Ihrem RecyclerView. Artikelversatzwert die halbe Größe des tatsächlichen Werts haben, den Sie als Leerzeichen zwischen Elementen hinzufügen möchten.

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

Legen Sie außerdem den Elementversatzwert als Auffüllung für seine fest RecyclerViewund geben Sie ihn an 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"/>

Perfekt, einfach und effektiv.
B. Shruti

28

Versuche dies. Es wird sich um gleichmäßige Abstände kümmern. Funktioniert sowohl mit List, Grid als auch StaggeredGrid.

Bearbeitet

Der aktualisierte Code sollte die meisten Eckfälle mit Bereichen, Ausrichtung usw. behandeln. Beachten Sie, dass bei Verwendung von setSpanSizeLookup () mit GridLayoutManager aus Leistungsgründen das Festlegen von setSpanIndexCacheEnabled () empfohlen wird.

Beachten Sie, dass es bei StaggeredGrid anscheinend einen Fehler gibt, bei dem der Index der Kinder verrückt und schwer zu verfolgen ist, sodass der folgende Code möglicherweise nicht sehr gut mit StaggeredGridLayoutManager funktioniert.

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

Ich hoffe es hilft.


1
Ich habe gleich nach der ersten Zeile eine doppelte Spannweite. Dies geschieht, weil parent.getChildCount () 1 für das erste Element, 2 für das zweite usw. zurückgibt. Daher schlage ich vor, Elementen am oberen Rand Platz hinzuzufügen: outRect.top = childIndex <spanCount? spacingInPixels: 0; Fügen Sie für jedes Element den unteren Bereich hinzu: outRect.bottom = spacingInPixels;
IvanP

Zum Zeitpunkt des Bildlaufs in RecyclerView hat sich der Abstand geändert.
Yugesh

3
Ich denke, parent.getChildCount () sollte in "parent.getLayoutManager (). GetItemCount ()" geändert werden. Außerdem muss die Funktion isBottomEdge in "return childIndex> = childCount - spanCount + spanIndex" geändert werden. Nachdem ich diese geändert hatte, bekam ich den gleichen Abstand. Beachten Sie jedoch, dass diese Lösung mir nicht die gleichen Artikelgrößen bietet, wenn die Anzahl der Bereiche größer als 2 ist, da der Versatzwert je nach Position unterschiedlich ist.
yqritc

1
@yqritc danke für nocticing parent.getChildCount (). Ich habe meine Antwort aktualisiert, um den parent.getLayoutManager () zu verwenden. GetItemCount ()
Pirdad Sakhizada

2
Dies funktionierte auch mit variablen Spannweiten, Glückwünschen und vielen Dank sofort!
Pierre-Luc Paour

21

Der folgende Code behandelt StaggeredGridLayoutManager, GridLayoutManager und 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;
    }
}

Dann benutze es

mRecyclerView.addItemDecoration(new SpacesItemDecoration(mMargin));

1
Dies ist die einfachste. Eine wichtige Sache ist, dass Sie auch die Auffüllung zum übergeordneten Element in der XML hinzufügen müssen. In meinem Fall funktioniert es so. Vielen Dank.
Samir

Das SpaceItemDecorationfügt die Auffüllung tatsächlich dem übergeordneten Element hinzu (die Recycler-Ansicht).
Mark Hetherington

Nur die halfSpacePolsterung erschien (auf der rechten Seite), als ich die Polsterung nicht auf das übergeordnete Element in XML gesetzt hatte
Samir

Es fehlte nur auf der rechten Seite? Es kann sein, dass Sie bereits in der XML den linken Speicherplatz als leftPadding auf der linken Seite festgelegt haben und dieser Code nur prüft, ob der linke Abstand in der RecyclerView festgelegt ist oder nicht.
Mark Hetherington

Nun, ich habe keine Polsterung in der XML eingestellt.
Samir

11

Hier ist eine Lösung, die kein "spanCount" (Anzahl der Spalten) erfordert. Ich verwende sie, weil ich GridAutofitLayoutManager verwende (berechnet die Anzahl der Spalten entsprechend der erforderlichen Zellengröße).

(Beachten Sie, dass dies nur mit GridLayoutManager funktioniert. )

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

        }

    }
}

Hier ist der GridAutofitLayoutManager, an dem sich jeder interessiert:

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

Schließlich:

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

Hallo. Das funktioniert erstaunlich, aber ich verwende einen Header mit Ihrer Lösung. Können Sie vorschlagen, wie ein Header in voller Breite erreicht werden kann?
Ajeet

Bitte überprüfen Sie die Position mit dem Layout-Manager wie folgt layoutManager.getPosition(view): Überprüfen Sie anschließend, ob die Position Null ist, die Ihre Überschrift sein wird. Auf diese Weise können Sie auch an jeder gewünschten Position eine weitere Überschrift hinzufügen :)
Mina Samir

9

Es gibt nur eine einfache Lösung, an die Sie sich erinnern und die Sie bei Bedarf implementieren können. Keine Bugs, keine verrückten Berechnungen. Fügen Sie dem Karten- / Elementlayout einen Rand hinzu und fügen Sie dem RecyclerView dieselbe Größe wie dem Auffüllen hinzu:

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

AKTUALISIEREN: Geben Sie hier die Bildbeschreibung ein


Es funktioniert gut! Könnten Sie bitte näher auf das Thema eingehen?
Elyar Abad

Ich danke dir sehr! Ich suchte nach einem technischen Grund, warum eine solche Zusammenarbeit zwischen der Polsterung des Recyclers und der Marge des Artikels erforderlich ist. Wie auch immer, du hast so viel für mich getan. . .
Elyar Abad

7

Wenn Sie die Größe Ihres Artikels auf allen Geräten FESTLEGEN möchten RecyclerView. Sie können dies tun

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>

dimension.xml

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

Aktivität

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

Geben Sie hier die Bildbeschreibung ein Geben Sie hier die Bildbeschreibung ein


Funktioniert es je nach Bildschirmgröße? Bedeutet dies, dass es auf dem 5-Zoll-Bildschirm angezeigt wird und auch auf anderen Bildschirmgrößen gleich aussieht?
Sunil

Die Größe des Artikels wird festgelegt, aber der Abstand zwischen den Artikeln kann unterschiedlich sein. Sie können auch 2 Bilder oben sehen, um sie zu verstehen
Phan Van Linh

Sie sehen anders auf verschiedene Bildschirm sizes.any Art und Weise aber die Arbeit danke
Sunil

6

Die ausgewählte Antwort ist nahezu perfekt, aber je nach Platz kann die Breite der Elemente nicht gleich sein. (In meinem Fall war es kritisch). Also habe ich diesen Code erhalten, der den Platz ein wenig vergrößert, sodass alle Elemente die gleiche Breite haben.

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

}

Ich würde folgende Zeilen hinzufügen, wenn jemand wie ich GridLayoutManager mit spanCount = 1 columnCount == 1 -> { outRect.left = space outRect.right = space }
massivemadness

5

Der von @edwardaa bereitgestellte Code wurde kopiert und ich mache es perfekt, um RTL zu unterstützen:

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

In den obigen Antworten wurden Möglichkeiten zum Festlegen der Margin-Behandlung für GridLayoutManager und LinearLayoutManager erläutert.

Für StaggeredGridLayoutManager lautet die Antwort von Pirdad Sakhizada jedoch: "Mit StaggeredGridLayoutManager funktioniert es möglicherweise nicht sehr gut." Es sollte das Problem mit IndexOfSpan sein.

Sie können es auf diese Weise erhalten:

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

Ein bisschen anders als die Antwort von edwardaa besteht der Unterschied darin, wie die Spalte bestimmt wird, da in Fällen wie Elementen mit unterschiedlichen Höhen die Spalte nicht einfach durch% bestimmt werden kann 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

Hier ist meine Modifikation SpacesItemDecoration, die numOfColums und Leerzeichen oben, unten, links und rechts gleichermaßen einnehmen kann .

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

und verwenden Sie den folgenden Code für Ihre Logik.

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

2

Es gibt eine sehr einfache und dennoch flexible Lösung für dieses Problem, bei der nur XML verwendet wird, das auf jedem LayoutManager funktioniert.

Angenommen, Sie möchten einen gleichen Abstand von X (z. B. 8 dp).

  1. Wickeln Sie Ihr CardView-Element in ein anderes Layout

  2. Geben Sie dem äußeren Layout eine Polsterung von X / 2 (4dp).

  3. Machen Sie den äußeren Layouthintergrund transparent

...

<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. Geben Sie Ihrem RecyclerView eine Auffüllung von X / 2 (4dp)

...

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

und das ist es. Sie haben einen perfekten Abstand von X (8dp).


2

Für diejenigen, die Probleme mit staggeredLayoutManager haben (wie https://imgur.com/XVutH5u )

Methoden von recyclerView:

getChildAdapterPosition(view)
getChildLayoutPosition(view)

Geben Sie manchmal -1 als Index zurück, damit beim Festlegen von itemDecor Probleme auftreten können. Meine Lösung besteht darin, die veraltete ItemDecoration-Methode zu überschreiben:

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

anstelle des Neulings:

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

so was:

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

Scheint bisher für mich zu arbeiten :)


Großartige Antwort Mann! Funktioniert in allen Fällen, einschließlich des nicht symmetrischen "normalen" GridLayoutManager, in dem sich zwischen Elementen ein Headerelement befindet. Vielen Dank!
Shirane85

2

Die Antworten auf diese Frage scheinen komplexer zu sein, als sie sein sollten. Hier ist meine Meinung dazu.

Angenommen, Sie möchten einen Abstand von 1 dp zwischen Rasterelementen. Mach Folgendes:

  1. Fügen Sie eine Polsterung von 0.5dp zu jedem Artikel
  2. Fügen Sie eine Polsterung von -0.5dp an die Abstand dp hinzu
  3. Das ist es! :) :)

1

Dies funktioniert auch RecyclerViewmit dem Header.

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

Was ist headerNum?
Tim Kranen

1

Die Antwort von yqritc hat bei mir perfekt funktioniert. Ich habe jedoch Kotlin verwendet, daher ist hier das Äquivalent dazu.

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

alles andere ist das gleiche.


1

Seien Sie vorsichtig, für StaggeredGridLayoutManager- Benutzer. Viele Antworten hier, einschließlich der am häufigsten bewerteten, berechnen die Elementspalte mit dem folgenden Code:

int column = position % spanCount

Dies setzt voraus, dass sich die Elemente 1st / 3rd / 5th / .. immer auf der linken Seite und die Elemente 2nd / 4th / 6th / .. immer auf der rechten Seite befinden. Ist diese Annahme immer wahr? Nein.

Angenommen, Ihr 1. Artikel ist 100 dp hoch und der 2. nur 50 dp. Ratet mal, wo sich Ihr 3. Artikel befindet, links oder rechts?


0

So habe ich es für meinen RecyclerView mit GridLayoutManager und HeaderView gemacht .

Im folgenden Code habe ich einen 4-dp-Abstand zwischen jedem Element festgelegt (2 dp um jedes einzelne Element und 2 dp-Auffüllung um die gesamte Recycling-Ansicht).

layout.xml

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

Fragment / Aktivität

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

Damit die Lösung von https://stackoverflow.com/a/29905000/1649371 (oben) funktioniert, musste ich die folgenden Methoden (und alle nachfolgenden Aufrufe) ändern.

@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

Dieser Link hat für mich in allen Situationen funktioniert, in denen Sie dies versuchen können.


0

Wenn Sie einen Kippschalter haben, der zwischen Liste und Raster umschaltet, vergessen Sie nicht, den Anruf zu tätigen, recyclerView.removeItemDecoration()bevor Sie eine neue Objektdekoration einstellen. Wenn nicht, wären die neuen Berechnungen für den Abstand falsch.


Etwas wie das.

        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

Wenn Sie Header mit GridLayoutManager verwenden, verwenden Sie diesen in Kotlin geschriebenen Code für den Abstand zwischen den Gittern:

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

Und ItemDecorationweiter recyclerviewals

mIssueGridView.addItemDecoration(SpacesItemDecoration(10))

0

Bei Verwendung von CardView für Kinder können Probleme mit Leerzeichen zwischen Elementen behoben werden, indem app: cardUseCompatPadding auf true gesetzt wird.

Für größere Ränder vergrößern Sie die Elementhöhe. CardElevation ist optional (Standardwert verwenden).

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

-1

danke edwardaas antwort https://stackoverflow.com/a/30701422/2227031

Ein weiterer zu beachtender Punkt ist:

Wenn der Gesamtabstand und die Gesamtgröße des Elements nicht der Bildschirmbreite entsprechen, müssen Sie auch die Breite des Elements anpassen, z. B. für die Methode adapter onBindViewHolder

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

Eine Kotlin-Version, die ich basierend auf der großartigen Antwort von edwardaa gemacht habe

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
  }

}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.