需求

最近在做的项目中需要有多种类item的RecyclerView(以下缩写为RV),用于在其中插入广告item,带提示信息的item等等。 大概看了一下网上的开源代码,发现大多过于臃肿(代码太多功能太杂),或者是与其他控件有冲突,又或者是我搜索的能力还不够o(╯□╰)o。
于是牙一咬,就决定自己尝试着写一个。

分析与实现

既然已经决定要写了,肯定是要考虑以后在别的地方也能复用而不仅仅是满足于当前的场景的。 因此这个适配器应该要能处理不管什么类型的item。
既然如此,便有两个问题需要解决:

  • 如何存储不同类型的数据
    答:在Java的世界里,所有类都是Object类派生出来的,因此可以将不同类型的数据放到一个Object数组当中
  • 如何区别不同类型数据
    答:在RV的适配器中,ItemViewType是int类型的,而对于不同类型的数据,可以用其类名( .getClass().getName() )来唯一标识。 类名和int之间可以通过Map来进行映射。这样一来就相当于以其类名作为适配器中的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);
    }

当然这当中的name2type也不是凭空来的,用户需要让适配器知道类型信息,同时还要告知不同类型item的处理方法以及对应的布局文件。因此需要提供一个类型注册方法以供用户提供这些信息
值的注意的是,name2type中的type对应着binderInfos中的索引

/**
     * 注册item类型,对于每一种在该RecyclerView中出现的item类型,都需要调用这个函数进行注册
     * @param rClass item类型的class
     * @param binder 待用户实现的数据绑定类
     * @param viewId 这种item对应的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));
    }
    /**
     * 留给外部进行实现的数据与view的绑定类
     */
    abstract static public class ViewBinder {
        abstract public void bindView(View itemView, Object ob, int position);
    }

这样一来就建立了以下的关系:
itemList中的某一个item—–通过name2type—–>ViewType——通过binderInfos.get(ViewType)——>binderInfos(其中包含数据绑定方法和布局id)

有了上述的关系之后,在onCreateViewHolder方法中就可以根据ViewType得到对应的数据绑定类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对象可用于做数据绑定

@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());
            //调用外部传入的binder的绑定数据的接口
            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.
 * 多类型的RecyclerView适配器
 * 对于每一种在该RecyclerView中出现的item类型,都需要调用registerType函数进行注册
 */

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中出现的item类型,都需要调用这个函数进行注册
     * @param rClass item类型的class
     * @param binder 待用户实现的数据绑定类
     * @param viewId 这种item对应的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());
            //调用外部传入的binder的绑定数据的接口
            binder.bindView(itemView, ob, getAdapterPosition());
        }
    }

    /**
     * 留给外部进行实现的数据与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);
// ***************  结束注册item类型  ***************

userLinearManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(userLinearManager);
userAdapter.notifyDataSetChanged();

recyclerView.setAdapter(userAdapter);

上述代码注册了两种类型,分别是User类型和HintTypeItem类型,于是就可以往itemList中添加这两种数据了,类似这样

itemList.add(user);
itemList.add(new HintTypeItem("感兴趣的用户"));
itemList.add(user);

最终的效果:

recyclerview如何判断item是否在屏幕中 recyclerview不同item_recyclerview-adapter