Equivalente di ListView.setEmptyView in RecyclerView


Risposte:


69

Con la nuova funzione di data binding puoi anche ottenere questo risultato direttamente nel tuo layout:

<TextView
   android:text="No data to display."
   android:visibility="@{dataset.size() > 0 ? View.GONE : View.VISIBLE}" />

In tal caso, devi solo aggiungere una variabile e un'importazione alla sezione dati del tuo XML:

<data>
<import type="android.view.View"/>
<variable
    name="dataset"
    type="java.util.List&lt;java.lang.String&gt;"
    />
</data>

6
L'esempio sopra è semplificato per enfatizzare l'approccio del data binding. Il data binding è molto flessibile. Ovviamente potresti importare il Adapterinvece del set di dati e usarlo getItemCount()o racchiudere tutto in ViewModele impostare android:visibilitysu viewModel.getEmptyViewVisibility().
André Diermann

4
Questo dovrebbe essere votato più in alto, è un eccellente esempio delle capacità di associazione dei dati
Ed George

1
@javmarina No, per me il layout non ha continuato ad aggiornarsi. Se il mio adattatore inizia con la dimensione 0 e successivamente il set di dati cresce, il layout non si aggiornerà come desiderato. Sembra che l'associazione dei dati non funzioni per me. :-(
meisteg

3
Questo si aggiornerà anche se l'adattatore cresce o si riduce a zero in modo dinamico? Ne dubito.
david

1
@ a11n Non aggiorna il layout quando l'elenco si riduce a 0 o riceve dati, dobbiamo impostare il valore per l'associazione dalla classe ogni volta che apportiamo modifiche alla lista, c'è un modo per aggiornare il layout da solo?
Sviluppatori Om Infowave

114

Ecco una classe simile a quella di @dragon born, ma più completa. Basato su questo succo .

public class EmptyRecyclerView extends RecyclerView {
    private View emptyView;
    final private AdapterDataObserver observer = new AdapterDataObserver() {
        @Override
        public void onChanged() {
            checkIfEmpty();
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            checkIfEmpty();
        }

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            checkIfEmpty();
        }
    };

    public EmptyRecyclerView(Context context) {
        super(context);
    }

    public EmptyRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public EmptyRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    void checkIfEmpty() {
        if (emptyView != null && getAdapter() != null) {
            final boolean emptyViewVisible = getAdapter().getItemCount() == 0;
            emptyView.setVisibility(emptyViewVisible ? VISIBLE : GONE);
            setVisibility(emptyViewVisible ? GONE : VISIBLE);
        }
    }

    @Override
    public void setAdapter(Adapter adapter) {
        final Adapter oldAdapter = getAdapter();
        if (oldAdapter != null) {
            oldAdapter.unregisterAdapterDataObserver(observer);
        }
        super.setAdapter(adapter);
        if (adapter != null) {
            adapter.registerAdapterDataObserver(observer);
        }

        checkIfEmpty();
    }

    public void setEmptyView(View emptyView) {
        this.emptyView = emptyView;
        checkIfEmpty();
    }
}

puoi spiegare come posso usare questa classe?
Ololoking

Ebbene, esattamente come faresti con RecyclerView, aggiunge semplicemente il setEmptyViewmetodo, che puoi chiamare ogni volta che vuoi definire la vista vuota. Vedi la ListView.setEmptyViewdocumentazione se non è chiara, è la stessa idea.
Marc Plano-Lesay

5
Un'implementazione simile da Google Samples: github.com/googlesamples/android-XYZTouristAttractions/blob/…
jase

2
Soluzione interessante ma il nome di una classe è strano =)
Шах

1
@AJW Immagino sia principalmente una questione di ciò che vuoi ottenere. La differenza tra le due implementazioni è davvero minima e non ne rimane nessuna non appena viene impostato un adattatore. Se non cambi l'adattatore (che è molto probabilmente il caso), non c'è differenza.
Marc Plano-Lesay

26

La soluzione fornita in questo collegamento sembra perfetta. Usa viewType per identificare quando mostrare emptyView. Non è necessario creare RecyclerView personalizzato

Aggiunta di codice dal collegamento sopra:

package com.example.androidsampleproject;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class RecyclerViewActivity extends Activity {

RecyclerView recyclerView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_recycler_view);
    recyclerView = (RecyclerView) findViewById(R.id.myList);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
    recyclerView.setAdapter(new MyAdapter());
}


