Android agrega espacio debajo del último elemento en la vista de reciclaje con gridlayoutmanager

84

Estoy tratando de agregar espacio debajo de la última fila del elemento RecyclerViewcon GridLayoutManager. Usé personalizado ItemDecorationpara este propósito con relleno inferior cuando su último elemento es el siguiente:

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

Pero el problema con este método es que estropeó las alturas de los elementos en la cuadrícula en la última fila. Supongo que eso GridLayoutManagercambia las alturas de los elementos según el espaciado dejado. ¿Cuál es la forma correcta de lograrlo?

Esto funcionará correctamente para a LinearLayoutManager. Solo en caso de que sea GridLayoutManagerproblemático.

Es muy útil en caso de que tenga una FABparte inferior y necesite elementos en la última fila para desplazarse hacia arriba FABpara que puedan ser visibles.

pratsJ
fuente

Respuestas:

11

La solución a este problema radica en invalidar SpanSizeLookup de GridLayoutManager.

Debe realizar cambios en GridlayoutManager en la Actividad o Fragmento donde está inflando 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);
}
Dhruv Pancholi
fuente
436

Simplemente agregue un relleno y configure android:clipToPadding="false"

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

¡Gracias a esta maravillosa respuesta !

vedant
fuente
2
Esto lo hizo por mí. Solución rápida y fácil cuando necesita espaciar los artículos por igual en el reciclador. Gracias.
Empty2k12
3
¡Esto era exactamente lo que estaba buscando! ¡Gracias!
Mariano Zorrilla
1
Gran y sencilla solución. ¡Gracias!
Mikhail
1
Esto es maravilloso, pero estropea los bordes que se desvanecen, por ejemplo, android: requireFadingEdge = "vertical" o recyclerView.setVerticalFadingEdgeEnabled (true);
Stephan Henningsen
6
Esto no extiende la barra de desplazamiento de la vista del reciclador hasta la parte inferior. Editar: para evitar este complementoandroid:scrollbarStyle="outsideOverlay"
Sebastian
8

Debe usar Decoración en vista de reciclador para el margen inferior en el caso del último artículo solamente

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
fuente
6

Tuve un problema similar y respondí a otro hilo en el desbordamiento de pila. Para ayudar a otros que lleguen a esta página, la volveré a publicar aquí.
Después de leer todas las respuestas de los demás, encontré que los cambios en el diseño xml para recyclerview funcionaron para mi vista de reciclador como se esperaba:

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

El diseño completo se ve así:

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

Para ver el efecto del antes y el después, consulte el enlace en androidblog.us: Agregar espacio al final de Android Recylerview Hágame
saber cómo funciona para usted.

David

us_david
fuente
3

Puede utilizar el siguiente código para detectar la primera y la última fila en una vista de cuadrícula y establecer las compensaciones superior e inferior en consecuencia.

@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
fuente
2

Lo que puede hacer es agregar un pie de página vacío a su vista de reciclaje. Su relleno será del tamaño de su pie de página.

@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
fuente
Esta es una buena opcion. Sin embargo, mi problema es que ya estoy usando un ItemDecoration personalizado que funciona en función de las posiciones de los elementos en la vista de reciclaje y decide dónde poner qué espaciado y divisor, etc. Ahora, si agrego este espaciado como elemento adicional, mi ItemDecoration lo cuenta como un elemento y agrega divisor al espaciado también. Entonces estaba pensando si alguien ha intentado usar un ItemDecoration personalizado para resolver el problema.
pratsJ
Además, su método funcionará bien en el caso de LinearLayoutManager o en el caso de GridLayoutManager si la fila inferior de la cuadrícula está llena. De lo contrario, el espacio entrará en la cuadrícula en el espacio del elemento vacío y no en la parte inferior de toda la cuadrícula.
pratsJ
0

Con cosas como esta, se recomienda resolver usando ItemDecoration, ya que están diseñadas para eso.

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

Copié una edición de mi respuesta original aquí, que en realidad es para espacios iguales, pero es el mismo concepto.

Pirdad Sakhizada
fuente
0

Puede tomar DividerItemDecoration.java como ejemplo del código fuente y reemplazar

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

con

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

en drawVertical () y drawHorizontal ()

Kamir
fuente