Эквивалент ListView.setEmptyView в RecyclerView

Ответы:

69

С помощью новой функции привязки данных вы также можете добиться этого непосредственно в макете:

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

В этом случае вам просто нужно добавить переменную и импорт в раздел данных вашего XML:

<data>
<import type="android.view.View"/>
<variable
    name="dataset"
    type="java.util.List&lt;java.lang.String&gt;"
    />
</data>
Андре Дирманн
источник
6
Приведенный выше пример упрощен, чтобы подчеркнуть подход привязки данных. Связывание данных очень гибкое. Вы, конечно, можете импортировать Adapterвместо набора данных и использовать его getItemCount()или обернуть все в ViewModelи установить android:visibilityзначение viewModel.getEmptyViewVisibility().
Андре Дирманн,
4
За это следует проголосовать выше, это отличный пример возможностей связывания данных
Эд Джордж
1
@javmarina Нет, у меня раскладка не продолжала обновляться. Если мой адаптер начинается с размера 0, а затем набор данных увеличивается, макет не будет обновляться должным образом. Похоже, привязка данных у меня не работает. :-(
meisteg
3
Будет ли это обновляться, даже если адаптер динамически растет или сжимается до нуля? Я сомневаюсь, что.
Дэвид
1
@ a11n Он не обновляет макет, когда список сокращается до 0 или получает данные, мы должны устанавливать значение для привязки из класса каждый раз, когда мы вносим какие-либо изменения в список, есть ли способ обновить макет самостоятельно?
Om Infowave Developers
114

Вот класс, похожий на @dragonborn, но более полный. Исходя из этого суть .

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();
    }
}
Марк Плано-Лесе
источник
не могли бы вы объяснить, как я могу использовать этот класс?
Ololoking
Точно так же, как вы сделали бы с RecyclerView, он просто добавляет setEmptyViewметод, который вы можете вызывать, когда хотите определить пустое представление. См. ListView.setEmptyViewДокументацию, если непонятно, идея та же.
Marc Plano-Lesay
5
Аналогичная реализация из Google Samples: github.com/googlesamples/android-XYZTouristAttractions/blob/…
jase
2
Классное решение, но у класса странное название =)
Шах
1
@AJW Думаю, это в основном вопрос того, чего вы хотите достичь. Разница между двумя реализациями действительно незначительна, и ее не остается, как только адаптер установлен. Если не менять адаптер (что, скорее всего, так), разницы нет.
Marc Plano-Lesay
26

Решение, представленное по этой ссылке, кажется идеальным. Он использует viewType, чтобы определить, когда показывать emptyView. Нет необходимости создавать собственный RecyclerView

Добавление кода по указанной выше ссылке:

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
Я думаю, что расширение RecyclerView - более подходящее решение, чем это, потому что обычно у меня много адаптеров ресайклера, и я хочу избежать добавления такой логики к каждому из них.
Gunhan
В этом есть смысл @Gunhan при использовании многих адаптеров ресайклера. Вы также можете попробовать расширить один BaseAdapter, настроенный для общих
задач
2
Даже если у вас есть только один адаптер и одно представление ресайклера, это не ответственность адаптера. Адаптер предназначен для демонстрации предметов, а не их отсутствия.
Марк Плано-Леси,
@Kernald Зависит от вашего варианта использования. Лично я считаю, что это намного чище, чем это сделал Судхасри. Особенно, если вы хотите показать другой вид в случае отсутствия каких-либо предметов, например: «Здесь нет предметов, иди за покупками!» или тому подобное
AgentKnopf 06
@Zainodis Как ты сказал, это другой взгляд. Это не ответственность адаптера, которая заключается в отображении элементов в recyclerview и ничего больше. Я согласен с тем, что технически оба решения работают и в значительной степени одинаковы. Но элементы адаптера не предназначены для отображения подобных представлений.
Марк Плано-Лесей,
10

Я бы просто предпочел простое решение, например,

иметь свой RecyclerView внутри FrameLayout или RelativeLayout с TextView или другим представлением с отображением пустого сообщения данных с видимостью GONE по умолчанию, а затем в классе адаптера примените логику

Здесь у меня есть один TextView с сообщением без данных

