Android fügt mit gridlayoutmanager einen Abstand unter dem letzten Element in der Recyclingansicht hinzu

82

Ich versuche, einen Abstand unterhalb der letzten Elementzeile RecyclerViewmit hinzuzufügen GridLayoutManager. Ich habe ItemDecorationfür diesen Zweck benutzerdefinierte mit Bodenpolsterung verwendet, wenn das letzte Element wie folgt lautet:

public class SpaceItemDecoration extends RecyclerView.ItemDecoration {
private int space;
private int bottomSpace = 0;

public SpaceItemDecoration(int space, int bottomSpace) {
    this.space = space;
    this.bottomSpace = bottomSpace;
}

public SpaceItemDecoration(int space) {
    this.space = space;
    this.bottomSpace = 0;
}

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

    int childCount = parent.getChildCount();
    final int itemPosition = parent.getChildAdapterPosition(view);
    final int itemCount = state.getItemCount();

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

    if (itemCount > 0 && itemPosition == itemCount - 1) {
        outRect.bottom = bottomSpace;
    }
}
}

Das Problem bei dieser Methode ist jedoch, dass die Elementhöhen im Raster in der letzten Zeile durcheinander gebracht wurden. Ich vermute, dass GridLayoutManagersich die Höhen für Elemente basierend auf dem Abstand nach links ändern. Was ist der richtige Weg, um dies zu erreichen?

Dies funktioniert korrekt für a LinearLayoutManager. Nur für den Fall, dass es GridLayoutManagerproblematisch ist.

Dies ist sehr nützlich, wenn Sie ein FABIn unten haben und Elemente in der letzten Zeile benötigen, um nach oben zu scrollen, FABdamit sie sichtbar sind.

pratsJ
quelle

Antworten:

11

Die Lösung für dieses Problem besteht darin, das SpanSizeLookup von GridLayoutManager zu überschreiben.

Sie müssen Änderungen am GridlayoutManager in der Aktivität oder im Fragment vornehmen, in der Sie die RecylerView aufblasen.

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //your code 
    recyclerView.addItemDecoration(new PhotoGridMarginDecoration(context));

    // SPAN_COUNT is the number of columns in the Grid View
    GridLayoutManager gridLayoutManager = new GridLayoutManager(context, SPAN_COUNT);

    // With the help of this method you can set span for every type of view
    gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
        @Override
        public int getSpanSize(int position) {
            if (list.get(position).getType() == TYPE_HEADER) {
                // Will consume the whole width
                return gridLayoutManager.getSpanCount();
            } else if (list.get(position).getType() == TYPE_CONTENT) {
                // will consume only one part of the SPAN_COUNT
                return 1;
            } else if(list.get(position).getType() == TYPE_FOOTER) {
                // Will consume the whole width
                // Will take care of spaces to be left,
                // if the number of views in a row is not equal to 4
                return gridLayoutManager.getSpanCount();
            }
            return gridLayoutManager.getSpanCount();
        }
    });
    recyclerView.setLayoutManager(gridLayoutManager);
}
Dhruv Pancholi
quelle
427

Einfach eine Polsterung hinzufügen und einstellen android:clipToPadding="false"

<RecyclerView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="8dp"
    android:clipToPadding="false" />

Dank dieser wunderbaren Antwort !

vedant
quelle
2
Das hat es für mich getan. Schnelle und einfache Lösung, wenn Sie Gegenstände gleichmäßig im Recycler platzieren müssen. Vielen Dank.
Empty2k12
3
Genau das habe ich gesucht! Vielen Dank!
Mariano Zorrilla
1
Tolle und einfache Lösung. Vielen Dank!
Mikhail
1
Das ist wunderbar, aber es bringt verblassende Kanten durcheinander, z. B. android: requireFadingEdge = "vertikal" oder recyclerView.setVerticalFadingEdgeEnabled (true);
Stephan Henningsen
6
Dadurch wird die Bildlaufleiste der Recycler-Ansicht nicht ganz nach unten erweitert. Bearbeiten: um dieses Hinzufügen zu verhindernandroid:scrollbarStyle="outsideOverlay"
Sebastian
7

Sollte Dekoration in der Recycler-Ansicht nur für den unteren Rand für den unteren Rand verwenden

recyclerView.addItemDecoration(MemberItemDecoration())

public class MemberItemDecoration extends RecyclerView.ItemDecoration {

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        // only for the last one
        if (parent.getChildAdapterPosition(view) == parent.getAdapter().getItemCount() - 1) {
            outRect.bottom = 50/* set your margin here */;
        }
    }
}
Dinesh Bagvan
quelle
4

