轉:關於RecyclerView如何設置空佈局(setEmptyView)的幾種方式

原文鏈接:https://blog.csdn.net/zhangqunshuai/article/details/81238767

前言:
小夥伴在使用ListView的時候,知道listView提供了一個setEmptyView(View view)用來處理當獲取不到數據的時候的界面處理-----用於做些數據爲空的提示等等。

伴隨着RecyclerView的出現,鑑於RecyclerView的可拓展性強且功能更強大等特性,很快俘獲了不少屌絲的芳心。但是我們也很遺憾的發現RecyclerView並沒有提供像listView那樣的setEmptyView()方法。但是兵來將擋,水來土掩。

下面就給大家提供3種可供RecyclerView進行界面爲空處理的方法。
1.在佈局文件中紅控制RecyclerView控件和空佈局的顯示和隱藏
2.重寫RecyclerView(也可以)
3.重寫RecyclerView.Adapter(推薦)

好的,下面進入正題:
 

1.在佈局文件中紅控制RecyclerView控件和空佈局的顯示和隱藏
哈哈,不好意思啊,這個我以前用過,當時的使用場景是:用SwipeRefreshLayout+RecyclerView時,請求數據爲空的時候需要進行界面的處理,於是通過控制RecyclerView的顯示和隱藏來進行數據爲空時候的界面處理。
這種方式自然是可以的,如果你是一個初學者或者趕時間來不及想其他方案的時候,方案一是完全可行的。
要知道萬變不離其宗。其實大家觀察LiistView.setEmptyView(View view)的源碼的時候,大家可以發現

 /**
     * Sets the view to show if the adapter is empty
     */
    @android.view.RemotableViewMethod
    public void setEmptyView(View 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();
        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) {
                mEmptyView.setVisibility(View.VISIBLE);
                setVisibility(View.GONE);
            } else {
                // 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。從源碼上看,就是隱藏顯示。

但是社會在進入,時代在發展。爲什麼不推薦大家使用這用方式呢?
原因有2點:
第一點: 代碼不優雅,如果單單只是一個RecyclerView 也還能接受,但是比如上面的例子,如果在使用SwipeRefreshLayout+RecyclerView時候,可能到時候就不單單控制RecyclerView的顯隱了,SwipeRefreshLayout也是跑不掉的。這樣以來的話,你會發現代碼中到處都是Gone和Visible等等,這樣以來顯得我們代碼寫的很沒有水準。。。知道了吧

第二點: 不易維護,其實這一點和第一點差不多,也是因爲太多的Gone和Visible造成的代碼邏輯混亂,日後不易維護。ListView之所以提供setEmptyView()方法也是體現了java的封裝思想。所以能提供一個方便大家使用,又不會引起代碼混亂的方式應用而生,那就是接下里的兩種方式嘍。
 

2.重寫RecyclerView(不要提到重寫就害怕,很簡單哈)
直接貼上完整代碼,拿來就能用。

package com.example.zq.recyclerviewdemo.module;

/**
 * 用來演示如何在RecyclerView裏面添加setEmptyView
 */
public class RecyclerViewEmptySupport extends RecyclerView {
    private static final String TAG = "RecyclerViewEmptySupport";
    /**
     * 當數據爲空時展示的View
     */
    private View mEmptyView;
    /**
     * 創建一個觀察者
     * *爲什麼要在onChanged裏面寫?
     * * 因爲每次notifyDataChanged的時候,系統都會調用這個觀察者的onChange函數
     * * 我們大可以在這個觀察者這裏判斷我們的邏輯,就是顯示隱藏
     */
    private AdapterDataObserver emptyObserver = new AdapterDataObserver() {
        @SuppressLint("LongLogTag")
        @Override
        public void onChanged() {
            Log.i(TAG, "onChanged: 000");
            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;
    }

    @SuppressLint("LongLogTag")
    @Override
    public void setAdapter(Adapter adapter) {
        super.setAdapter(adapter);
        Log.i(TAG, "setAdapter: adapter::" + adapter);
        if (adapter != null) {
            //這裏用了觀察者模式,同時把這個觀察者添加進去,
            // 至於這個模式怎麼用,谷歌一下,不多講了,因爲這個涉及到了Adapter的一些原理,感興趣可以點進去看看源碼,還是受益匪淺的
            adapter.registerAdapterDataObserver(emptyObserver);
        }
        //當setAdapter的時候也調一次(實際上,經我粗略驗證,不添加貌似也可以。不行就給添上唄,多大事嘛)
        emptyObserver.onChanged();
    }
}

我們在RecyclerView裏面添加了一個成員變量emptyObserver,這個作用就是用於觀察每次Adapter進行數據刷新的時候都調用一次觀察者的onChange(),至於爲什麼會調,這個就說得有點遠了,暫時不說了。先學會用。另外有一點需要說一下,代碼中的new AdapterDataObserver()…觀察者是RecyclerView控件自身提供的,不是我們自定義出來的哦。回到上面來,我們看到我們的onChange裏面的代碼,不就是跟ListView裏面的一樣嗎,沒錯,我想說的第一種方式就是借鑑了ListView的做法哦。
 

另外關於觀察者模式的使用,網上一大堆,簡明扼要即使一對多的接口回調。順便說一下,EventBus和廣播其實也屬於觀察者模式的使用示例。

好了,別的不扯了,看看引用,佈局引用也很簡單。自定義的RecyclerViewEmptySupport和空的佈局文件。
直接貼出:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
   
    <com.example.zq.recyclerviewdemo.module.RecyclerViewEmptySupport
        android:id="@+id/recy_empty"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

     <!--todo 自定義的空佈局-->
    <include layout="@layout/empty_view_tab" />

</LinearLayout>

 

3.重寫RecyclerView.Adapter(推薦)
我們知道,RecyclerView的出現,更大程度給了開發者去自定義自己希望的佈局,RecyclerView可以通過引入不同的ViewType進行不同的列表顯示,舉個列子:及時通訊的聊天記錄,一般都會有左邊的佈局跟右邊的佈局,那麼,就有兩個不同的ViewType了,根據不同情況進行引入不同的ViewType。(哈哈,這個我也做過耶)好,那麼,我們也可以根據我們的情況進行引入佈局啊,例如,如果數據爲空的時候,那我能不能在我們的Adapter裏面引入一個emptyView這樣的佈局。

package com.example.zq.recyclerviewdemo.module;
/**
 * 適配器,模擬列表
 */
public class EmptyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements View.OnClickListener {

    private static final String TAG = "EmptyAdapter";

    private EmptyAdapter.onRecyclerViewListener onRecyclerViewListener;
    private List<Person> mList;

    /**
     * viewType--分別爲item以及空view
     */
    public static final int VIEW_TYPE_ITEM = 1;
    public static final int VIEW_TYPE_EMPTY = 0;

    public EmptyAdapter(List<Person> datas) {
        mList = datas;
    }


    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //在這裏根據不同的viewType進行引入不同的佈局
        if (viewType == VIEW_TYPE_EMPTY) {
            View emptyView = LayoutInflater.from(parent.getContext()).inflate(R.layout.empty_view_tab, parent, false);

            return new RecyclerView.ViewHolder(emptyView) {

            };

        }
        //其他的引入正常的
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_item_person_info, parent, false);
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
        if (holder instanceof MyViewHolder) {
            MyViewHolder hd = (MyViewHolder) holder;
            Person person = mList.get(position);
            hd.ivEditHead.setTag(position);
            hd.ivDelete.setTag(position);
            hd.tvPersonInfo.setText(person.getAddress());
            hd.ivEditHead.setImageResource(person.getHeadIconId());
            hd.ivEditHead.setOnClickListener(this);
            hd.ivDelete.setOnClickListener(this);
        }
    }

    @Override
    public int getItemCount() {
        //同時這裏也需要添加判斷,如果mData.size()爲0的話,只引入一個佈局,就是emptyView
        // 那麼,這個recyclerView的itemCount爲1
        if (mList.size() == 0) {
            return 1;
        }
        //如果不爲0,按正常的流程跑
        return mList.size();
    }

    @Override
    public int getItemViewType(int position) {
        //在這裏進行判斷,如果我們的集合的長度爲0時,我們就使用emptyView的佈局
        if (mList.size() == 0) {
            return VIEW_TYPE_EMPTY;
        }
        //如果有數據,則使用ITEM的佈局
        return VIEW_TYPE_ITEM;
    }


    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.iv_edit_head:
                int position = (int) v.getTag();
                Log.i(TAG, "onClick: position:::" + position);
                onRecyclerViewListener.editHeadIcon(position);
                break;
            case R.id.iv_delete:
                int position1 = (int) v.getTag();
                onRecyclerViewListener.deleteInfo(position1);
                break;
            default:
                break;
        }
    }


    class MyViewHolder extends RecyclerView.ViewHolder {

        ImageView ivEditHead;
        TextView tvPersonInfo;
        ImageView ivDelete;

        public MyViewHolder(View itemView) {
            super(itemView);
            ivEditHead = itemView.findViewById(R.id.iv_edit_head);
            tvPersonInfo = itemView.findViewById(R.id.tv_person_info);
            ivDelete = itemView.findViewById(R.id.iv_delete);
        }
    }

    public void setOnRecyclerViewListener(EmptyAdapter.onRecyclerViewListener onRecyclerViewListener) {
        this.onRecyclerViewListener = onRecyclerViewListener;
    }

    public interface onRecyclerViewListener {
        void editHeadIcon(int position);

        void deleteInfo(int position);
    }
}

