目录介绍

  • 1.关于需求介绍

    • 1.1 需求有这些
    • 1.2 封装理念
  • 2.简单封装【V1.0版本】

    • 2.1 封装简单的ViewHolder
    • 2.2 封装简单RecyclerView.Adapter
    • 2.3 如何使用通用adapter
    • 2.4 如何使布局多样化

      • 做法
      • 原理
      • a.定义一个接口,判断返回数据类型
      • b.修改封装adapter中getItemViewType中代码
      • c.修改adapter,实现自定义接口
      • d.在Activity中设置参数location【定义类型参数】
  • 3.简单封装困境

    • 3.1 遇到问题与困境
    • 3.2 用之前封装类实现多种类型布局,出现的弊端
  • 4.关于复杂界面封装

    • 4.1 具体可以看YCRefreshView

0.备注

1.关于需求介绍

  • RecycleView可以满足诸多功能,封装公用的adapter,提高编程效率

1.1 关于需求,大概有这些:

  • 数据的绑定,刷新
  • 多种不同类型的数据绑定
  • 优雅添加头布局或者底布局
  • 增加onItemClickListener , onItenLongClickListener
  • 支持加载相应type错误页面,无数据页面
  • 支持集合set,add,remove,clear等操作刷新

1.2 封装理念

  • 构造一个通用的Adapter模版,避免每添加一个列表就要写一个Adapter,避免写Adapter中的大量重复代码
  • 高内聚,低耦合,扩展方便
  • 通过组装的方式来构建Adapter,将每一种(ViewType不同的)Item抽象成一个单独组件,Adapter 就是一个壳,我们只需要向Adapter中添加Item就行,这样做的好处就是减少耦合,去掉一种item 或者添加一种item对于列表是没有任何影响的

2.简单封装

2.1 封装简单的ViewHolder

  • 首先,继承 RecyclerView.ViewHolder 实现一个通用的 ViewHolder当中,使用 SparseArray 来存放 View 以减少 findViewById 的次数,SparseArray 比 HashMap 更省内存,在某些条件下性能会更好,不过只能存储 key 为 int 类型的数据,正好用来存放资源ID
  • 因为列表项中一般都是使用 TextView,ImageView 等控件,所以这里提供控件的操作方法。此外,为了监听列表项单击和双击事件,这里再来自定义一个接口 onItemCommonClickListener ,用于点击事件回调
/**
 * ================================================
 * 作    者:杨充
 * 版    本:1.0
 * 创建日期:2016/3/9
 * 描    述:ViewHolder的抽取类
 * 修订历史:
 * ================================================
 */
public class BaseViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {

    // SparseArray 比 HashMap 更省内存,在某些条件下性能更好,只能存储 key 为 int 类型的数据,
    // 用来存放 View 以减少 findViewById 的次数
    private SparseArray<View> viewSparseArray;
    //这个是item的对象
    private View mItemView;

    public BaseViewHolder(View itemView) {
        super(itemView);
        this.mItemView = itemView;
        itemView.setOnClickListener(this);
        itemView.setOnLongClickListener(this);
        viewSparseArray = new SparseArray<>();
    }

    /**
     * 根据 ID 来获取 View
     * @param viewId viewID
     * @param <T>    泛型
     * @return 将结果强转为 View 或 View 的子类型
    */
    public <T extends View> T getView(int viewId) {
        // 先从缓存中找,找打的话则直接返回
        // 如果找不到则 findViewById ,再把结果存入缓存中
        View view = viewSparseArray.get(viewId);
        if (view == null) {
            view = itemView.findViewById(viewId);
            viewSparseArray.put(viewId, view);
        }
        return (T) view;
    }

    /**
     * 获取item的对象
     */
    public View getItemView(){
        return mItemView;
    }


    /**
     * 设置TextView的值
     */
    public BaseViewHolder setText(int viewId, String text) {
        TextView tv = getView(viewId);
        tv.setText(text);
        return this;
    }

    /**
     * 设置imageView图片
     */
    public BaseViewHolder setImageResource(int viewId, int resId) {
        ImageView view = getView(viewId);
        view.setImageResource(resId);
        return this;
    }

    /**
     * 设置imageView图片
     */
    public BaseViewHolder setImageBitmap(int viewId, Bitmap bitmap) {
        ImageView view = getView(viewId);
        view.setImageBitmap(bitmap);
        return this;
    }



    @Override
    public void onClick(View v) {
        if (commonClickListener != null) {
            commonClickListener.onItemClickListener(getAdapterPosition());
        }
    }

    @Override
    public boolean onLongClick(View v) {
        if (commonClickListener != null) {
            commonClickListener.onItemLongClickListener(getAdapterPosition());
        }
        return false;
    }

    public interface onItemCommonClickListener {
        void onItemClickListener(int position);
        void onItemLongClickListener(int position);
    }


