如何使用RecyclerView显示空视图?

JJD*_*JJD 249 android listview no-data android-recyclerview

我习惯在布局文件中放置一个特殊视图,ListActivity没有数据时显示文档中所述.此视图具有id "android:id/empty".

<TextView
    android:id="@android:id/empty"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/no_data" />
Run Code Online (Sandbox Code Playgroud)

我想知道如何用新的方式做到这一点RecyclerView

sle*_*lis 284

在定义的相同布局上RecyclerView,添加TextView:

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

<TextView
    android:id="@+id/empty_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:visibility="gone"
    android:text="@string/no_data_available" />
Run Code Online (Sandbox Code Playgroud)

onCreate或适当的回调,您检查提供您的数据集是否RecyclerView为空.如果数据集为空,则RecyclerView也为空.在这种情况下,消息将显示在屏幕上.如果没有,请更改其可见性:

private RecyclerView recyclerView;
private TextView emptyView;

// ...

recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
emptyView = (TextView) rootView.findViewById(R.id.empty_view);

// ...

if (dataset.isEmpty()) {
    recyclerView.setVisibility(View.GONE);
    emptyView.setVisibility(View.VISIBLE);
}
else {
    recyclerView.setVisibility(View.VISIBLE);
    emptyView.setVisibility(View.GONE);
}
Run Code Online (Sandbox Code Playgroud)

  • 这对我不起作用,因为回收器视图布局在片段中膨胀,而在适配器中决定是显示正常项目布局还是无项目布局. (10认同)
  • @JJD 使用 Fragment 时,同样适用。当你膨胀你的片段视图时,你有 recyclerview,然后是另一个用于你的空视图的布局。通常在我的片段中,我会有以下视图:1) recyclerview,2) 空视图,和 3) 进度条视图。无论列表是什么,我通常都会传递适配器,但是根据列表的大小,我将隐藏回收站视图并显示空的,反之亦然。如果一个 recyclerview 有一个空列表,那么它不会对它做任何事情。你只看到一个空的视图。 (2认同)
  • @Zapnologica你可以反过来做:使用Eventbus(例如greenrobots Eventbus或Otto),然后在数据集发生变化时在事件总线上发布适配器帖子.在片段中,您所做的只是创建一个方法,其参数对应于适配器传递到Eventbus.post方法的内容,然后相应地更改布局.更简单但也更加耦合的方法是在适配器中创建回调接口,并在创建适配器时让片段实现它. (2认同)
  • 在布局中,我得到了多个根标签。您的完整布局文件是什么样的? (2认同)

小智 190

对于我的项目,我做了这个解决方案(RecyclerView使用setEmptyView方法):

public class RecyclerViewEmptySupport extends RecyclerView {
    private View emptyView;

    private AdapterDataObserver emptyObserver = new AdapterDataObserver() {


        @Override
        public void onChanged() {
            Adapter<?> adapter =  getAdapter();
            if(adapter != null && emptyView != null) {
                if(adapter.getItemCount() == 0) {
                    emptyView.setVisibility(View.VISIBLE);
                    RecyclerViewEmptySupport.this.setVisibility(View.GONE);
                }
                else {
                    emptyView.setVisibility(View.GONE);
                    RecyclerViewEmptySupport.this.setVisibility(View.VISIBLE);
                }
            }

        }
    };

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

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

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

    @Override
    public void setAdapter(Adapter adapter) {
        super.setAdapter(adapter);

        if(adapter != null) {
            adapter.registerAdapterDataObserver(emptyObserver);
        }

        emptyObserver.onChanged();
    }

    public void setEmptyView(View emptyView) {
        this.emptyView = emptyView;
    }
}
Run Code Online (Sandbox Code Playgroud)

你应该使用它而不是RecyclerView类:

<com.maff.utils.RecyclerViewEmptySupport android:id="@+id/list1"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    />

<TextView android:id="@+id/list_empty"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Empty"
    />
Run Code Online (Sandbox Code Playgroud)

RecyclerViewEmptySupport list = 
    (RecyclerViewEmptySupport)rootView.findViewById(R.id.list1);
