在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等列表控件。