web-dev-qa-db-fra.com

Comment mettre en place un pied de page collant dans recyclerview

J'ai RecyclerView et j'ai besoin du comportement suivant:

  • s'il y a beaucoup d'éléments (plus que l'écran convient) - footer est le dernier élément
  • si peu d'articles/aucun article - le pied de page est situé au bas de l'écran

S'il vous plaît aviser comment puis-je implémenter ce problème.

19
resource8218

Vous pouvez utiliser RecyclerView.ItemDecoration pour implémenter ce problème.

public class StickyFooterItemDecoration extends RecyclerView.ItemDecoration {

    /**
     * Top offset to completely hide footer from the screen and therefore avoid noticeable blink during changing position of the footer.
     */
    private static final int OFF_SCREEN_OFFSET = 5000;

    @Override
    public void getItemOffsets(Rect outRect, final View view, final RecyclerView parent, RecyclerView.State state) {
        int adapterItemCount = parent.getAdapter().getItemCount();
        if (isFooter(parent, view, adapterItemCount)) {
            //For the first time, each view doesn't contain any parameters related to its size,
            //hence we can't calculate the appropriate offset.
            //In this case, set a big top offset and notify adapter to update footer one more time.
            //Also, we shouldn't do it if footer became visible after scrolling.
            if (view.getHeight() == 0 && state.didStructureChange()) {
                hideFooterAndUpdate(outRect, view, parent);
            } else {
                outRect.set(0, calculateTopOffset(parent, view, adapterItemCount), 0, 0);
            }
        }
    }

    private void hideFooterAndUpdate(Rect outRect, final View footerView, final RecyclerView parent) {
        outRect.set(0, OFF_SCREEN_OFFSET, 0, 0);
        footerView.post(new Runnable() {
            @Override
            public void run() {
                parent.getAdapter().notifyDataSetChanged();
            }
        });
    }

    private int calculateTopOffset(RecyclerView parent, View footerView, int itemCount) {
        int topOffset = parent.getHeight() - visibleChildsHeightWithFooter(parent, footerView, itemCount);
        return topOffset < 0 ? 0 : topOffset;
    }

    private int visibleChildsHeightWithFooter(RecyclerView parent, View footerView, int itemCount) {
        int totalHeight = 0;
        //In the case of dynamic content when adding or removing are possible itemCount from the adapter is reliable,
        //but when the screen can fit fewer items than in adapter, getChildCount() from RecyclerView should be used.
        int onScreenItemCount = Math.min(parent.getChildCount(), itemCount);
        for (int i = 0; i < onScreenItemCount - 1; i++) {
            totalHeight += parent.getChildAt(i).getHeight();
        }
        return totalHeight + footerView.getHeight();
    }

    private boolean isFooter(RecyclerView parent, View view, int itemCount) {
        return parent.getChildAdapterPosition(view) == itemCount - 1;
    }
}

Assurez-vous de définir match_parent pour la hauteur RecyclerView.

Veuillez consulter l'exemple d'application https://github.com/JohnKuper/recyclerview-sticky-footer et comment cela fonctionne http://sendvid.com/nbpj0806

L'énorme inconvénient de cette solution est qu'elle ne fonctionne correctement qu'après notifyDataSetChanged () tout au long d'une application (et non à l'intérieur de la décoration). Avec des notifications plus spécifiques, cela ne fonctionnera pas correctement et pour les prendre en charge, cela nécessite un peu plus de logique. Vous pouvez également obtenir des informations de la bibliothèque recyclerview-stickyheaders by eowise et améliorer cette solution.

10
Dmitry Korobeinikov

Improviser sur Dmitriy Korobeynikov et résoudre le problème de l'appel de l'ensemble de données "notify" changé

public class StickyFooterItemDecoration extends RecyclerView.ItemDecoration {

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

    int position = parent.getChildAdapterPosition(view);
    int adapterItemCount = parent.getAdapter().getItemCount();
    if (adapterItemCount == RecyclerView.NO_POSITION || (adapterItemCount - 1) != position) {
      return;
    }
    outRect.top = calculateTopOffset(parent, view, adapterItemCount);
  }


  private int calculateTopOffset(RecyclerView parent, View footerView, int itemCount) {
    int topOffset =
        parent.getHeight() - parent.getPaddingTop() - parent.getPaddingBottom()
            - visibleChildHeightWithFooter(parent, footerView, itemCount);
    return topOffset < 0 ? 0 : topOffset;
  }



  private int visibleChildHeightWithFooter(RecyclerView parent, View footerView, int itemCount) {
    int totalHeight = 0;
    int onScreenItemCount = Math.min(parent.getChildCount(), itemCount);
    for (int i = 0; i < onScreenItemCount - 1; i++) {
      RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) parent.getChildAt(i)
          .getLayoutParams();
      int height =
          parent.getChildAt(i).getHeight() + layoutParams.topMargin
              + layoutParams.bottomMargin;
      totalHeight += height;
    }
    int footerHeight = footerView.getHeight();
    if (footerHeight == 0) {
      fixLayoutSize(footerView, parent);
      footerHeight = footerView.getHeight();
    }
    footerHeight = footerHeight + footerView.getPaddingBottom() + footerView.getPaddingTop();

    return totalHeight + footerHeight;
  }

  private void fixLayoutSize(View view, ViewGroup parent) {
    // Check if the view has a layout parameter and if it does not create one for it
    if (view.getLayoutParams() == null) {
      view.setLayoutParams(new ViewGroup.LayoutParams(
          ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
    }

    // Create a width and height spec using the parent as an example:
    // For width we make sure that the item matches exactly what it measures from the parent.
    //  IE if layout says to match_parent it will be exactly parent.getWidth()
    int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY);
    // For the height we are going to create a spec that says it doesn't really care what is calculated,
    //  even if its larger than the screen
    int heightSpec = View.MeasureSpec
        .makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED);

    // Get the child specs using the parent spec and the padding the parent has
    int childWidth = ViewGroup.getChildMeasureSpec(widthSpec,
        parent.getPaddingLeft() + parent.getPaddingRight(), view.getLayoutParams().width);
    int childHeight = ViewGroup.getChildMeasureSpec(heightSpec,
        parent.getPaddingTop() + parent.getPaddingBottom(), view.getLayoutParams().height);

    // Finally we measure the sizes with the actual view which does margin and padding changes to the sizes calculated
    view.measure(childWidth, childHeight);

    // And now we setup the layout for the view to ensure it has the correct sizes.
    view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
  }
}
6
Abhimaan

