在Android開發中,列表無數據時,一般會顯示一個空布局。普遍的做法是把列表布局(如:RecyclerView)和空布局都寫在布局文件(xml)里,通過對列表和空布局的隱藏/顯示來切換需要顯示的控件。如果我們有很多頁面都需要顯示這種空布局,就需要每個頁面都要寫重復的代碼。這種方法不僅耦合度高,而且實現和維護麻煩。網上也有把空布局封裝成公共組件,通過給空布局組件設置需要替換的目標控件,把目標控件(如:RecyclerView)替換掉的做法。這兩種做法其實都是通過切換顯示不同的控件來實現功能的,而且都需要我們自己去操作列表和空布局之間的切換。那么有沒有不需要切換控件,就可以讓我們的列表在無數據時自動顯示一個空布局的方法呢。答案肯定是有的,今天我就給大家介紹一種通過給RecyclerView設置空布局item實現空布局顯示的方法。
顯示列表一般是使用RecyclerView,RecyclerView的顯示內容是由Adapter提供和管理的。Adapter知道RecyclerView是否有數據,如果沒有數據時,由Adapter提供一個空布局給RecyclerView顯示,這樣RecyclerView的空布局功能能就實現了。因為RecyclerView支持多種ViewType的item,所以我們可以把空布局作為一個ViewType,。當無數據時,getItemCount返回1,讓RecyclerView顯示一個item,這個item就是我們要顯示的空布局。item的ViewType為TYPE_EMPTY(可隨便定義,只要不跟普通的item沖突即可),item的寬高為match_parent,這樣空布局就可以鋪面RecyclerView。
public class EmptyAdapter extends RecyclerView.Adapter<EmptyAdapter.ViewHolder> {
// 普通的item ViewType
private static final int TYPE_ITEM = 1;
// 空布局的ViewType
private static final int TYPE_EMPTY = 2;
private Context mContext;
// 數據
private List<String> mList;
// 是否顯示空布局,默認不顯示
private boolean showEmptyView = false;
public EmptyAdapter(Context context) {
mContext = context;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == TYPE_EMPTY) {
// 創建空布局item
return new ViewHolder(getEmptyView(parent));
} else {
// 創建普通的item
View view = LayoutInflater.from(mContext).inflate(R.layout.adapter_item, parent, false);
return new ViewHolder(view);
}
}
/**
* 獲取空布局
*/
private View getEmptyView(ViewGroup parent) {
View view = LayoutInflater.from(mContext).inflate(R.layout.adapter_empty_view, parent, false);
Button btnLoadData = view.findViewById(R.id.btn_load_data);
btnLoadData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setList(initData());
}
});
return view;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
// 如果是空布局item,不需要綁定數據
if (!isEmptyPosition(position)) {
holder.tvItem.setText(mList.get(position));
}
}
@Override
public int getItemCount() {
// 判斷數據是否空,如果沒有數據,并且需要顯示空布局,就返回1。
int count = mList != null ? mList.size() : 0;
if (count > 0) {
return count;
} else if (showEmptyView) {
// 顯示空布局
return 1;
} else {
return 0;
}
}
@Override
public int getItemViewType(int position) {
if (isEmptyPosition(position)) {
// 空布局
return TYPE_EMPTY;
} else {
return TYPE_ITEM;
}
}
public void setList(List<String> list) {
mList = list;
notifyDataSetChanged();
}
/**
* 判斷是否是空布局
*/
public boolean isEmptyPosition(int position) {
int count = mList != null ? mList.size() : 0;
return position == 0 && showEmptyView && count == 0;
}
/**
* 設置空布局顯示。默認不顯示
*/
public void showEmptyView(boolean isShow) {
if (isShow != showEmptyView) {
showEmptyView = isShow;
notifyDataSetChanged();
}
}
public boolean isShowEmptyView() {
return showEmptyView;
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView tvItem;
public ViewHolder(@NonNull View itemView) {
super(itemView);
tvItem = itemView.findViewById(R.id.tv_item);
}
}
}
由于列表中的空布局最多只會有一個,所以它不需要在onBindViewHolder中重新綁定數據。我們在創建空布局的時候就可以直接給它設置需要顯示的內容和設置點擊事件。
在上面的例子中,我通過getEmptyView方法提供空布局對象。這樣做是為了讓空布局和adapter解耦,不過在這個例子中沒有明顯的體現出來。試想,如果我們通過一個方法或者接口提供需要的空布局,那么我們就可以讓具體的adapter子類(或者使用者)試下這個方法(實現接口),由子類或者外部提供具體的空布局,讓空布局可自定義,而且不會影響adapter的基本功能。比如我們項目中一般都會封裝一個BaseAdapter類,或者使用第三方開源的BaseAdapter,項目中的adapter都繼承自BaseAdapter。我們可以把空布局的功能集成在BaseAdapter,并且提供默認的通用空布局。然后子類可通過重寫方法提供自己的空布局,這樣就讓空布局可自定義了。把空布局功能集成到adapter后,直接使用這個adapter,在沒有數據時就會顯示空布局了,而且無需我們手動地調用空布局的顯示或者隱藏。
mAdapter = new EmptyAdapter(this);
// 顯示空布局
mAdapter.showEmptyView(true);
rvList.setLayoutManager(new LinearLayoutManager(this));
rvList.setAdapter(mAdapter);
如果是使用GridLayoutManager,為了能讓空布局鋪面RecyclerView,需要setSpanSizeLookup。
final GridLayoutManager layoutManager = new GridLayoutManager(this, 2);
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
// 如果是空布局,讓它占滿一行
if (mAdapter.isEmptyPosition(position)) {
return layoutManager.getSpanCount();
}
return 1;
}
});
rvList.setLayoutManager(layoutManager);
到這里,空布局的實現就完成了。有興趣的朋友可以體驗一下我在GitHub提供的例子:EmptyAdapter。
之所以考慮把空布局功能集成到adapter中,是因為我之前在GitHub開源了一個可分組的RecyclerView Adapter:GroupedRecyclerViewAdapter。后面收到不少反饋,希望GroupedRecyclerViewAdapter能提供空布局的設置,所以我就想出了這種實現方式,并且給GroupedRecyclerViewAdapter集成了這個功能。今天寫這篇文章,是希望能把這種實現方法分享給大家。雖然我們現在是給RecyclerView 的Adapter實現空布局,但是這種思路同樣適用于ListView等列表控件。