Android aggiunge la spaziatura sotto l'ultimo elemento in recyclerview con gridlayoutmanager


85

Sto cercando di aggiungere una spaziatura sotto l'ultima riga dell'elemento RecyclerViewcon GridLayoutManager. Ho usato personalizzato ItemDecorationper questo scopo con imbottitura inferiore quando il suo ultimo elemento come segue:

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

Ma il problema con questo metodo è che ha incasinato le altezze degli elementi nella griglia nell'ultima riga. Immagino che GridLayoutManagercambi le altezze degli elementi in base alla spaziatura a sinistra. Qual è il modo corretto per ottenere questo risultato?

Questo funzionerà correttamente per un file LinearLayoutManager. Solo in caso di GridLayoutManagerproblemi.

È molto utile nel caso in cui tu abbia un FABin fondo e hai bisogno di elementi nell'ultima riga per scorrere sopra in FABmodo che possano essere visibili.

Risposte:


11

La soluzione a questo problema risiede nel sovrascrivere lo SpanSizeLookup di GridLayoutManager.

È necessario apportare modifiche a GridlayoutManager nell'attività o nel frammento in cui si sta gonfiando RecylerView.

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

439

Basta aggiungere un'imbottitura e impostare android:clipToPadding="false"

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

Grazie a questa meravigliosa risposta !


2
Questo ha fatto per me. Soluzione rapida e semplice quando è necessario distribuire equamente gli articoli nel riciclatore. Grazie.
Vuoto2k12

3
Questo era esattamente quello che stavo cercando! Grazie!
Mariano Zorrilla

1
Ottima e semplice soluzione. Grazie!
Mikhail

1
Questo è meraviglioso, ma incasina i bordi in dissolvenza, ad esempio android: requireFadingEdge = "vertical" o recyclerView.setVerticalFadingEdgeEnabled (true);
Stephan Henningsen

6
Ciò non estende la barra di scorrimento della visualizzazione del riciclatore fino in fondo. Modifica: per evitare questa aggiuntaandroid:scrollbarStyle="outsideOverlay"
Sebastian

8

Utilizzare la decorazione nella visualizzazione riciclatore per il margine inferiore solo nel caso dell'ultimo elemento

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

6

Ho avuto un problema simile e ho risposto a un altro thread in overflow dello stack. Per aiutare gli altri che atterrano su questa pagina, la ripubblicherò qui.
Dopo aver letto tutte le altre risposte e ho scoperto che le modifiche al layout xml per recyclerview hanno funzionato per la mia visualizzazione riciclatore come previsto:

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

Il layout completo si presenta come:

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

Per l'effetto del prima e del dopo, vedere il collegamento su androidblog.us: Aggiunta di spazio alla fine di Android Recylerview
Fammi sapere come funziona per te.

David


3

È possibile utilizzare il codice seguente per rilevare la prima e l'ultima riga in una visualizzazione griglia e impostare gli offset superiore e inferiore in modo corrispondente.

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

2

Quello che puoi fare è aggiungere un piè di pagina vuoto al tuo recyclerview. La tua imbottitura avrà le dimensioni del tuo piè di pagina.

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

Questa è una buona opzione. Tuttavia il mio problema è che sto già utilizzando una ItemDecoration personalizzata che funziona in base alle posizioni degli articoli in Recyclerview e decide dove mettere quale spaziatura e divisore ecc. Ora se aggiungo questa spaziatura come elemento aggiuntivo, la mia ItemDecoration la conta come un elemento e aggiunge anche un divisore alla spaziatura. Quindi stavo pensando se qualcuno ha provato a utilizzare una ItemDecoration personalizzata per risolvere il problema.
pratsJ

Anche il tuo metodo funzionerà bene nel caso di LinearLayoutManager o nel caso di GridLayoutManager se la riga inferiore della griglia è piena. Altrimenti lo spazio andrà nella griglia nello spazio degli elementi vuoti e non nella parte inferiore dell'intera griglia.
pratsJ

0

Con cose come questa, si consiglia di risolvere utilizzando ItemDecoration poiché sono pensate per questo.

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

Ho copiato un modificati dalla mia risposta originale qui che in realtà è per la parità di distanza, ma è lo stesso concetto.


0

Potresti prendere DividerItemDecoration.java come esempio dal codice sorgente e sostituire

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

con

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

in drawVertical () e drawHorizontal ()

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.