list.setLayoutManager(new LinearLayoutManager(context));
list.setEmptyView(rootView.findViewById(R.id.list_empty));
Run Code Online (Sandbox Code Playgroud)

  • 呃,我必须说这只是经典的谷歌BS.我必须实现这个只是为了添加一个空视图?它应该包含在他们的cr @ psh1t SDK中!对于godsake! (12认同)
  • 您是否从[此答案](http://stackoverflow.com/a/27801394/1276636)或[从此处](https://gist.github.com/adelnizamutdinov/31c8f054d1af4588dc5c)获取此代码? (2认同)
  • 我试图将它与FirebaseRecyclerAdapter结合使用,但它没有用.这是因为FirebaseRecyclerAdapter不调用onChange(AdapterDataObserver),而是调用onItem*-methods.为了使它工作,添加:`覆盖public void onItemRangeRemoved(int positionStart,int itemCount){// check}覆盖public void onItemRangeMoved(int fromPosition,int toPosition,int itemCount){// check} // etc .. (2认同)

rad*_*122 61

这是一个解决方案,仅使用具有不同视图类型的自定义适配器来处理空白情况.

public class EventAdapter extends 
    RecyclerView.Adapter<EventAdapter.ViewHolder> {

    private static final int VIEW_TYPE_EVENT = 0;
    private static final int VIEW_TYPE_DATE = 1;
    private static final int VIEW_TYPE_EMPTY = 2;

    private ArrayList items;

    public EventAdapter(ArrayList items) {
        this.items = items;
    }

    @Override
    public int getItemCount() {
        if(items.size() == 0){
            return 1;
        }else {
            return items.size();
        }
    }

    @Override
    public int getItemViewType(int position) {
        if (items.size() == 0) {
            return VIEW_TYPE_EMPTY;
        }else{
            Object item = items.get(position);
            if (item instanceof Event) {
                return VIEW_TYPE_EVENT;
            } else {
                return VIEW_TYPE_DATE;
            }
        }
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v;
        ViewHolder vh;
        if (viewType == VIEW_TYPE_EVENT) {
            v = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.item_event, parent, false);
            vh = new ViewHolderEvent(v);
        } else if (viewType == VIEW_TYPE_DATE) {
            v = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.item_event_date, parent, false);
            vh = new ViewHolderDate(v);
        } else {
            v = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.item_event_empty, parent, false);
            vh = new ViewHolder(v);
        }

        return vh;
    }

    @Override
    public void onBindViewHolder(EventAdapter.ViewHolder viewHolder, 
                                 final int position) {
        int viewType = getItemViewType(position);
        if (viewType == VIEW_TYPE_EVENT) {
            //...
        } else if (viewType == VIEW_TYPE_DATE) {
            //...
        } else if (viewType == VIEW_TYPE_EMPTY) {
            //...
        }
    }

    public static class ViewHolder extends ParentViewHolder {
        public ViewHolder(View v) {
            super(v);
        }
    }

    public static class ViewHolderDate extends ViewHolder {

        public ViewHolderDate(View v) {
            super(v);
        }
    }

    public static class ViewHolderEvent extends ViewHolder {

        public ViewHolderEvent(View v) {
            super(v);
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

  • @ sfk92fksdf为什么容易出错?这是您应该在recyclerview中处理多种视图类型的方式 (3认同)
  • 不必是一行。如果您有多种视图类型,这就是这样做的方法。 (3认同)
  • 是的,这是一个聪明,难以维护且容易出错的解决方案 (2认同)

wnc*_*_21 43

我使用ViewSwitcher

<ViewSwitcher
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/switcher"
    >

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

    <TextView android:id="@+id/text_empty"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/list_empty"
        android:gravity="center"
        />

</ViewSwitcher>
Run Code Online (Sandbox Code Playgroud)

在代码中,您将检查光标/数据集并切换视图.

void showItems(Cursor items) {
    if (items.size() > 0) {

        mAdapter.switchCursor(items);

        if (R.id.list == mListSwitcher.getNextView().getId()) {
            mListSwitcher.showNext();
        }
    } else if (R.id.text_empty == mListSwitcher.getNextView().getId()) {
        mListSwitcher.showNext();
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您希望使用几行代码,也可以设置动画

mListSwitcher.setInAnimation(slide_in_left);
mListSwitcher.setOutAnimation(slide_out_right);
Run Code Online (Sandbox Code Playgroud)


won*_*suc 23

由于凯文的回答并不完整.
这是,如果你使用更加正确的答案RecyclerAdapternotifyItemInsertednotifyItemRemoved更新的数据集.
只需添加以下代码即可

mAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {

    @Override
    public void onChanged() {
        super.onChanged();
        checkEmpty();
    }

    @Override
    public void onItemRangeInserted(int positionStart, int itemCount) {
        super.onItemRangeInserted(positionStart, itemCount);
        checkEmpty();
    }

    @Override
    public void onItemRangeRemoved(int positionStart, int itemCount) {
        super.onItemRangeRemoved(positionStart, itemCount);
        checkEmpty();
    }

    void checkEmpty() {
        mEmptyView.setVisibility(mAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
    }
});
Run Code Online (Sandbox Code Playgroud)

  • 这是一个很好的解决方案。我提议对其进行编辑,其中将包括 kotlin 版本。 (3认同)

She*_*yar 15

RVEmptyObserver

RecyclerView扩展an AdapterDataObserver是一种更简单的解决方案,它允许设置View在列表中没有项目时显示的自定义,而不是使用自定义:

用法示例:

RVEmptyObserver observer = new RVEmptyObserver(recyclerView, emptyView)
rvAdapter.registerAdapterDataObserver(observer);
Run Code Online (Sandbox Code Playgroud)

类:

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(); }
}
Run Code Online (Sandbox Code Playgroud)


Ped*_*ira 9

在适配器上getItemViewType检查适配器是否有0个元素,如果是,则返回不同的viewType.

然后在onCreateViewHolder检查viewType是否是您之前返回的那个并且膨胀不同的视图.在这种情况下,使用TextView的布局文件

编辑

如果这仍然不起作用,那么您可能希望以编程方式设置视图的大小,如下所示:

Point size = new Point();
((WindowManager)itemView.getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getSize(size);
Run Code Online (Sandbox Code Playgroud)

然后当你夸大你的观看电话时:

inflatedView.getLayoutParams().height = size.y;
inflatedView.getLayoutParams().width = size.x;
Run Code Online (Sandbox Code Playgroud)


Pha*_*inh 7

这是我的班级,用于显示空视图,重试视图(加载api失败时)和加载进度 RecyclerView

public class RecyclerViewEmptyRetryGroup extends RelativeLayout {
    private RecyclerView mRecyclerView;
    private LinearLayout mEmptyView;
    private LinearLayout mRetryView;
    private ProgressBar mProgressBar;
    private OnRetryClick mOnRetryClick;

    public RecyclerViewEmptyRetryGroup(Context context) {
        this(context, null);
    }

    public RecyclerViewEmptyRetryGroup(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RecyclerViewEmptyRetryGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void onViewAdded(View child) {
        super.onViewAdded(child);
        if (child.getId() == R.id.recyclerView) {
            mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
            return;
        }
        if (child.getId() == R.id.layout_empty) {
            mEmptyView = (LinearLayout) findViewById(R.id.layout_empty);
            return;
        }
        if (child.getId() == R.id.layout_retry) {
            mRetryView = (LinearLayout) findViewById(R.id.layout_retry);
            mRetryView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    mRetryView.setVisibility(View.GONE);
                    mOnRetryClick.onRetry();
                }
            });
            return;
        }
        if (child.getId() == R.id.progress_bar) {
            mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
        }
    }

    public void loading() {
        mRetryView.setVisibility(View.GONE);
        mEmptyView.setVisibility(View.GONE);
        mProgressBar.setVisibility(View.VISIBLE);
    }

    public void empty() {
        mEmptyView.setVisibility(View.VISIBLE);
        mRetryView.setVisibility(View.GONE);
        mProgressBar.setVisibility(View.GONE);
    }

    public void retry() {
        mRetryView.setVisibility(View.VISIBLE);
        mEmptyView.setVisibility(View.GONE);
        mProgressBar.setVisibility(View.GONE);
    }

    public void success() {
        mRetryView.setVisibility(View.GONE);
        mEmptyView.setVisibility(View.GONE);
        mProgressBar.setVisibility(View.GONE);
    }

    public RecyclerView getRecyclerView() {
        return mRecyclerView;
    }

    public void setOnRetryClick(OnRetryClick onRetryClick) {
        mOnRetryClick = onRetryClick;
    }

    public interface OnRetryClick {
        void onRetry();
    }
}
Run Code Online (Sandbox Code Playgroud)

activity_xml

<...RecyclerViewEmptyRetryGroup
        android:id="@+id/recyclerViewEmptyRetryGroup">

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

        <LinearLayout
            android:id="@+id/layout_empty">
            ...
        </LinearLayout>

        <LinearLayout
            android:id="@+id/layout_retry">
            ...
        </LinearLayout>

        <ProgressBar
            android:id="@+id/progress_bar"/>

</...RecyclerViewEmptyRetryGroup>
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

来源在这里https://github.com/PhanVanLinh/AndroidRecyclerViewWithLoadingEmptyAndRetry


hid*_*idd 7

另一种方法是使用addOnChildAttachStateChangeListenerwhich 处理RecyclerView.

recyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() {
            @Override
            public void onChildViewAttachedToWindow(@NonNull View view) {
                forEmptyTextView.setVisibility(View.INVISIBLE);
            }

            @Override
            public void onChildViewDetachedFromWindow(@NonNull View view) {
                forEmptyTextView.setVisibility(View.VISIBLE);
            }
        });
Run Code Online (Sandbox Code Playgroud)


Ras*_*iri 6

在自定义RecyclerView中使用AdapterDataObserver

科特林:

RecyclerViewEnum.kt

enum class RecyclerViewEnum {
    LOADING,
    NORMAL,
    EMPTY_STATE
}
Run Code Online (Sandbox Code Playgroud)

RecyclerViewEmptyLoadingSupport.kt

class RecyclerViewEmptyLoadingSupport : RecyclerView {

    var stateView: RecyclerViewEnum? = RecyclerViewEnum.LOADING
        set(value) {
            field = value
            setState()
        }
    var emptyStateView: View? = null
    var loadingStateView: View? = null


    constructor(context: Context) : super(context) {}

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}

    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {}


    private val dataObserver = object : AdapterDataObserver() {
        override fun onChanged() {
            onChangeState()
        }

        override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
            super.onItemRangeRemoved(positionStart, itemCount)
            onChangeState()
        }

        override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
            super.onItemRangeInserted(positionStart, itemCount)
            onChangeState()
        }
    }


    override fun setAdapter(adapter: RecyclerView.Adapter<*>?) {
        super.setAdapter(adapter)
        adapter?.registerAdapterDataObserver(dataObserver)
        dataObserver.onChanged()
    }


    fun onChangeState() {
        if (adapter?.itemCount == 0) {
            emptyStateView?.visibility = View.VISIBLE
            loadingStateView?.visibility = View.GONE
            this@RecyclerViewEmptyLoadingSupport.visibility = View.GONE
        } else {
            emptyStateView?.visibility = View.GONE
            loadingStateView?.visibility = View.GONE
            this@RecyclerViewEmptyLoadingSupport.visibility = View.VISIBLE
        }
    }

    private fun setState() {

        when (this.stateView) {
            RecyclerViewEnum.LOADING -> {
                loadingStateView?.visibility = View.VISIBLE
                this@RecyclerViewEmptyLoadingSupport.visibility = View.GONE
                emptyStateView?.visibility = View.GONE
            }

            RecyclerViewEnum.NORMAL -> {
                loadingStateView?.visibility = View.GONE
                this@RecyclerViewEmptyLoadingSupport.visibility = View.VISIBLE
                emptyStateView?.visibility = View.GONE
            }
            RecyclerViewEnum.EMPTY_STATE -> {
                loadingStateView?.visibility = View.GONE
                this@RecyclerViewEmptyLoadingSupport.visibility = View.GONE
                emptyStateView?.visibility = View.VISIBLE
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <LinearLayout
        android:id="@+id/emptyView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white"
        android:gravity="center"
        android:orientation="vertical">

        <TextView
            android:id="@+id/emptyLabelTv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="empty" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/loadingView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white"
        android:gravity="center"
        android:orientation="vertical">

        <ProgressBar
            android:id="@+id/progressBar"
            android:layout_width="45dp"
            android:layout_height="45dp"
            android:layout_gravity="center"
            android:indeterminate="true"
            android:theme="@style/progressBarBlue" />
    </LinearLayout>

    <com.peeyade.components.recyclerView.RecyclerViewEmptyLoadingSupport
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>
Run Code Online (Sandbox Code Playgroud)

在活动中使用这种方式:

recyclerView?.apply {
        layoutManager = GridLayoutManager(context, 2)
        emptyStateView = emptyView
        loadingStateView = loadingView
        adapter = adapterGrid
    }

    // you can set LoadingView or emptyView manual
    recyclerView.stateView = RecyclerViewEnum.EMPTY_STATE
    recyclerView.stateView = RecyclerViewEnum.LOADING
Run Code Online (Sandbox Code Playgroud)

  • 如果您采用该解决方案,请注意使用枚举是不够的,因为它增加了内存分配大小。根据您的上下文使用 Integer 或 String 以及适当的 `StringDef` 或 `IntDef` 注释 (2认同)