J'utilise un Linearlayout avec des poids. J'ai créé plusieurs valeurs pour le poids du pied de page, cela fonctionne parfaitement.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:tools="http://schemas.Android.com/tools"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:background="@color/white"
    Android:orientation="vertical"

<include layout="@layout/header" />

    <Android.support.v7.widget.RecyclerView
    Android:id="@+id/recycleView"
    Android:layout_width="match_parent"
    Android:layout_height="0dp"
    Android:layout_weight="0.5"
    tools:layout_height="0dp"
    tools:listitem="@layout/row" />

<TextView
    Android:layout_width="match_parent"
    Android:layout_height="0dp"
    Android:layout_weight="@dimen/footer_weight"
    Android:padding="@dimen/extra_padding"
    Android:paddingEnd="@dimen/small_padding"
    Android:paddingLeft="@dimen/small_padding"
    Android:paddingRight="@dimen/small_padding"
    Android:paddingStart="@dimen/small_padding"
    Android:text="@string/contact"
    Android:textColor="@color/grey" />

 </LinearLayout>
1
Karoly

Toutes ces solutions ne fonctionnent pas. Lorsque vous réduisez l'application et que vous l'ouvrez à nouveau, le pied de page vole plus bas que le bas de l'écran et vous devez faire défiler l'écran pour le voir, même s'il ne contient que 1 ou 2 éléments. Vous pouvez ajouter une vue de pied de page sous votre vue de recycleur au format xml.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 
xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:app="http://schemas.Android.com/apk/res-auto"
xmlns:tools="http://schemas.Android.com/tools"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:background="@Android:color/white">

<Android.support.v4.widget.NestedScrollView
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:fillViewport="true"
    Android:overScrollMode="never"
    Android:scrollbars="none">

    <LinearLayout
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:orientation="vertical">

        <Android.support.v7.widget.RecyclerView
            Android:id="@+id/recyclerView"
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content" />

        <Android.support.v4.widget.Space
            Android:layout_width="match_parent"
            Android:layout_height="0dp"
            Android:layout_weight="1"
            Android:minHeight="1dp" />

        <FrameLayout
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content">

            <include layout="@layout/recyclerView_footer" />
        </FrameLayout>
    </LinearLayout>
</Android.support.v4.widget.NestedScrollView>

faites attention - j'ai utilisé NestedScrollView avec

    recyclerView.isNestedScrollingEnabled = false

et SpaceView has weight 1 and height = 0dp et tout ce genre de choses à l'intérieur de linear layout et NestedScrollView has height = match_parent, maintenant j'ai le pied de page collé au fond et il bouge plus loin quand la liste devient plus grande

1

Je sais que c’est une vieille question, mais j’ajouterai une réponse à ceux qui rechercheraient une telle décision à l’avenir. Il estPOSSIBLEde conserver le dernier élément en bas de l’écran au cas où vous n’auriez que peu ou pas d’éléments et de faire défiler le dernier élément avec la vue recyclée lorsque vous avez plusieurs éléments.

Comment réaliser . Votre adaptateur RecyclerView doit appliquer plusieurs types de vues: les vues, qui doivent être affichées sous forme d'élément de liste; view, qui doit être affiché comme pied de page; une vue vide. Vous pouvez vérifier comment placer des articles avec différentes vues sur le RecyclerView ici: https://stackoverflow.com/a/29362643/6329995 Recherchez une vue vide entre votre liste principale et la vue du pied de page. Ensuite, dans onBindViewHolder pour la vue vide, vérifiez si vos vues de liste principale et de pied de page prennent tout l'écran. Si oui, définissez la hauteur de vue vide sur zéro, sinon définissez-la sur la hauteur qui semble ne pas être prise par les éléments et le pied de page. C'est tout. Vous pouvez également mettre à jour cette hauteur de manière dynamique lorsque vous supprimez/ajoutez des lignes. Appelez juste notifyItemChanged pour votre élément d'espace vide après avoir mis à jour la liste.

Vous pouvez également définir la hauteur de votre RecyclerView sur match_parent ou la hauteur exacte, PAS wrap_content!

J'espère que cela t'aides.

0
Lev161