    private onItemCommonClickListener commonClickListener;
    public void setCommonClickListener(onItemCommonClickListener commonClickListener) {
        this.commonClickListener = commonClickListener;
    }

}

2.2 封装简单RecyclerView.Adapter

  • 因为不知道要使用到的数据类型是哪一种,也为了更好的适配各种数据类型,所以这里需要用到泛型当中,onBindViewHolder(CommonViewHolder holder, int position) 需要我们自己来操作,所以这里再来声明一个抽象方法 bindData(CommonViewHolder holder, T data) ,由子类来负责实现绑定操作
  • 添加简单的设置数据,清理数据,移除数据的方法
/**
 * ================================================
 * 作    者:杨充
 * 版    本:1.0
 * 创建日期:2016/3/9
 * 描    述:RecycleView的adapter抽取类
 * 修订历史:
 * ================================================
 */
public abstract class BaseAdapter<T> extends RecyclerView.Adapter<BaseViewHolder> {

    private Context context;
    private int layoutId;
    private List<T> data;

    /**
     * 构造方法
     * @param layoutId      布局
     * @param context       上下文
     */
    public BaseAdapter(Context context , int layoutId) {
        this.layoutId = layoutId;
        this.context = context;
        data = new ArrayList<>();
    }

    @Override
    public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(layoutId, parent, false);
        return new BaseViewHolder(view);
    }

    @Override
    public void onBindViewHolder(BaseViewHolder holder, int position) {
        if(data!=null && data.size()>0){
            bindData(holder, data.get(position));
        }
    }

    @Override
    public int getItemViewType(int position) {
        return super.getItemViewType(position);
    }

    @Override
    public int getItemCount() {
        return data==null ? 0 : data.size();
    }


    /**
     * 当子类adapter继承此BaseAdapter时,需要子类实现的绑定数据方法
     */
    protected abstract void bindData(BaseViewHolder holder, T t);

    /**
     * 设置数据,并且刷新页面
     */
    public void setData(List<T> list){
        data.clear();
        if(list==null || list.size()==0){
            return;
        }
        data.addAll(list);
        notifyItemRangeChanged(data.size()-list.size(),data.size());
        notifyDataSetChanged();
    }

    /**
     * 获取数据
     */
    public List<T> getData(){
        return data;
    }

    /**
     * 清理所有数据,并且刷新页面
     */
    public void clear(){
        data.clear();
        notifyDataSetChanged();
    }

    /**
     * 移除数据
     */
    public void remove(T t){
        if(data.size()==0){
            return;
        }
        int index = data.indexOf(t);
        remove(index);
    }


    /**
     * 移除数据
     */
    public void remove(int index){
        if(data.size()==0){
            return;
        }
        data.remove(index);
        notifyItemRemoved(index);
    }

    /**
     * 移除数据
     */
    public void remove(int start,int count){
        if(data.size()==0){
            return;
        }
        if((start +count) > data.size()){
            return;
        }
        data.subList(start,start+count).clear();
        notifyItemRangeRemoved(start,count);
    }
}

2.3 如何使用通用adapter

  • 需要先来继承 XXXAdapter ,只需要实现一个方法即可,看起来简洁多了吧。代码中声明了两个构造函数,根据是否需要用到点击事件监听来选择
public class SecondAdapter extends BaseAdapter<String> {

    private BaseViewHolder.onItemCommonClickListener commonClickListener;

    public SecondAdapter(Context context) {
        super(context,R.layout.item_first);

    }

    public SecondAdapter(Context context, BaseViewHolder.onItemCommonClickListener commonClickListener) {
        super(context, R.layout.item_first);
        this.commonClickListener = commonClickListener;
    }

    @Override
    protected void bindData(BaseViewHolder holder, String s) {
        holder.setText(R.id.tv_title,s);
        //TextView view = holder.getView(R.id.tv_title);
        //view.setText(s);
        holder.setCommonClickListener(commonClickListener);
    }
}

2.4 如何使布局多样化

  • 抽取的adapter已经可以为我们节省很多代码了,免去了一些重复性操作。但是如果list列表有多种类型,比如像聊天界面,有聊天文字,图片,文件,红点等多种不同的布局。那么添加使用不同布局的功能十分重要。
  • 做法:
  • 复写getItemViewType,根据我们的bean去返回不同的类型
  • onCreateViewHolder中根据itemView去生成不同的ViewHolder
  • 定义一个接口,判断返回数据类型 需要有一个方法来判断哪种数据类型需要使用哪种布局,所以再来定义一个接口,getLayoutId() 用于返会布局文件ID
public interface MultiTypeSupport<T> {
    int getLayoutId(T item, int position);
}
  • b.修改封装adapter中getItemViewType中代码
  • 修改 XXXAdapter。如果 multiTypeSupport 不为 null,意思就是要使用到不同的布局文件了,则调用 getLayoutId() 方法,将其返回值作为 ItemViewType
