需求
最近在做的項(xiàng)目中需要有多種類item的RecyclerView(以下縮寫為RV),用于在其中插入廣告item,帶提示信息的item等等。 大概看了一下網(wǎng)上的開源代碼,發(fā)現(xiàn)大多過于臃腫(代碼太多功能太雜),或者是與其他控件有沖突,又或者是我搜索的能力還不夠o(╯□╰)o。
于是牙一咬,就決定自己嘗試著寫一個。
分析與實(shí)現(xiàn)
既然已經(jīng)決定要寫了,肯定是要考慮以后在別的地方也能復(fù)用而不僅僅是滿足于當(dāng)前的場景的。 因此這個適配器應(yīng)該要能處理不管什么類型的item。
既然如此,便有兩個問題需要解決:
- 如何存儲不同類型的數(shù)據(jù)
答:在Java的世界里,所有類都是Object類派生出來的,因此可以將不同類型的數(shù)據(jù)放到一個Object數(shù)組當(dāng)中 - 如何區(qū)別不同類型數(shù)據(jù)
答:在RV的適配器中,ItemViewType是int類型的,而對于不同類型的數(shù)據(jù),可以用其類名( .getClass().getName() )來唯一標(biāo)識。 類名和int之間可以通過Map來進(jìn)行映射。這樣一來就相當(dāng)于以其類名作為適配器中的ItemViewType
于是適配器中的getItemViewType方法可以寫成下面這樣:
private List<Object> itemList = new ArrayList<>();
@Override
public int getItemViewType(int position) {
String name = itemList.get(position).getClass().getName();
return name2type.get(name);
}
當(dāng)然這當(dāng)中的name2type也不是憑空來的,用戶需要讓適配器知道類型信息,同時還要告知不同類型item的處理方法以及對應(yīng)的布局文件。因此需要提供一個類型注冊方法以供用戶提供這些信息
值的注意的是,name2type中的type對應(yīng)著binderInfos中的索引
/**
* 注冊item類型,對于每一種在該RecyclerView中出現(xiàn)的item類型,都需要調(diào)用這個函數(shù)進(jìn)行注冊
* @param rClass item類型的class
* @param binder 待用戶實(shí)現(xiàn)的數(shù)據(jù)綁定類
* @param viewId 這種item對應(yīng)的layoutID
*/
public void registerType(Class rClass, ViewBinder binder, int viewId){
String className = rClass.getName();
name2type.put(className, binderInfos.size());
binderInfos.add(new BinderInfo(binder, viewId));
}
/**
* 留給外部進(jìn)行實(shí)現(xiàn)的數(shù)據(jù)與view的綁定類
*/
abstract static public class ViewBinder {
abstract public void bindView(View itemView, Object ob, int position);
}
這樣一來就建立了以下的關(guān)系:
itemList中的某一個item-----通過name2type----->ViewType------通過binderInfos.get(ViewType)------>binderInfos(其中包含數(shù)據(jù)綁定方法和布局id)
有了上述的關(guān)系之后,在onCreateViewHolder方法中就可以根據(jù)ViewType得到對應(yīng)的數(shù)據(jù)綁定類Binder和布局id
@Override
public MTypeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
MTypeViewHolder viewHolder;
ViewBinder binder = binderInfos.get(viewType).binder;
int viewId = binderInfos.get(viewType).id;
view = LayoutInflater.from(parent.getContext()).inflate(viewId, parent, false);
viewHolder = new MTypeViewHolder(binder, view);
return viewHolder;
}
因此每個ViewHolder都有了自己的Binder對象可用于做數(shù)據(jù)綁定
@Override
public void onBindViewHolder(MTypeViewHolder holder, int position) {
holder.bindView();
}
class MTypeViewHolder extends RecyclerView.ViewHolder {
ViewBinder binder;
public MTypeViewHolder(ViewBinder binder, View itemView) {
super(itemView);
this.binder = binder;
}
public void bindView(){
Object ob = itemList.get(getAdapterPosition());
//調(diào)用外部傳入的binder的綁定數(shù)據(jù)的接口
binder.bindView(itemView, ob, getAdapterPosition());
}
}
完整代碼以及示例
完整代碼:
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by LaiXiancheng on 2018/1/27.
* Email: lxc.sysu@qq.com
* 多類型的RecyclerView適配器
* 對于每一種在該RecyclerView中出現(xiàn)的item類型,都需要調(diào)用registerType函數(shù)進(jìn)行注冊
*/
public class MultiTypeRVAdapter extends RecyclerView.Adapter<MultiTypeRVAdapter.MTypeViewHolder> {
private List<Object> itemList = new ArrayList<>();
private Map<String, Integer> name2type = new HashMap<>();
private List<BinderInfo> binderInfos= new ArrayList<>();
public MultiTypeRVAdapter(List<Object> itemList) {
this.itemList = itemList;
}
@Override
public int getItemViewType(int position) {
String name = itemList.get(position).getClass().getName();
return name2type.get(name);
}
/**
* 注冊item類型,對于每一種在該RecyclerView中出現(xiàn)的item類型,都需要調(diào)用這個函數(shù)進(jìn)行注冊
* @param rClass item類型的class
* @param binder 待用戶實(shí)現(xiàn)的數(shù)據(jù)綁定類
* @param viewId 這種item對應(yīng)的layoutID
*/
public void registerType(Class rClass, ViewBinder binder, int viewId){
String className = rClass.getName();
name2type.put(className, binderInfos.size());
binderInfos.add(new BinderInfo(binder, viewId));
}
@Override
public MTypeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
MTypeViewHolder viewHolder;
ViewBinder binder = binderInfos.get(viewType).binder;
int viewId = binderInfos.get(viewType).id;
view = LayoutInflater.from(parent.getContext()).inflate(viewId, parent, false);
viewHolder = new MTypeViewHolder(binder, view);
return viewHolder;
}
@Override
public void onBindViewHolder(MTypeViewHolder holder, int position) {
holder.bindView();
}
@Override
public int getItemCount() {
return itemList.size();
}
class MTypeViewHolder extends RecyclerView.ViewHolder {
ViewBinder binder;
public MTypeViewHolder(ViewBinder binder, View itemView) {
super(itemView);
this.binder = binder;
}
public void bindView(){
Object ob = itemList.get(getAdapterPosition());
//調(diào)用外部傳入的binder的綁定數(shù)據(jù)的接口
binder.bindView(itemView, ob, getAdapterPosition());
}
}
/**
* 留給外部進(jìn)行實(shí)現(xiàn)的數(shù)據(jù)與view的綁定類
*/
abstract static public class ViewBinder {
abstract public void bindView(View itemView, Object ob, int position);
}
private class BinderInfo{
ViewBinder binder;
int id;
BinderInfo(ViewBinder binder, int id) {
this.binder = binder;
this.id = id;
}
}
}
示例:
recyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerView_user);
itemList = new ArrayList<>();
itemList.add(new HintTypeItem("感興趣的用戶"));
userAdapter = new MultiTypeRVAdapter(itemList);
// *************** 注冊item類型 ***************
userAdapter.registerType(User.class, new MultiTypeRVAdapter.ViewBinder(){
//注冊User類型的item
@Override
public void bindView(View itemView, Object ob, int position) {
TextView tv_nickname = itemView.findViewById(R.id.tv_nickname);
TextView tv_fade_name = itemView.findViewById(R.id.tv_fade_name);
ImageView iv_header = itemView.findViewById(R.id.iv_header);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(context,OtherActivity.class);
intent.putExtra("user_id",user.getUser_id());
activity.startActivity(intent);
}
});
User user = (User)ob;
ViewGroup.LayoutParams layoutParams1 = tv_nickname.getLayoutParams();
layoutParams1.height = 80;
tv_nickname.setLayoutParams(layoutParams1);
tv_nickname.setText(Html.fromHtml(user.getNickname()));
tv_fade_name.setText(Html.fromHtml(user.getFade_name()));
Glide.with(context).load(Const.BASE_IP + user.getHead_image_url()).into(iv_header);
}
}, R.layout.item_user);
userAdapter.registerType(HintTypeItem.class, new MultiTypeRVAdapter.ViewBinder() {
//注冊HintTypeItem類型的item
@Override
public void bindView(View itemView, Object ob, int position) {
TextView textView = itemView.findViewById(R.id.tv_hint);
textView.setText(((HintTypeItem)ob).getHint());
}
}, R.layout.random_item);
// *************** 結(jié)束注冊item類型 ***************
userLinearManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(userLinearManager);
userAdapter.notifyDataSetChanged();
recyclerView.setAdapter(userAdapter);
上述代碼注冊了兩種類型,分別是User類型和HintTypeItem類型,于是就可以往itemList中添加這兩種數(shù)據(jù)了,類似這樣
itemList.add(user);
itemList.add(new HintTypeItem("感興趣的用戶"));
itemList.add(user);