2015-11-24 15 views
13

ho RecyclerView e ho bisogno di un comportamento successivo:Come implementare piè di pagina appiccicoso in recyclerview

  • se ci sono un sacco di elementi (più di adatta schermo) - piè di pagina è l'ultima voce
  • se pochi voce/no elemento - il piè di pagina si trova nella parte inferiore dello schermo

Si prega di avvisare come posso implementare questo comportamento.

+0

si può mettere il ultimo elemento con un altro layout per la parte inferiore dello schermo e si può mettere il recyclerview a inizio it.so sarà sempre in fondo e il tuo recycleview non verrà effettuato – nomad

+0

nomad, mi dispiace, ma non è previsto risultato: se avessi l'ultimo elemento recyclerview trasparente - così su scrollare l'ultimo elemento (il mio footer) non avrebbe un'animazione adeguata (invece di essere appiccicoso all'ultimo elemento 1 sarebbe apparire dal basso). Spero tu abbia capito la mia mente. – resource8218

+0

checkout https://gist.github.com/mheras/0908873267def75dc746 e questa risposta SO: http://stackoverflow.com/a/29168617/3140227 –

risposta

6

È possibile utilizzare RecyclerView.ItemDecoration per implementare questo comportamento.

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

Assicurarsi di impostare match_parent per l'altezza RecyclerView.

prega di dare un'occhiata al l'applicazione di esempio https://github.com/JohnKuper/recyclerview-sticky-footer e come funziona http://sendvid.com/nbpj0806

Un inconveniente enorme di questa soluzione è che funziona correttamente solo dopo notifyDataSetChanged() in tutta l'applicazione (decorazione non all'interno). Con notifiche più specifiche non funzionerà correttamente e per supportarle, richiede un modo più logico. Inoltre, è possibile ottenere informazioni dalla biblioteca recyclerview-stickyheaders entro il eowise e migliorare questa soluzione.

+0

Chiamare 'notifyDataSetChanged()' in 'getItemOffset' è una cattiva idea, questo può causare un loop infinito. Che è successo nel mio caso. – AmeyaB

+0

è una soluzione molto bella, solo una cosa che ho dovuto aggiungere in 'visibleChildsHeightWithFooter', margine superiore e inferiore per ogni vista – lelloman

+0

@AmeyaB sotto soluzione risolve il problema credo. –

-1

Se non è possibile dimenticare RecyclerView e utilizzare ListView, quindi controllare questo collegamento Is there an addHeaderView equivalent for RecyclerView? ha tutto ciò che serve. Riguarda l'intestazione, ma è praticamente la stessa, tranne che l'intestazione è all'inizio del tuo elenco e il piè di pagina è alla fine.

+2

Grazie per il tuo link! Sfortunatamente, non ci sono soluzioni per mantenere il piè di pagina nella parte inferiore dello schermo se non ci sono/pochi elementi. – resource8218

0

Lo so, questa è una domanda vecchia, ma aggiungerò una risposta a coloro che vorrebbero cercare una tale decisione in futuro. È POSSIBLE per conservare l'ultimo elemento nella parte inferiore dello schermo nel caso in cui si abbiano solo pochi o nessun articolo e si faccia l'ultimo elemento da scorrere con il recyclerview quando si hanno molti articoli.

Come raggiungere. L'adattatore RecyclerView dovrebbe applicare diversi tipi di visualizzazione: viste, che dovrebbero essere visualizzate come voci di elenco; vista, che dovrebbe essere mostrata come piè di pagina; una vista vuota. È possibile verificare come inserire elementi con viste diverse su RecyclerView qui: https://stackoverflow.com/a/29362643/6329995 Individuare una vista vuota tra l'elenco principale e la visualizzazione del piè di pagina. Quindi in onBindViewHolder per la vista vuota controlla se la vista dell'elenco principale e la vista a piè di pagina occupano tutto lo schermo. In caso affermativo, imposta l'altezza della vista vuota su zero, altrimenti imposta l'altezza che sembra non essere presa da elementi e piè di pagina. È tutto. È inoltre possibile aggiornare tale altezza in modo dinamico, quando si eliminano/aggiungono righe. Basta chiamare notifyItemChanged per il tuo spazio vuoto dopo aver aggiornato l'elenco.

Si dovrebbe anche impostare l'altezza di RecyclerView su match_parent o altezza esatta, NON wrap_content!

Spero che questo aiuti.

+0

Ho capito come funziona quando ci sono pochi oggetti, ma quando ci sono molti oggetti come fai a essere sicuro che l'elemento del piè di pagina si trovi nella parte inferiore dello schermo? –

1

Sto usando un Linearlayout con pesi. Ho creato più valori per il peso del footer, funziona perfettamente.

<?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> 
0

Improvvisare su Dmitriy Korobeynikov e risolvere il problema di chiamare notificare dataset cambiato

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