前言:
小夥伴在使用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