Еквівалент 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()або обернути все в a ViewModelта встановити android:visibilityна viewModel.getEmptyViewVisibility().
Андре Дірманн

4
За це слід проголосувати вище, це чудовий приклад можливостей прив'язки даних
Ед Джордж

1
@javmarina Ні, для мене макет не продовжував оновлюватися. Якщо мій адаптер починається з розміру 0, а потім пізніше набір даних зростає, макет не буде оновлюватися за бажанням. Здається, прив’язка даних для мене не спрацює. :-(
meisteg

3
Чи буде це оновлюватися, навіть якщо адаптер динамічно зростає або стискається до нуля? Я сумніваюся в цьому.
Девід

1
@ a11n Він не оновлює макет, коли список стискається до 0 або отримує дані, ми повинні встановлювати значення прив'язки з класу кожного разу, коли ми робимо якісь зміни в списку, чи є спосіб оновити макет самостійно?
Om Infowave Developers

114

Ось клас, подібний до @dragon born, але більш повний. Виходячи з цього суті .

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

Ви можете пояснити, як я можу використовувати цей клас?
Ололокінг,

Ну точно так, як це було б зроблено з RecyclerView, він просто додає setEmptyViewметод, який ви можете викликати, коли хочете визначити порожній вигляд. Подивіться ListView.setEmptyViewдокументацію, якщо незрозуміло, це та сама ідея.
Марк Плано-Лесей

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, коли використовується безліч адаптерів для утилізації. Ви також можете спробувати розширити один BaseAdapter, призначений для загальних речей у всіх
Судхасрі,

2
Навіть якщо у вас є лише один адаптер і один вид переробника, це не відповідальність адаптера. Адаптер призначений для представлення предметів, а не відсутності елементів.
Марк Плано-Лесей

@Kernald Залежить від вашого випадку використання. Особисто я думаю, що набагато чистіше, як це зробив Судхасрі. Особливо, якщо ви хочете показати інший вигляд у випадку, якщо подарунків немає, наприклад: "Тут немає предметів, ходіть по магазинах!" або подібні речі
AgentKnopf

@Zainodis Як ви вже сказали, це інший погляд. Це не відповідальність адаптера, який полягає у відображенні елементів у перегляді утилізатора, ніщо інше. Я погоджуюсь, що технічно кажучи, обидва рішення працюють і майже однакові. Але елементи адаптера не створені для відображення таких поглядів.
Марк Плано-Лесей

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яка дозволяє просто встановити a 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();
    }
}

3
Хороша ідея також здійснити реалізацію setVisibility.
Марк Плано-Лесей

2

Я вважаю за краще реалізувати цю функцію в Recycler.Adapter

У своєму перевизначеному методі getItemCount введіть туди порожні контрольні коди:

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

3
Це не відповідальність адаптера. Адаптер призначений для представлення предметів, а не відсутності елементів.
Марк Плано-Лесей

@Kernald Це наш код і по-своєму, як ми його налаштовуємо та використовуємо.
Lalit Poptani

@LalitPoptani впевнений. Але це веб-сайт із запитаннями та відповідями, на якому люди шукають відповіді, здебільшого, не замислюючись більше, ніж "що це за ярлик копії знову?". Вказівка ​​на те, що рішення є семантично помилковим (до того ж, коли у вас також є рішення "з правами"), насправді не є марною ...
Марк Плано-Лесей

@Kernald ну, я думаю, що це рішення є найпростішим з усіх, і це також хороше рішення, тому що кожного разу, коли адаптер отримує повідомлення, це буде викликано і може бути використано для перевірки розміру даних!
Lalit Poptani

1
@ MarcPlano-Lesay правильний. Ця відповідь є неповною, оскільки вона не обробляє випадок, коли порожній вигляд повинен бути невидимим, коли елементи заповнюються. Після реалізації цієї частини це рішення стає неефективним, оскільки кожен раз, коли адаптер запитує кількість елементів, setVisibility()викликається. Звичайно, ви можете додати деякі прапорці для компенсації, але саме тоді він стає більш складним.
razzledazzle

2

Якщо ви хочете підтримувати більше станів, таких як стан завантаження, стан помилок, тоді ви можете перевірити https://github.com/rockerhieu/rv-adapter-states . В іншому випадку підтримка порожнього подання може бути легко реалізована за допомогою RecyclerViewAdapterWrapper( 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

З моєї точки зору, найпростіший спосіб зробити порожній вигляд - це створити новий порожній RecyclerView з макетом, який ви хочете надути як фон. І цей порожній адаптер встановлюється, коли ви перевіряєте розмір набору даних.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.