Ich hatte ein ähnliches Problem und antwortete auf einen anderen Thread im Stapelüberlauf. Um anderen zu helfen, die auf dieser Seite landen, werde ich sie hier erneut veröffentlichen.
Nachdem ich alle anderen Antworten gelesen hatte und festgestellt hatte, dass die Änderungen im Layout-XML für die Recycling-Ansicht erwartungsgemäß für meine Recycling-Ansicht funktionierten:

        android:paddingBottom="127px"
        android:clipToPadding="false"
        android:scrollbarStyle="outsideOverlay"  

Das komplette Layout sieht aus wie:

<android.support.v7.widget.RecyclerView
        android:id="@+id/library_list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="160px"
        android:layout_marginEnd="160px"
        tools:listitem="@layout/library_list_item" />  

Informationen zu den Auswirkungen von Vorher und Nachher finden Sie unter dem Link auf androidblog.us: Hinzufügen von Speicherplatz zum Ende von Android Recylerview
Lassen Sie mich wissen, wie es bei Ihnen funktioniert.

David

us_david
quelle
3

Mit dem folgenden Code können Sie die erste und letzte Zeile in einer Rasteransicht erkennen und die oberen und unteren Offsets entsprechend festlegen.

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
    LayoutParams params = (LayoutParams) view.getLayoutParams();
    int pos = params.getViewLayoutPosition();
    int spanCount = mGridLayoutManager.getSpanCount();

    boolean isFirstRow = pos < spanCount;
    boolean isLastRow = state.getItemCount() - 1 - pos < spanCount;

    if (isFirstRow) {
      outRect.top = top offset value here
    }

    if (isLastRow) {
      outRect.bottom = bottom offset value here
    }
}

// you also need to keep reference to GridLayoutManager to know the span count
private final GridLayoutManager mGridLayoutManager;
sergej shafarenka
quelle
2

Sie können Ihrer Recycling-Ansicht eine leere Fußzeile hinzufügen. Ihre Polsterung entspricht der Größe Ihrer Fußzeile.

@Override
public Holder onCreateViewHolder( ViewGroup parent, int viewType) {
    if (viewType == FOOTER) {
        return new FooterHolder();
    }
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
    return new Holder(view);
}

@Override
public void onBindViewHolder(final Holder holder, final int position) {
    //if footer
    if (position == items.getSize() - 1) {
    //do nothing
        return;
    }
    //do regular object bindding

}

@Override
public int getItemViewType(int position) {
    return (position == items.getSize() - 1) ? FOOTER : ITEM_VIEW_TYPE_ITEM;
}

@Override
public int getItemCount() {
    //add one for the footer
    return items.size() + 1;
}
Nicolas
quelle
Dies ist eine gute Option. Mein Problem dabei ist jedoch, dass ich bereits eine benutzerdefinierte ItemDecoration verwende, die basierend auf den Artikelpositionen in der Recyclingansicht funktioniert und entscheidet, wo welcher Abstand und Teiler usw. platziert werden soll. Wenn ich diesen Abstand nun als zusätzliches Element hinzufüge, zählt meine ItemDecoration ihn als Element und Fügt dem Abstand ebenfalls einen Teiler hinzu. Also habe ich mir überlegt, ob jemand versucht hat, das Problem mit einer benutzerdefinierten ItemDecoration zu lösen.
PratsJ
Auch Ihre Methode funktioniert gut im Fall von LinearLayoutManager oder im Fall von GridLayoutManager, wenn die untere Reihe des Gitters voll ist. Andernfalls wird der Raum im leeren Elementraum und nicht am unteren Rand des gesamten Gitters in das Raster verschoben.
PratsJ
0

Bei solchen Dingen wird empfohlen, die ItemDecoration so zu lösen, wie sie dafür vorgesehen ist.

public class ListSpacingDecoration extends RecyclerView.ItemDecoration {

  private static final int VERTICAL = OrientationHelper.VERTICAL;

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


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

    spacing = context.getResources().getDimensionPixelSize(spacingDimen);
  }

  public ListSpacingDecoration(int spacingPx) {

    spacing = spacingPx;
  }

  @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) {

    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 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 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 habe hier eine bearbeitete aus meiner ursprünglichen Antwort kopiert , die eigentlich für gleichen Abstand ist, aber es ist das gleiche Konzept.

Pirdad Sakhizada
quelle
0

Sie können DividerItemDecoration.java als Beispiel aus dem Quellcode nehmen und ersetzen

for (int i = 0; i < childCount; i++)

mit

for (int i = 0; i < childCount - 1; i++)

in drawVertical () und drawHorizontal ()

Kamir
quelle