YCBaseAdapter封装控件

简介:

目录介绍

  • 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/yangchong211/YCRefreshView
  • GitHub地址:https://github.com/yangchong211
目录
相关文章
|
存储 Cloud Native Linux
C++Qt表格控件一些常用的设置封装
C++Qt表格控件一些常用的设置封装
|
关系型数据库 MySQL C#
C# winform 一个窗体需要调用自定义用户控件的控件名称
给用户控件ucQRCode增加属性: //二维码图片 private PictureBox _pictureBoxFSHLQrCode; public PictureBox PictureBoxFSHLQrCode {   get { return _pictureBoxFSHLQrCode; }   set { this.pictureBoxFSHLQrCode = value; } } 在Form1窗体直接调用即可: ucQRCode uQRCode=new ucQRCode(); ucQRCode.PictureBoxFSHLQrCode.属性= 要复制或传给用户控件上的控件的值
71 0
|
小程序 JavaScript 前端开发
小程序封装加载动画
在小程序的开发中,页面的加载过程可能会因为网络状况的不好或数据量的过大而显得非常缓慢,这时候加上一个加载动画就能有效的缓解用户的等待焦虑感。而对于应用的多个页面来说,使用全局加载动画可以提高用户体验,让应用显得更加美观和专业。本篇技术分享博客将为大家介绍在小程序中封装全局加载动画的具体实现步骤,帮助您提高小程序的用户体验。通过上述步骤,我们就完成了小程序中封装全局加载动画的具体实现方法。在实际开发中,我们可以根据实际需求对组件样式和方法进行调整和修改,以满足不同的开发需求。
247 0
|
前端开发
封装avalonia指定组件允许拖动的工具类
封装avalonia指定组件允许拖动的工具类
172 0
Silverlight自定义数据绑定控件应该如何处理IEditableObject和IEditableCollectionView对象
原文:Silverlight自定义数据绑定控件应该如何处理IEditableObject和IEditableCollectionView对象 原创文章,如需转载,请注明出处。   最近在一直研究Silverlight下的数据绑定控件,发现有这样两个接口IEditableObject 和IEditableCollectionView,记录一下结论,欢迎交流指正。
870 0
|
C#
WPF查找子控件和父控件方法
原文:WPF查找子控件和父控件方法 public List GetChildObjects(DependencyObject obj, string name) where T : FrameworkElement { Dependen...
1411 0