RecyclerView item多佈局顯示新姿勢
問題
RecyclerView正在逐漸取代ListView,在使用RecyclerView中遇到了一個問題,需要在RecyclerView的固定位置設定不同佈局的特殊Item,其中最大的問題在於不同型別data和View佈局如何進行正確對應。
思路
- 將需要顯示的不同型別資料都放到同一個容器中
- 顯示時根據不同的型別載入不同的佈局
實現
在使用RecyclerView時最重要的一個角色是Adapter,所以先從它入手,看下Adapter的程式碼
public class MyAdapter extends RecyclerView.Adapter {
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
}
@Override
public int getItemCount() {
return 0;
}
}
從程式碼中可以看出其中有兩個比較重要比較重要的函式,onCreateViewHolder、onBindViewHolder兩個函式,含義從字面可以理解其含義,主要作用是用來將View和ViewHolder以及ViewHolder和data進行繫結。
我們知道每個不同型別的data將需要不同型別ViewHolder進行繫結,不同型別的ViewHolder也對應著不同的View佈局,所以對於不同data在顯示過程中執行的onCreateViewHolder、onBindViewHolder方法內容都不同,我們可以抽象一個用於繫結的介面如下:
public interface ItemViewBinder<T, H extends RecyclerView.ViewHolder> {
H onCreateViewHolder(LayoutInflater inflater, ViewGroup parent);
void onBindViewHolder(H viewHolder, T t);
}
不同data可以有不同的實現,其中一個實現如下,其他類似:
public class NormalItemViewBinder implements ItemViewBinder<NormalItemBean , NormalItemViewBinder.NormalViewHolder> {
@Override
public NormalViewHolder onCreateViewHolder(LayoutInflater inflater, ViewGroup parent) {
return new NormalViewHolder(inflater.inflate(R.layout.recycle_item_normal, null));
}
@Override
public void onBindViewHolder(NormalViewHolder viewHolder, final NormalItemBean normalItemBean) {
viewHolder.tv.setText(normalItemBean.content);
}
static class NormalViewHolder extends BaseViewHolder {
TextView tv;
public NormalViewHolder(View itemView) {
super(itemView);
tv = itemView.findViewById(R.id.tv);
}
}
}
解決了不同型別的繫結問題,接下來需要面對的問題就是如何得到想要型別的ItemViewBinder例項。
- onCreateViewHolder(ViewGroup parent, int viewType)方法中可以通過viewType引數判斷型別,構造一個viewType和ItemViewBinder的對應關係即可;
- 要使用viewType需要重寫getItemViewType(int position)方法,因為引數只有position,在item的位置和顯示樣式沒有固定對應關係的情況下,position和viewType是沒辦法直接產生聯絡的,通過position可以直接獲得的一般是data,所以可以通過data的型別讓position和viewType產生聯絡,即通過position可以知道該位置的data型別,然後每個型別都會對應著一個viewType
- onBindViewHolder(RecyclerView.ViewHolder holder, int position)方法中傳入的引數除了ViewHolder以外只有一個position引數,同樣position和ItemViewBinder在一般情況下也不會有直接對應關係,我們仍然可以通過data的型別讓兩者產生聯絡,即通過position可以得到data型別,每個data型別都會對應特定的ItemViewBinder
綜合上面問題我們知道需要找到一個合適的資料結構讓viewType、data型別分別與ItemViewBinder例項產生對應關係,可以想到如下資料結構:
- 用兩個Map分別儲存兩兩對應關係
- 利用ArrayMap集合
這裡使用ArrayMap集合實現,主要利用ArrayMap的特性,可以通過index或者key查詢value,我們viewType作為index,讓Class型別作為key,ItemViewBinder例項作為value,實現程式碼如下:
public class TypeTool {
private static volatile TypeTool instance;
private ArrayMap<Class, ItemViewBinder> itemViewBinders;
@TargetApi(Build.VERSION_CODES.KITKAT)
private TypeTool() {
itemViewBinders = new ArrayMap<>();
}
public static TypeTool getInstance() {
if (instance == null) {
synchronized (TypeTool.class) {
if (instance == null) {
instance = new TypeTool();
}
}
}
return instance;
}
public void register(Class clazz, ItemViewBinder itemViewBinder) {
itemViewBinders.put(clazz, itemViewBinder);
}
public int indexOfClass(Class clazz) {
return itemViewBinders.indexOfKey(clazz);
}
public ItemViewBinder getItemViewBinderByIndex(int index) {
return itemViewBinders.valueAt(index);
}
public ItemViewBinder getItemViewBinderByClass(Class clazz) {
return itemViewBinders.get(clazz);
}
public void clear() {
itemViewBinders.clear();
}
}
解決了上面問題基本就完成了RecyclerView的Item多佈局顯示問題,下面是對ItemViewBinder的呼叫方法:
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
List<Object> datas;
LayoutInflater inflater;
TypeTool typeTool;
public MyAdapter(Context context, List<Object> datas) {
inflater = LayoutInflater.from(context);
this.datas = datas;
typeTool = TypeTool.getInstance();
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ItemViewBinder itemViewBinder = typeTool.getItemViewBinderByIndex(viewType);
return itemViewBinder.onCreateViewHolder(inflater, parent);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
ItemViewBinder itemViewBinder = typeTool.getItemViewBinderByClass(datas.get(position).getClass());
itemViewBinder.onBindViewHolder(holder, datas.get(position));
}
@Override
public int getItemCount() {
return datas.size();
}
@Override
public int getItemViewType(int position) {
return typeTool.indexOfClass(datas.get(position).getClass());
}
public void register(Class clazz, ItemViewBinder itemViewBinder) {
typeTool.register(clazz, itemViewBinder);
}
}
Activity中程式碼如下:
public class MainActivity extends AppCompatActivity {
RecyclerView rv;
MyAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
rv = (RecyclerView) findViewById(R.id.rv);
adapter = new MyAdapter(this, getDatas());
rv.setAdapter(adapter);
rv.setLayoutManager(new LinearLayoutManager(this));
initData();
}
public void initData() {
adapter.register(NormalItemBean.class, new NormalItemViewBinder());
adapter.register(ADItemBean.class, new ADItemViewBinder());
}
public List<Object> getDatas() {
List<Object> datas = new ArrayList<>();
for (int i = 0; i < 30; i++) {
datas.add(new NormalItemBean("item:" + i));
}
datas.add(2, new ADItemBean(R.drawable.ad1));
datas.add(8, new ADItemBean(R.drawable.ad2));
return datas;
}
}
效果
如圖,RecyclerView中包含兩種佈局,一種是文字,一種是圖片