private class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private List<String> dataList = new ArrayList<String>();

    public class EmptyViewHolder extends RecyclerView.ViewHolder {
        public EmptyViewHolder(View itemView) {
            super(itemView);
        }
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        TextView data;

        public ViewHolder(View v) {
            super(v);
            data = (TextView) v.findViewById(R.id.data_view);
        }
    }

    @Override
    public int getItemCount() {
        return dataList.size() > 0 ? dataList.size() : 1;
    }

    @Override
    public int getItemViewType(int position) {
        if (dataList.size() == 0) {
            return EMPTY_VIEW;
        }
        return super.getItemViewType(position);
    }


    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder vho, final int pos) {
        if (vho instanceof ViewHolder) {
            ViewHolder vh = (ViewHolder) vho;
            String pi = dataList.get(pos);
        }
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v;

        if (viewType == EMPTY_VIEW) {
            v = LayoutInflater.from(parent.getContext()).inflate(R.layout.empty_view, parent, false);
            EmptyViewHolder evh = new EmptyViewHolder(v);
            return evh;
        }

        v = LayoutInflater.from(parent.getContext()).inflate(R.layout.data_row, parent, false);
        ViewHolder vh = new ViewHolder(v);
        return vh;
    }

    private static final int EMPTY_VIEW = 10;
}

}

6
Penso che l'estensione di RecyclerView sia una soluzione più appropriata di questa perché generalmente ho molti adattatori per riciclatori e voglio evitare di aggiungere questo tipo di logica a ognuno di essi.
Gunhan

Questo ha senso @Gunhan, quando si utilizzano molti adattatori per riciclatori. Puoi anche provare a estendere un singolo BaseAdapter personalizzato per le cose comuni in tutti
Sudhasri

2
Anche se si dispone di un solo adattatore e di una visualizzazione riciclatore, non è responsabilità dell'adattatore. L'adattatore è qui per presentare gli elementi, non l'assenza di elementi.
Marc Plano-Lesay

@Kernald Dipende dal tuo caso d'uso. Personalmente penso che sia molto più pulito il modo in cui l'ha fatto Sudhasri. Soprattutto se vuoi mostrare una vista diversa nel caso in cui non ci siano articoli presenti come: "Nessun articolo qui, vai a fare la spesa!" o cose del genere
AgentKnopf

@Zainodis Come hai detto, è una visione diversa. E ' non è responsabilità della scheda, che è quello di visualizzare elementi nella recyclerview, nient'altro. Sono d'accordo che tecnicamente parlando, entrambe le soluzioni funzionano e sono praticamente uguali. Ma gli elementi dell'adattatore non sono fatti per visualizzare viste come questa.
Marc Plano-Lesay

10

Preferirei semplicemente una soluzione semplice come,

avere il tuo RecyclerView all'interno di un FrameLayout o RelativeLayout con un TextView o altra vista con la visualizzazione di un messaggio di dati vuoto con visibilità GONE per impostazione predefinita e quindi nella classe dell'adattatore, applicare la logica

Qui, ho un TextView con messaggio senza dati

@Override
public int getItemCount() {
    textViewNoData.setVisibility(data.size() > 0 ? View.GONE : View.VISIBLE);
    return data.size();
}

3

Prova RVEmptyObserver:

È un'implementazione di un AdapterDataObserverche ti consente di impostare semplicemente un Viewlayout vuoto predefinito per il tuo RecylerView. In questo modo, invece di usare un custom RecyclerViewe complicarti la vita, puoi usarlo facilmente con il tuo codice esistente:


Utilizzo di esempio:

RVEmptyObserver observer = new RVEmptyObserver(recyclerView, emptyView)
rvAdapter.registerAdapterDataObserver(observer);

Puoi vedere il codice e l' utilizzo di esempio in un'app reale qui.


Classe:

public class RVEmptyObserver extends RecyclerView.AdapterDataObserver {
    private View emptyView;
    private RecyclerView recyclerView;

    public RVEmptyObserver(RecyclerView rv, View ev) {
        this.recyclerView = rv;
        this.emptyView    = ev;
        checkIfEmpty();
    }

    private void checkIfEmpty() {
        if (emptyView != null && recyclerView.getAdapter() != null) {
            boolean emptyViewVisible = recyclerView.getAdapter().getItemCount() == 0;
            emptyView.setVisibility(emptyViewVisible ? View.VISIBLE : View.GONE);
            recyclerView.setVisibility(emptyViewVisible ? View.GONE : View.VISIBLE);
        }
    }