這種做法,就是把我們的emptyView設置放進去Adapter,根據不同的情況引入不同的佈局,跟第一的區別就是顯示與否都交給系統去處理,通過引入不同佈局的做法達到了顯示emptyView的效果。
 
前方高能。。。。。

別慌張,只是爲了引起你的重視而已。

在使用第三種方式,設置空佈局的時候,發下現RelativeLayout中的子view的layout_height="match_parent"不起作用。

還是先把空佈局代碼貼出來吧。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorAccent"
    android:gravity="center">


    <ImageView
        android:id="@+id/iv_empty"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:src="@mipmap/empty_view_tab" />

    <TextView
        android:id="@+id/empty_view_message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/iv_empty"
        android:layout_gravity="center"
        android:text="暫無內容下拉刷新"
        android:textColor="@android:color/darker_gray"
        android:textSize="12sp" />

</RelativeLayout>

 
爲了方便演示問題所在,我將空佈局的背景設置成了紅色。


空佈局界面上述處理沒啥問題吧。然而,運行過程中當數據爲空時確是這樣的。


哎醜到爆,你會發現根佈局中的 android:layout_height="match_parent"沒有起作用,首先有一點你不用懷疑,紅色區域的高度也不等於設置的子項的item的高度哦,這一點我已經證實。另外如果指定了高度(只要不是match_parent都可以)那麼佈局就會起作用。找度娘要答案,前篇一律的說應該加載空佈局時應該使用

   View emptyView = LayoutInflater.from(parent.getContext()).inflate(R.layout.empty_view_tab, parent, false);