@Override
public int getItemCount() {
    textViewNoData.setVisibility(data.size() > 0 ? View.GONE : View.VISIBLE);
    return data.size();
}
Лалит Поптани
источник
3

Попробуйте RVEmptyObserver:

Это реализация, AdapterDataObserverкоторая позволяет вам просто установить Viewпустой макет по умолчанию для вашего RecylerView. Таким образом, вместо того, чтобы использовать обычай RecyclerViewи усложнять себе жизнь, вы можете легко использовать его с существующим кодом:


Пример использования:

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

Вы можете увидеть код и пример использования в реальном приложении здесь.


Класс:

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

Моя версия, основанная на 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();
    }
}
localhost
источник
3
Хорошая идея и для повторной реализации setVisibility.
Marc Plano-Lesay
2

Я бы предпочел реализовать эту функцию в Recycler.Adapter.

В вашем переопределенном методе getItemCount введите туда пустые проверочные коды:

@Override
public int getItemCount() {
    if(data.size() == 0) listIsEmtpy();
    return data.size();
}
Билал
источник
3
Дело не в адаптере. Адаптер предназначен для демонстрации предметов, а не их отсутствия.
Марк Плано-Лесей,
@Kernald Это наш код и его собственный подход, как мы его настраиваем и используем.
Lalit Poptani
@LalitPoptani уверен. Но это веб-сайт вопросов и ответов, где люди ищут ответы, большую часть времени не задумываясь больше, чем «что такое ярлык для копирования снова?». Указывать, что решение семантически неверно (тем более, когда у вас есть «правильные» решения), на самом деле не бесполезно…
Марк Плано-Лесай 04
@Kernald хорошо, я думаю, что это решение является самым простым из всех, и это также хорошее решение, потому что каждый раз, когда адаптер получает уведомление, он будет вызываться и может использоваться для проверки размера данных!
Лалит Поптани,
1
@ MarcPlano-Lesay прав. Этот ответ неполный, потому что он не обрабатывает случай, когда пустое представление должно быть невидимым после заполнения элементов. После реализации этой части это решение становится неэффективным, потому что каждый раз, когда адаптер запрашивает количество элементов, setVisibility()вызывается. Конечно, вы могли бы добавить несколько флагов для компенсации, но тогда это станет более сложным.
razzledazzle
2

Если вы хотите поддерживать больше состояний, таких как состояние загрузки, состояние ошибки, вы можете проверить https://github.com/rockerhieu/rv-adapter-states . В противном случае поддержку пустого представления можно легко реализовать с помощью RecyclerViewAdapterWrapperfrom ( https://github.com/rockerhieu/rv-adapter ). Основное преимущество этого подхода в том, что вы можете легко поддерживать пустое представление, не изменяя логику существующего адаптера:

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

Применение:

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);
Хиеу Рокер
источник
Я использовал ваш код как основу для подобного решения. Благодарность!
Альберт Вила Кальво,
2

Я исправил это:
Создан файл layout_recyclerview_with_emptytext.xml.
Создан EmptyViewRecyclerView.java
---------

EmptyViewRecyclerView emptyRecyclerView = (EmptyViewRecyclerView) findViewById (R.id.emptyRecyclerViewLayout);
emptyRecyclerView.addAdapter (mPrayerCollectionRecyclerViewAdapter, «Нет молитвы для выбранной категории.»);

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

что-то вроде этого может помочь

Мунаввар Хуссейн Шелия
источник
2
Это неполно. Вероятно, вам нужно будет скрыть, RecyclerViewкогда emptyViewотображается (и наоборот). Вам также нужно позвонить checkIfEmpty()на onItemRangeInserted()и onItemRangeRemoved(). О, и вы могли бы процитировать свой источник: gist.github.com/adelnizamutdinov/31c8f054d1af4588dc5c
Марк Плано-Лесай
1

Вы можете просто нарисовать текст на RecyclerViewпустом месте. Следующие пользовательский подкласс поддерживает empty, failed, loading, и offlineрежимы. Для успешной компиляции раскрасьте recyclerView_stateTextсвои ресурсы.

/**
 * {@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

С моей точки зрения, самый простой способ создать пустой View - это создать новый пустой RecyclerView с макетом, который вы хотите раздуть в качестве фона. И этот пустой адаптер устанавливается, когда вы проверяете размер своего набора данных.

user7108272
источник