    public void onChanged() { checkIfEmpty(); }
    public void onItemRangeInserted(int positionStart, int itemCount) { checkIfEmpty(); }
    public void onItemRangeRemoved(int positionStart, int itemCount) { checkIfEmpty(); }
}

2

La mia versione, basata su https://gist.github.com/adelnizamutdinov/31c8f054d1af4588dc5c

public class EmptyRecyclerView extends RecyclerView {
    @Nullable
    private View emptyView;

    public EmptyRecyclerView(Context context) { super(context); }

    public EmptyRecyclerView(Context context, AttributeSet attrs) { super(context, attrs); }

    public EmptyRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    private void checkIfEmpty() {
        if (emptyView != null && getAdapter() != null) {
            emptyView.setVisibility(getAdapter().getItemCount() > 0 ? GONE : VISIBLE);
        }
    }

    private final AdapterDataObserver observer = new AdapterDataObserver() {
        @Override
        public void onChanged() {
            checkIfEmpty();
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            checkIfEmpty();
        }

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            checkIfEmpty();
        }
    };

    @Override
    public void setAdapter(@Nullable Adapter adapter) {
        final Adapter oldAdapter = getAdapter();
        if (oldAdapter != null) {
            oldAdapter.unregisterAdapterDataObserver(observer);
        }
        super.setAdapter(adapter);
        if (adapter != null) {
            adapter.registerAdapterDataObserver(observer);
        }
        checkIfEmpty();
    }

    @Override
    public void setVisibility(int visibility) {
        super.setVisibility(visibility);
        if (null != emptyView && (visibility == GONE || visibility == INVISIBLE)) {
            emptyView.setVisibility(GONE);
        } else {
            checkIfEmpty();
        }
    }

    public void setEmptyView(@Nullable View emptyView) {
        this.emptyView = emptyView;
        checkIfEmpty();
    }
}

3
Buona idea anche da reimplementare setVisibility.
Marc Plano-Lesay

2

Preferirei implementare questa funzionalità in Recycler.Adapter

Sul tuo metodo getItemCount sovrascritto, inserisci qui i codici di controllo vuoti:

@Override
public int getItemCount() {
    if(data.size() == 0) listIsEmtpy();
    return data.size();
}

3
Non è responsabilità dell'adattatore. L'adattatore è qui per presentare gli elementi, non l'assenza di elementi.
Marc Plano-Lesay

@Kernald È il nostro codice ed è a modo nostro, come lo personalizziamo e lo usiamo.
Lalit Poptani