而不能使用

   View emptyView = LayoutInflater.from(parent.getContext()).inflate(R.layout.empty_view_tab, null);
等等。
 
一般情況加載佈局這樣用是沒問題的,但是,我已經使用的是上面inflate(R.layout.empty_view_tab, parent, false);的佈局了啊,還是沒有起作用啊。

好在找到了一個靠譜點的答案,詳情參考此篇文章:
RelativeLayout中的子view的layout_height="match_parent"不管用

劃重點:
造成這種情況的原因:

其實不是RelativeLayout中的子view的layout_height="match_parent"不管用,而是relativeLayout的layout_height沒起作用。

listview(我們這裏用的是RecyclerView)的item的高度是適應於item內部的容納view的最大高度的,也就是說,他自己是無法設置高度的,這個類似於marginTop(或者bottom)不起作用。

而子view的match_parent又是適應於parent的最大高度,因此子view的高度成爲了item內所有子view的最大高度,最終成爲了大家都是wrap_parent。

好啦,文章給出了答案,我就不再解釋了。那麼怎麼能讓方案三正常使用呢?
答案:更改空佈局。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorAccent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_centerInParent="true"
        android:gravity="center"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/iv_empty"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:src="@mipmap/empty_view_tab" />

        <TextView
            android:id="@+id/empty_view_message"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:text="暫無內容下拉刷新"
            android:textColor="@android:color/black"
            android:textSize="12sp" />

    </LinearLayout>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="1dp"
        android:layout_alignParentBottom="true" />

</RelativeLayout>

這樣就可以了,只需要添加一個可以用到**android:layout_alignParentBottom=“true”**屬性的TextView即可,這樣系統就會認爲我們的空佈局的高度就是填充屏幕的高度。這樣就可以了。
實在不行,你也可以使用TextView的 android:drawableTop="@drawable/ic_launcher" 屬性來添加圖片也是可以的。
 
看看最終的效果顯示:(不要急,模擬5s後就會自動進入空佈局)


相信大家都會在想,哪一種方式更好用,這得看個人的需求,但我更傾向於用第二種,因爲google這兩年提供了許多很好的資源給我們開發者使用,最熱的莫過於是support-design包裏的一些新控件,tabLayout、toolbar、CoordinatorLayout等等,但如果想要更好的使用它們很炫的一些效果,得好好了解一下NestedScrollingParent這個接口,google提供了這些接口很好的處理了事件分發的處理,而RecyclerView均實現了這些接口,能很好的配合support-design使用其特效。說的啥意思呢?
簡單粗暴,就是RecyclerView的配合使用很多,能少動RecyclerView就少動,不然在日後配套使用的時候可能會遇到別的問題,因此就改動Adapter嘍。
————————————————
原文鏈接:https://blog.csdn.net/zhangqunshuai/article/details/81238767

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章