@Override
public int getItemViewType(int position) {
    if (multiTypeSupport != null) {
        return multiTypeSupport.getLayoutId(data.get(position), position);
    }
    return super.getItemViewType(position);
}

@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (multiTypeSupport != null) {
        layoutId = viewType;
    }
    View view = LayoutInflater.from(context).inflate(layoutId, parent, false);
    return new BaseViewHolder(view);
}
  • c.修改adapter,实现自定义接口
  • 修改Adapter 类,实现 MultiTypeSupport 接口,根据 T 对象的 location 字段的值,来决定返回哪个布局文件的ID
@Override
public int getLayoutId(ThirdBean item, int position) {
    if (item.getLocation()==1) {
        return R.layout.main_chat_from_msg;
    }
    return R.layout.main_chat_send_msg;
}

public ThirdAdapter(Context context, BaseViewHolder.onItemCommonClickListener commonClickListener) {
    super(context, R.layout.main_chat_from_msg);
    this.commonClickListener = commonClickListener;
    this.multiTypeSupport = this;
}
  • d.在Activity中设置参数location【定义类型参数】
list = new ArrayList<>();
for(int a=0 ; a<13 ; a++){
    if(a==3 || a==8 || a==10 || a==12){
        list.get(a).setTitle("这个是假数据"+a);
        list.get(a).setLocation(1);
    }else {
        list.get(a).setTitle("这个是假数据"+a);
        list.get(a).setLocation(2);
    }
}

3.迭代封装

3.1 遇到问题与困境

  • 前面,我们可以简单实现不同布局类型的。但是大多数的App首页都是比较复杂的,比如一个社交APP的首页,包含Banner区、广告区、文本内容、图片内容、视频内容等等。RecyclerView 可以用ViewType 来区分不同的item,也可以满足需求 ,但还是存在一些问题。

    • 0,如果type的部分实体类参数不同,如何传递setData。即使合并了实体类,但是维护起来十分困难。
    • 1,在item过多逻辑复杂列表界面,Adapter里面的代码量庞大,逻辑复杂,后期难以维护
    • 2,每次增加一个列表都需要增加一个Adapter,重复搬砖,效率低下。

3.2 用之前封装类实现多种类型布局,出现的弊端

  • 0.传递进来的实体类只能是一种,如果处理多种类型的参数不相同,那么合并实体类容易出问题
  • 1.下面这样就是我们通常写一个多Item列表的方法,根据不同的ViewType 处理不同的item,如果逻辑复杂,这个类的代码量是很庞大的。如果版本迭代添加新的需求,修改代码很麻烦,后期维护困难。
public class FourAdapter extends BaseAdapter<FourBean> implements MultiTypeSupport<FourBean>{

    public FourAdapter(Context context) {
        super(context, R.layout.main_chat_from_msg);
        //这句话一点要添加
        this.multiTypeSupport = this;
    }

    @Override
    protected void bindData(BaseViewHolder holder, FourBean s) {
        int location = s.getLocation();
        switch (location){
            case 1:     //处理头部布局逻辑
                holder.setText(R.id.tv_title,s.getTitle());
                break;
            case 2:     //文本逻辑处理
                holder.setText(R.id.tv_title,s.getTitle());
                break;
            case 3:     //图片逻辑处理
                holder.setText(R.id.tv_title,s.getTitle());
                break;
            case 4:     //处理底部布局逻辑
                holder.setText(R.id.tv_title,s.getTitle());
                break;
        }

    }

    @Override
    public int getLayoutId(FourBean item, int position) {
        if (item.getLocation()==1) {
            return R.layout.main_chat_from_msg;
        }else if(item.getLocation()==2){
            return R.layout.item_first;
        } else if(item.getLocation()==4){
            return R.layout.view_footer;
        }
        return R.layout.main_chat_send_msg;
    }
}

4.关于复杂界面封装

4.1 具体可以看YCRefreshView

  • **自定义支持上拉加载更多,下拉刷新,支持自由切换状态【加载中,加载成功,加载失败,没网络等状态】的控件,拓展功能[支持长按拖拽,侧滑删除]可以选择性添加

。具体使用方法,可以直接参考demo。**

  • 轻量级侧滑删除菜单,支持recyclerView,listView,直接嵌套item布局即可使用,整个侧滑菜单思路是:跟随手势将item向左滑动
  • 该库已经用到了实际开发项目中,会持续更新并且修改bug。如果觉得可以,可以star一下,多谢支持!
  • 感谢前辈大神们案例及开源分享精神。
  • 一行代码集成:compile 'org.yczbj:YCRefreshViewLib:2.4'
  • 项目地址:https://github.com/yangchong2...
  • GitHub地址:https://github.com/yangchong211

杨充
221 声望42 粉丝