@LalitPoptani sicuro. Ma è un sito di domande e risposte, dove le persone cercano risposte, il più delle volte senza pensare più a "qual è di nuovo la scorciatoia per la copia?". Indicare che la soluzione è semanticamente sbagliata (inoltre quando si hanno anche soluzioni "
a destra

@Kernald, beh, penso che questa soluzione sia la più semplice di tutte ed è anche una buona soluzione, perché ogni volta che l'adattatore viene notificato questo verrà chiamato e può essere utilizzato per verificare la dimensione dei dati!
Lalit Poptani

1
@ MarcPlano-Lesay è corretto. Questa risposta è incompleta perché non gestisce il caso in cui la vista vuota deve essere invisibile una volta riempiti gli elementi. Dopo aver implementato quella parte, questa soluzione diventa inefficiente perché ogni volta che l'adattatore interroga il conteggio degli elementi, setVisibility()viene chiamato. Sicuramente potresti aggiungere alcuni flag per compensare, ma è allora che diventa più complesso.
Razzledazzle

2

Se desideri supportare più stati come stato di caricamento, stato di errore, puoi effettuare il checkout su https://github.com/rockerhieu/rv-adapter-states . Altrimenti il ​​supporto della visualizzazione vuota può essere implementato facilmente utilizzando RecyclerViewAdapterWrapperda ( https://github.com/rockerhieu/rv-adapter ). Il vantaggio principale di questo approccio è che puoi facilmente supportare la visualizzazione vuota senza modificare la logica dell'adattatore esistente:

public class StatesRecyclerViewAdapter extends RecyclerViewAdapterWrapper {
    private final View vEmptyView;

    @IntDef({STATE_NORMAL, STATE_EMPTY})
    @Retention(RetentionPolicy.SOURCE)
    public @interface State {
    }

    public static final int STATE_NORMAL = 0;
    public static final int STATE_EMPTY = 2;

    public static final int TYPE_EMPTY = 1001;

    @State
    private int state = STATE_NORMAL;

    public StatesRecyclerViewAdapter(@NonNull RecyclerView.Adapter wrapped, @Nullable View emptyView) {
        super(wrapped);
        this.vEmptyView = emptyView;
    }

    @State
    public int getState() {
        return state;
    }

    public void setState(@State int state) {
        this.state = state;
        getWrappedAdapter().notifyDataSetChanged();
        notifyDataSetChanged();
    }

    @Override
    public int getItemCount() {
        switch (state) {
            case STATE_EMPTY:
                return 1;
        }
        return super.getItemCount();
    }

    @Override
    public int getItemViewType(int position) {
        switch (state) {
            case STATE_EMPTY:
                return TYPE_EMPTY;
        }
        return super.getItemViewType(position);
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        switch (viewType) {
            case TYPE_EMPTY:
                return new SimpleViewHolder(vEmptyView);
        }
        return super.onCreateViewHolder(parent, viewType);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        switch (state) {
            case STATE_EMPTY:
                onBindEmptyViewHolder(holder, position);
                break;
            default:
                super.onBindViewHolder(holder, position);
                break;
        }
    }

    public void onBindEmptyViewHolder(RecyclerView.ViewHolder holder, int position) {
    }

    public static class SimpleViewHolder extends RecyclerView.ViewHolder {
        public SimpleViewHolder(View itemView) {
            super(itemView);
        }
    }
}

Utilizzo:

Adapter adapter = originalAdapter();
StatesRecyclerViewAdapter statesRecyclerViewAdapter = new StatesRecyclerViewAdapter(adapter, emptyView);
rv.setAdapter(endlessRecyclerViewAdapter);

// Change the states of the adapter
statesRecyclerViewAdapter.setState(StatesRecyclerViewAdapter.STATE_EMPTY);
statesRecyclerViewAdapter.setState(StatesRecyclerViewAdapter.STATE_NORMAL);

Ho usato il tuo codice come base per una soluzione simile. Grazie!
Albert Vila Calvo

2

Ho risolto questo problema:
Creato layout layout_recyclerview_with_emptytext.xml file.
Creato EmptyViewRecyclerView.java
---------

EmptyViewRecyclerView emptyRecyclerView = (EmptyViewRecyclerView) findViewById (R.id.emptyRecyclerViewLayout);
emptyRecyclerView.addAdapter (mPrayerCollectionRecyclerViewAdapter, "Nessuna preghiera per la categoria selezionata.");

file layout_recyclerview_with_emptytext.xml

    <?xml version="1.0" encoding="utf-8"?>
    <merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/switcher"
>

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

<com.ninestars.views.CustomFontTextView android:id="@+id/recyclerViewEmptyTextView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:text="Empty Text"
    android:layout_gravity="center"
    android:gravity="center"
    android:textStyle="bold"
    />

    </merge>


EmptyViewRecyclerView.java

public class EmptyViewRecyclerView extends ViewSwitcher {
private RecyclerView mRecyclerView;
private CustomFontTextView mRecyclerViewExptyTextView;

public EmptyViewRecyclerView(Context context) {
    super(context);
    initView(context);
}

public EmptyViewRecyclerView(Context context, AttributeSet attrs) {
    super(context, attrs);
    initView(context);
}


private void initView(Context context) {
    LayoutInflater.from(context).inflate(R.layout.layout_recyclerview_with_emptytext, this, true);
    mRecyclerViewExptyTextView = (CustomFontTextView) findViewById(R.id.recyclerViewEmptyTextView);
    mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
    mRecyclerView.setLayoutManager(new LinearLayoutManager(context));
}

public void addAdapter(final RecyclerView.Adapter<?> adapter) {
    mRecyclerView.setAdapter(adapter);
    adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
        @Override
        public void onChanged() {
            super.onChanged();
            if(adapter.getItemCount() > 0) {
                if (R.id.recyclerView == getNextView().getId()) {
                    showNext();
                }
            } else {
                if (R.id.recyclerViewEmptyTextView == getNextView().getId()) {
                    showNext();
                }
            }
        }
    });
}

public void addAdapter(final RecyclerView.Adapter<?> adapter, String emptyTextMsg) {
    addAdapter(adapter);
    setEmptyText(emptyTextMsg);
}

public RecyclerView getRecyclerView() {
    return mRecyclerView;
}

public void setEmptyText(String emptyTextMsg) {
    mRecyclerViewExptyTextView.setText(emptyTextMsg);
}

}

1
public class EmptyRecyclerView extends RecyclerView {
  @Nullable View emptyView;

  public EmptyRecyclerView(Context context) { super(context); }

  public EmptyRecyclerView(Context context, AttributeSet attrs) { super(context, attrs); }

