这一年来公司做的项目主要是电商,市场用到了列表的显示,也遇到了一些坑,今天咱们来聊一下,如何用 RecyclerView 进行设置空列表界面的提示.
RecyclerView 是 listView 的升级版,其实在日常的开发中,有很多地方我们都在使用 RecyclerView,为什么这样说,RecyclerView 除了列表之外,还能替代 ScrollView.RecyclerView 实现了 NestedScrollingChild, 可以配合官方提供的 support-design 包实现很多很炫的效果。
这几年 google 一直推崇其设计理念 MaterialDesign,同时也出了许多方便的包,如 RecyclerView、CardView、SupportDesign 包里的控件,可以说,这两年 google 给研发人员带来了春天,很多很炫的效果,直接拿来就用,这样说下去就扯远了,咱们先看看什么是 RecyclerView。RecyclerView 在 android.support.v7.widget 包中。
- compile 'com.android.support:appcompat-v7:24.2.1'
RecyclerView 其实就是 listview 的升级版,具有更高灵活、扩展,但也往往因此失去了 listview 很多封装好的一些接口,如 setHeader、setFooter、setEmptyView,因此你会发现 RecyclerView 不支持 setEmptyView,咱们先来看一下 listView 的 setEmptyView 是怎么做的,咱们也可以借鉴一下 listView 的做法做一个。
从上图所得,ListView 继承于 AbsListView, 而 AbsListView 继承与 AdapterView。 我们可以直接去 AdapterView 看其源码是怎样实现 setEmptyView 的。
- /**
- * Sets the view to show if the adapter is empty
- */
- @android.view.RemotableViewMethod public void setEmptyView(View emptyView) {
- //这里保存一个emptyView
- mEmptyView = emptyView;
- // If not explicitly specified this view is important for accessibility.
- if (emptyView != null && emptyView.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
- emptyView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
- }
- final T adapter = getAdapter();
- //判断是否adapter中的集合为空
- final boolean empty = ((adapter == null) || adapter.isEmpty());
- updateEmptyStatus(empty);
- }
- /**
- * Update the status of the list based on the empty parameter. If empty is true and
- * we have an empty view, display it. In all the other cases, make sure that the listview
- * is VISIBLE and that the empty view is GONE (if it's not null).
- */
- private void updateEmptyStatus(boolean empty) {
- if (isInFilterMode()) {
- empty = false;
- }
- //这里才是真正判断进行显示与否
- if (empty) {
- if (mEmptyView != null) {
- //1.如果ListView中有调用setEmptyView,同时adapter中的集合为0的话,显示emptyView,隐藏listView
- mEmptyView.setVisibility(View.VISIBLE);
- setVisibility(View.GONE);
- } else {
- //如果listView中没有设置emptyView,让listView显示
- // If the caller just removed our empty view, make sure the list view is visible
- setVisibility(View.VISIBLE);
- }
- // We are now GONE, so pending layouts will not be dispatched.
- // Force one here to make sure that the state of the list matches
- // the state of the adapter.
- if (mDataChanged) {
- this.onLayout(false, mLeft, mTop, mRight, mBottom);
- }
- } else {
- if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
- setVisibility(View.VISIBLE);
- }
- }
从源码上可得,ListView 中的 setEmptyView(View),其实只是在内部进行一个判断,如果 Adapter 里面的 isEmpty() 为 true 并且 listView 里面的 mEmptyView 不为空,则显示 mEmptyView, 同时隐藏自身的 ListView.
从源码上看,不就是隐藏显示嘛,没错,但要做到隐藏显示也不容易,因此,我们对我们的 RecyclerView 进行一番改造,看一下,是否能达到我们想要的效果。
- <ffzxcom.mytest.recyclerviewemptyviewdemo.way1.RecyclerViewEmptySupport
- android:id="@+id/recycler_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- </ffzxcom.mytest.recyclerviewemptyviewdemo.way1.RecyclerViewEmptySupport>
- <TextView
- android:id="@+id/empty_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerInParent="true"
- android:drawablePadding="10dp"
- android:drawableTop="@mipmap/empty_view"
- android:text="@string/no_data_tips"
- android:gravity="center"
- android:visibility="gone" />
根据 ListView 的做法,我们可以对 RecyclerView 进行重写:
- /**
- * Created by shenminjie on 2016/10/19.
- * 用来演示如何在RecyclerView里面添加setEmptyView
- */
- public class RecyclerViewEmptySupport extends RecyclerView {
- /**
- * 当数据为空时展示的View
- */
- private View mEmptyView;
- /**
- * 创建一个观察者
- * 为什么要在onChanged里面写?
- * 因为每次notifyDataChanged的时候,系统都会调用这个观察者的onChange函数
- * 我们大可以在这个观察者这里判断我们的逻辑,就是显示隐藏
- */
- private AdapterDataObserver emptyObserver = new AdapterDataObserver() {
- @Override public void onChanged() {
- Adapter < ?>adapter = getAdapter();
- //这种写发跟之前我们之前看到的ListView的是一样的,判断数据为空否,在进行显示或者隐藏
- if (adapter != null && mEmptyView != null) {
- if (adapter.getItemCount() == 0) {
- mEmptyView.setVisibility(View.VISIBLE);
- RecyclerViewEmptySupport.this.setVisibility(View.GONE);
- } else {
- mEmptyView.setVisibility(View.GONE);
- RecyclerViewEmptySupport.this.setVisibility(View.VISIBLE);
- }
- }
- }
- };
- public RecyclerViewEmptySupport(Context context) {
- super(context);
- }
- public RecyclerViewEmptySupport(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- }
- public RecyclerViewEmptySupport(Context context, @Nullable AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
- /**
- * 依赖注入
- *
- * @param emptyView 展示的空view
- */
- public void setEmptyView(View emptyView) {
- mEmptyView = emptyView;
- }
- @Override public void setAdapter(Adapter adapter) {
- super.setAdapter(adapter);
- if (adapter != null) {
- //这里用了观察者模式,同时把这个观察者添加进去,
- // 至于这个模式怎么用,谷歌一下,不多讲了,因为这个涉及到了Adapter的一些原理,感兴趣可以点进去看看源码,还是受益匪浅的
- adapter.registerAdapterDataObserver(emptyObserver);
- }
- //当setAdapter的时候也调一次
- emptyObserver.onChanged();
- }
- }
我们在 RecyclerView 里面添加了一个成员变量 emptyObserver,这个作用就是用于观察每次 Adapter 进行数据刷新的时候都调用一次观察者的 onChange(), 至于为什么会调,这个就说得有点远了,建议大家如果想看源码,可以参考一下 Android 源码设计模式 (何红辉,关爱民),里面有很详细的介绍 android 的一些设计原理。
回到上面来,我们看到我们的 onChange 里面的代码,不就是跟 ListView 里面的一样吗,没错, 我想说的第一种方式就是借鉴了 ListView 的做法,当然,还是其他的做法,只是这个思路会有些不一样,第一种的方法跟 listview 一样,在 xml 里面写上 emptyview,即便不用在 RecyclerView 里写,相信大家也知道可以在 activity 里进行判断,不就是隐藏显示嘛,当然,还有第二种做法。
我们知道,RecyclerView 的出现,更大程度给了开发者去自定义自己希望的布局,RecyclerView 可以通过引入不同的 ViewType 进行不同的列表显示,举个列子:及时通讯的聊天记录,一般都会有左边的布局跟右边的布局,那么,就有两个不同的 ViewType 了,根据不同情况进行引入不同的 ViewType。好,那么,我们也可以根据我们的情况进行引入布局啊,例如,如果数据为空的时候,那我能不能在我们的 Adapter 里面引入一个 emptyView 这样的布局。
- /**
- * Created by shenminjie on 2016/10/20.
- * 适配器,模拟列表
- */
- public class EmptyAdapter extends RecyclerView.Adapter < RecyclerView.ViewHolder > {
- /**
- * 数据
- */
- private List < String > mDatas;
- /**
- * 点击事件回调
- */
- private OnItemSelectListener mListener;
- /**
- * viewType--分别为item以及空view
- */
- public static final int VIEW_TYPE_ITEM = 1;
- public static final int VIEW_TYPE_EMPTY = 0;
- public EmptyAdapter(List < String > datas) {
- mDatas = datas;
- }
- /**
- * 设置回调
- *
- * @param listener
- */
- public void setOnItemSelectListener(OnItemSelectListener listener) {
- mListener = listener;
- }
- @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- //在这里根据不同的viewType进行引入不同的布局
- if (viewType == VIEW_TYPE_EMPTY) {
- View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_empty_view_layout, parent, false);
- return new RecyclerView.ViewHolder(view) {};
- }
- //其他的引入正常的
- View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_my_list_layout, parent, false);
- return new MyViewHolder(view);
- }
- @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
- if (holder instanceof MyViewHolder) {
- MyViewHolder viewHolder = (MyViewHolder) holder;
- viewHolder.setData(mDatas.get(position));
- viewHolder.getItemView().setOnClickListener(new View.OnClickListener() {@Override public void onClick(View v) {
- if (mListener != null) {
- mListener.onItemSelectListener(v, position);
- }
- }
- });
- }
- }
- @Override public int getItemCount() {
- //同时这里也需要添加判断,如果mData.size()为0的话,只引入一个布局,就是emptyView
- //那么,这个recyclerView的itemCount为1
- if (mDatas.size() == 0) {
- return 1;
- }
- //如果不为0,按正常的流程跑
- return mDatas.size();
- }
- @Override public int getItemViewType(int position) {
- //在这里进行判断,如果我们的集合的长度为0时,我们就使用emptyView的布局
- if (mDatas.size() == 0) {
- return VIEW_TYPE_EMPTY;
- }
- //如果有数据,则使用ITEM的布局
- return VIEW_TYPE_ITEM;
- }
- }
这种做法,就是把我们的 emptyView 设置放进去 Adapter, 根据不同的情况引入不同的布局,跟第一的区别就是显示与否都交给系统去处理,通过引入不同布局的做法达到了显示 emptyView 的效果。
相信大家都会在想,哪一种方式更好用,这得看个人的需求,但我更倾向于用第二种,因为 google 这两年提供了许多很好的资源给我们开发者使用,最热的莫过于是 support-design 包里的一些新控件,tabLayout、toolbar、CoordinatorLayout 等等,但如果想要更好的使用它们很炫的一些效果,得好好了解一下 NestedScrollingParent 这个接口,google 提供了这些接口很好的处理了事件分发的处理,而 RecyclerView 均实现了这些接口,能很好的配合 support-design 使用其特效。感兴趣的朋友可以上去 github 下载 demo,感受一下两种方式的不同。
- https://github.com/shenminjie/blog_demo
来源: https://juejin.im/post/5a31d5e36fb9a04525782061