目录介绍
-
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.备注
- 建议结合代码,看博客更加高效,项目地址:https://github.com/yangchong211/
- 博客大汇总,持续更新目录说明,记录所有开源项目和博客
- GitHub:https://github.com/yangchong211
- 本项目地址:https://github.com/yangchong211/YCBaseAdapter
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