  public EmptyRecyclerView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
  }

  void checkIfEmpty() {
    if (emptyView != null) {
      emptyView.setVisibility(getAdapter().getItemCount() > 0 ? GONE : VISIBLE);
    }
  }

  final @NotNull AdapterDataObserver observer = new AdapterDataObserver() {
    @Override public void onChanged() {
      super.onChanged();
      checkIfEmpty();
    }
  };

  @Override public void setAdapter(@Nullable Adapter adapter) {
    final Adapter oldAdapter = getAdapter();
    if (oldAdapter != null) {
      oldAdapter.unregisterAdapterDataObserver(observer);
    }
    super.setAdapter(adapter);
    if (adapter != null) {
      adapter.registerAdapterDataObserver(observer);
    }
  }

  public void setEmptyView(@Nullable View emptyView) {
    this.emptyView = emptyView;
    checkIfEmpty();
  }
}

qualcosa del genere potrebbe aiutare


2
Questo è incompleto. Probabilmente dovrai nascondere RecyclerViewquando emptyViewè visibile (e il contrario). Avrete anche bisogno di chiamare checkIfEmpty()in onItemRangeInserted()e onItemRangeRemoved(). Oh, e avresti potuto citare la tua fonte: gist.github.com/adelnizamutdinov/31c8f054d1af4588dc5c
Marc Plano-Lesay


1

Puoi semplicemente dipingere il testo RecyclerViewquando è vuoto. Supporta i seguenti personalizzati sottoclasse empty, failed, loading, e offlinele modalità. Per una compilazione di successo, aggiungi recyclerView_stateTextcolore alle tue risorse.

/**
 * {@code RecyclerView} that supports loading and empty states.
 */
public final class SupportRecyclerView extends RecyclerView
{
    public enum State
    {
        NORMAL,
        LOADING,
        EMPTY,
        FAILED,
        OFFLINE
    }

    public SupportRecyclerView(@NonNull Context context)
    {
        super(context);

        setUp(context);
    }

    public SupportRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs)
    {
        super(context, attrs);

        setUp(context);
    }

    public SupportRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);

        setUp(context);
    }

    private Paint textPaint;
    private Rect textBounds;
    private PointF textOrigin;

    private void setUp(Context c)
    {
        textPaint = new Paint();
        textPaint.setAntiAlias(true);
        textPaint.setColor(ContextCompat.getColor(c, R.color.recyclerView_stateText));

        textBounds = new Rect();
        textOrigin = new PointF();
    }

    private State state;

    public State state()
    {
        return state;
    }

    public void setState(State newState)
    {
        state = newState;
        calculateLayout(getWidth(), getHeight());
        invalidate();
    }

    private String loadingText = "Loading...";

    public void setLoadingText(@StringRes int resId)
    {
        loadingText = getResources().getString(resId);
    }

    private String emptyText = "Empty";

    public void setEmptyText(@StringRes int resId)
    {
        emptyText = getResources().getString(resId);
    }

    private String failedText = "Failed";

    public void setFailedText(@StringRes int resId)
    {
        failedText = getResources().getString(resId);
    }

    private String offlineText = "Offline";

    public void setOfflineText(@StringRes int resId)
    {
        offlineText = getResources().getString(resId);
    }

    @Override
    public void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);

        String s = stringForCurrentState();
        if (s == null)
            return;

        canvas.drawText(s, textOrigin.x, textOrigin.y, textPaint);
    }

    private void calculateLayout(int w, int h)
    {
        String s = stringForCurrentState();
        if (s == null)
            return;

        textPaint.setTextSize(.1f * w);
        textPaint.getTextBounds(s, 0, s.length(), textBounds);

        textOrigin.set(
         w / 2f - textBounds.width() / 2f - textBounds.left,
         h / 2f - textBounds.height() / 2f - textBounds.top);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
        super.onSizeChanged(w, h, oldw, oldh);

        calculateLayout(w, h);
    }

    private String stringForCurrentState()
    {
        if (state == State.EMPTY)
            return emptyText;
        else if (state == State.LOADING)
            return loadingText;
        else if (state == State.FAILED)
            return failedText;
        else if (state == State.OFFLINE)
            return offlineText;
        else
            return null;
    }
}

1

Dal mio punto di vista il modo più semplice per eseguire una visualizzazione vuota è creare un nuovo RecyclerView vuoto con il layout che si desidera gonfiare come sfondo. E questo adattatore vuoto viene impostato quando controlli la dimensione del set di dati.

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.