前言
最近开始了实习,熟悉项目时,发现一些RecyclerView的Adapter类都继承了一个名为BaseQuickAdapter的类,百度得知这是由BRVAH(官方网站)提供的万用适配器,相比原始的适配器,能减少70%的代码。遂学习之,学习后发现针不戳,只要熟悉RecyclerView的基础使用即可快速上手,并且框架高度封装,能极大提高开发效率。另外,不熟悉RecyclerView使用的同学可以看看我之前的文章:Android——RecyclerView使用汇总
框架引入
首先在build.gradle(Project: YourProjectName)中添加:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
注意: 此文件下的repositories有两个,分别在buildscript和allprojects下,配置在allprojects下才是正确的,不要配置错。
然后在build.gradle(Module: app)中添加依赖:
dependencies {
...
implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4'
}
注意: 由于BRAVH从2.x升级到3.x为不完全兼容升级(官方原话),因此不同版本之间的代码差异可能较大,本文以3.0.4版本为准。(PS:网络上大部分教程似乎都仍是2.x版本,我之前的demo也是用的是2.x版本,后来换成3.x版本后报红了一大片…)
基础使用
假设我们的需求是在页面显示多张图片以及它们的名称,那么我们首先需要一个存储图片路径和图片名称的Bean类以及用于展示它们的布局文件,布局文件十分简单,避免篇幅过长就不展示了。
public class ItemBean {
private String text;
private int imgResId;
/* 省略构造方法和Get/Set方法 */
}
然后需要一个Adapter将数据和布局与RecyclerView绑定。创建MyAdapter并继承BaseQuickAdapter<T, VH>,第一个泛型对应数据类型,就是ItemBean;第二个泛型对应ViewHolder,一般直接填写BaseViewHolder即可。接着实现相关方法,此时我们的适配器大概是这个模样:
public class MyAdapter extends BaseQuickAdapter<ItemBean, BaseViewHolder> {
public MyAdapter(int layoutResId, @Nullable List<ItemBean> data) {
super(layoutResId, data);
}
public MyAdapter(int layoutResId) {
super(layoutResId);
}
@Override
protected void convert(final BaseViewHolder helper, final ItemBean item) {
}
}
- 两个构造方法比较好理解,参数分别是布局资源ID和初始数据。
- convert方法类似原始适配器中的onBindViewHolder方法,用于绑定数据、设置事件等。
最后,完成convert方法,再为RecyclerView设置此适配器即完成了最基本的使用。
protected void convert(final BaseViewHolder helper, final ItemBean item) {
//为指定ID的组件设置属性
helper.setText(R.id.item_tv,item.getText());
helper.setImageResource(R.id.item_iv,item.getImgResId());
//上下两种方法都行
//先获取指定组件,再设置属性
//TextView textView = helper.getView(R.id.item_tv);
//textView.setText(item.getText());
//ImageView imageView = helper.getView(R.id.item_iv);
//imageView.setImageResource(item.getImgResId());
}
//初始化RecyclerView
RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new GridLayoutManager(this,3));
//初始化数据
List<ItemBean> dataList = initData();
//初始化适配器
MyAdapter myAdapter = new MyAdapter(R.layout.layout_item);
myAdapter.setNewInstance(dataList);
//设置适配器
recyclerView.setAdapter(myAdapter);
添加空布局
myAdapter.setRecyclerView(recyclerView);
myAdapter.setEmptyView(R.layout.layout_empty);
recyclerView.setAdapter(myAdapter);
12月18日更新
此处说明有误,适配器设置空布局的方法中需要RecyclerView实例,所以调用该方法之前需要先绑定对应的RecyclerView。即先调用recylerView.setAdapter(myAdapter);
或myAdapter.setRecyclerView(recyclerView);
方法,此处需要注意代码逻辑顺序,以下代码与上述代码实现效果一致。
recyclerView.setAdapter(myAdapter);
myAdapter.setEmptyView(R.layout.layout_empty);
添加头部和尾部
添加头部和尾部并没有像添加空布局一样有接收布局ID的方法,需要手动填充。
//设置头部和尾部
View footView = LayoutInflater.from(this).inflate(R.layout.layout_foot, recyclerView, false);
myAdapter.setFooterView(footView);
View headView = LayoutInflater.from(this).inflate(R.layout.layout_head, recyclerView, false);
myAdapter.setHeaderView(headView);
添加事件
事件的对象可以是Item,也可以是Item中的子控件,事件可以是点击或者长按。下面分别介绍如何添加Item和Item子控件的点击事件,长按事件类似则不过多赘述。
Item事件
//设置Item点击事件
myAdapter.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(@NonNull BaseQuickAdapter<?, ?> adapter, @NonNull View view, int position) {
Toast.makeText(MainActivity.this,"你点击了Item" + (position + 1), Toast.LENGTH_SHORT).show();
}
});
Item子控件事件
添加Item子控件事件的方式有两种,一种是在Adapter的convert方法中获取控件,然后设置;另一种方式则与设置Item事件类似,不过需要先注册对应的控件。
- 在convert方法中设置
protected void convert(final BaseViewHolder helper, final ItemBean item) {
TextView textView = helper.getView(R.id.item_tv);
textView.setText(item.getText());
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getContext(),"你点击了TextView" + (helper.getAdapterPosition() + 1), Toast.LENGTH_SHORT).show();
}
});
ImageView imageView = helper.getView(R.id.item_iv);
imageView.setImageResource(item.getImgResId());
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getContext(),"你点击了ImageView" + (helper.getAdapterPosition() + 1), Toast.LENGTH_SHORT).show();
}
});
}
- 使用adapter设置
myAdapter.addChildClickViewIds(R.id.item_iv,R.id.item_tv); //注册对应的控件
myAdapter.setOnItemChildClickListener(new OnItemChildClickListener() {
@Override
public void onItemChildClick(@NonNull BaseQuickAdapter adapter, @NonNull View view, int position) {
if (view instanceof TextView) {
Toast.makeText(MainActivity.this,"你点击了TextView" + (position + 1), Toast.LENGTH_SHORT).show();
} else if (view instanceof ImageView) {
Toast.makeText(MainActivity.this,"你点击了ImageView" + (position + 1), Toast.LENGTH_SHORT).show();
}
}
});
添加加载动画
myAdapter.setAnimationWithDefault(AnimationType type);
默认提供五种动画类型:渐显、缩放、从下方滑入、从左侧滑入、从右侧滑入。
/**
* 内置默认动画类型
*/
enum class AnimationType {
AlphaIn, ScaleIn, SlideInBottom, SlideInLeft, SlideInRight
}
实现效果:在主界面新增一个按钮,当点击按钮时为列表新增一个数据,并使其缩放显示。
//添加加载动画
myAdapter.setAnimationWithDefault(BaseQuickAdapter.AnimationType.ScaleIn);
//增加数据
Button button = findViewById(R.id.add_btn);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
myAdapter.addData(new ItemBean("新增的图片",R.drawable.pic11));
}
});
上拉加载
简单使用
使用此功能首先需要让适配器实现LoadMoreModule接口:
public class MyAdapter extends BaseQuickAdapter<ItemBean, BaseViewHolder> implements LoadMoreModule
然后从适配器获取到这个LoadMoreModule并设置监听事件即可:
//上拉加载
myAdapter.getLoadMoreModule().setOnLoadMoreListener(new OnLoadMoreListener() {
@Override
public void onLoadMore() {
//模拟加载数据
List<ItemBean> newData = new ArrayList<>();
newData.add(new ItemBean("爷",R.drawable.pic17));
newData.add(new ItemBean("惊了",R.drawable.pic16));
newData.add(new ItemBean("爷",R.drawable.pic17));
myAdapter.addData(newData);
//加载结束
Toast.makeText(MainActivity.this,"加载数据成功",Toast.LENGTH_SHORT).show();
myAdapter.getLoadMoreModule().loadMoreComplete();
}
});
相关功能
关闭自动加载
当需求希望用户手动点击加载数据时,可以开启此功能。
setAutoLoadMore(false);
//handler
handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
if (msg.what == 200) {
List<ItemBean> newData = new ArrayList<>();
newData.add(new ItemBean("针", R.drawable.pic18));
newData.add(new ItemBean("牛", R.drawable.pic19));
newData.add(new ItemBean("啤", R.drawable.pic20));
myAdapter.addData(newData);
//加载完成
myAdapter.getLoadMoreModule().loadMoreComplete();
}
}
};
//上拉加载
myAdapter.getLoadMoreModule().setAutoLoadMore(false);//关闭自动加载
myAdapter.getLoadMoreModule().setOnLoadMoreListener(new OnLoadMoreListener() {
@Override
public void onLoadMore() {
//模拟加载数据
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message msg = new Message();
msg.what = 200;
handler.sendMessage(msg);
}
}).start();
}
});
加载结束
当没有更多数据时,开启此功能。
loadMoreEnd();
//handler
handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
if (msg.what == 200) {
//省略部分代码
//加载结束
myAdapter.getLoadMoreModule().loadMoreEnd();
}
}
};
加载成功/失败
与加载结束类似,根据后台获取数据的回调分别调用即可。
loadMoreComplete();//加载成功
loadMoreFail();//加载失败
自定义提示布局
需求总是花里胡哨的,自定义布局总是不可避免的。加载更多数据时,一般有五种状态——未加载(等待加载)、加载中、加载失败、加载成功、加载结束(没有数据了)。其中,加载成功一般会伴随着新数据的显示,所以不需要特别提示用户。那么,首先为其余四种状态编写不同的提示布局:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_40">
<LinearLayout
android:id="@+id/load_more_loading_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal">
<ProgressBar
android:id="@+id/loading_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleSmall"
android:layout_marginRight="@dimen/dp_4"
android:indeterminateDrawable="@drawable/brvah_sample_footer_loading_progress"/>
<TextView
android:id="@+id/loading_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/dp_4"
android:text="正在加载中..."
android:textColor="#0dddb8"
android:textSize="@dimen/sp_14"/>
</LinearLayout>
<FrameLayout
android:id="@+id/load_more_load_fail_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<TextView
android:id="@+id/tv_prompt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#0dddb8"
android:text="加载失败,点👴重试"/>
</FrameLayout>
<FrameLayout
android:id="@+id/load_more_load_complete_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="👇点击查看更多👇"
android:textColor="@android:color/darker_gray"/>
</FrameLayout>
<FrameLayout
android:id="@+id/load_more_load_end_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="没有数据了,👴很生气"
android:textColor="@android:color/darker_gray"/>
</FrameLayout>
</FrameLayout>
然后,我们只需根据规则,创建一个类继承BaseLoadMoreView,并重写相关方法,在对应的方法下返回对应的容器ID即可。
public class CustomLoadMoreView extends BaseLoadMoreView {
@NotNull
@Override
public View getRootView(@NotNull ViewGroup parent) {
return LayoutInflater.from(parent.getContext()).inflate(R.layout.view_load_more, parent, false);
}
@NotNull
@Override
public View getLoadingView(@NotNull BaseViewHolder holder) {
return holder.findView(R.id.load_more_loading_view);
}
@NotNull
@Override
public View getLoadComplete(@NotNull BaseViewHolder holder) {
return holder.findView(R.id.load_more_load_complete_view);
}
@NotNull
@Override
public View getLoadEndView(@NotNull BaseViewHolder holder) {
return holder.findView(R.id.load_more_load_end_view);
}
@NotNull
@Override
public View getLoadFailView(@NotNull BaseViewHolder holder) {
return holder.findView(R.id.load_more_load_fail_view);
}
}
最后,将编写好的CustomLoadMoreView设置到适配器中,并对代码做一些修改来模拟不同的加载状态。
//handler
handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case 200:
List<ItemBean> newData = new ArrayList<>();
newData.add(new ItemBean("针", R.drawable.pic18));
newData.add(new ItemBean("牛", R.drawable.pic19));
newData.add(new ItemBean("啤", R.drawable.pic20));
myAdapter.addData(newData);
//加载完成
myAdapter.getLoadMoreModule().loadMoreComplete();
break;
case 201:
myAdapter.getLoadMoreModule().loadMoreFail();
break;
case 202:
myAdapter.getLoadMoreModule().loadMoreEnd();
break;
}
}
};
//上拉加载
myAdapter.getLoadMoreModule().setLoadMoreView(new CustomLoadMoreView());//👈👈👈
myAdapter.getLoadMoreModule().setAutoLoadMore(false);
myAdapter.getLoadMoreModule().setOnLoadMoreListener(new OnLoadMoreListener() {
@Override
public void onLoadMore() {
//模拟加载数据
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message msg = new Message();
switch (count) {
case 0:
//模拟加载成功
msg.what = 200;
break;
case 1:
//模拟加载失败
msg.what = 201;
break;
default:
//模拟没有更多数据
msg.what = 202;
break;
}
count++;
handler.sendMessage(msg);
}
}).start();
}
});
侧滑和拖拽
此框架中,加载更多(LoadMore)和拖拽(Drag)都是单独的模块,需要根据需求集成。类似LoadMoreModule,使用拖拽和侧滑功能之前,需要让适配器实现相应接口。
public class MyAdapter extends BaseQuickAdapter<ItemBean, BaseViewHolder> implements DraggableModule
并且大部分功能逻辑已经集成在框架中,接下来只需要让适配器允许侧滑和拖拽即可使用侧滑删除和拖拽移动功能。
myAdapter.getDraggableModule().setSwipeEnabled(true);//允许侧滑
myAdapter.getDraggableModule().setDragEnabled(true); //允许拖拽
如果想要实现更多的功能,就对事件进行监听处理吧,我大概对每个方法都试验了一下,结论在注释里。
myAdapter.getDraggableModule().setOnItemDragListener(new OnItemDragListener() {
//拖拽开始
@Override
public void onItemDragStart(RecyclerView.ViewHolder viewHolder, int pos) {}
/**
* 因拖拽而发生位置互换时回调
* @param source 非用户拖拽的那个改变位置的Item
* @param from 用户拖拽的Item换位之前的position
* @param target 用户拖拽的Item
* @param to 用户拖拽的Item换位之后的position
*/
@Override
public void onItemDragMoving(RecyclerView.ViewHolder source, int from, RecyclerView.ViewHolder target, int to) {}
//拖拽结束
@Override
public void onItemDragEnd(RecyclerView.ViewHolder viewHolder, int pos) {}
});
myAdapter.getDraggableModule().setOnItemSwipeListener(new OnItemSwipeListener() {
//侧滑开始
@Override
public void onItemSwipeStart(RecyclerView.ViewHolder viewHolder, int pos) {}
//侧滑结束时回调,如果item被移除,pos将为负
@Override
public void clearView(RecyclerView.ViewHolder viewHolder, int pos) {}
//Item被移出时回调
@Override
public void onItemSwiped(RecyclerView.ViewHolder viewHolder, int pos) {}
//侧滑中,可以利用canvas在滑动的空白区域绘制,dX和dY分别是用户在不同方向上滑动的偏移量
@Override
public void onItemSwipeMoving(Canvas canvas, RecyclerView.ViewHolder viewHolder, float dX, float dY, boolean isCurrentlyActive) {}
});
使用侧滑的绘制功能,实现一个侧滑删除表情的时候在背后生成一张“可怜”表情的效果。
@Override
public void onItemSwipeMoving(Canvas canvas, RecyclerView.ViewHolder viewHolder, float dX, float dY, boolean isCurrentlyActive) {
canvas.drawBitmap(bitmap, 0, 0, new Paint());
}
最后,有一个小问题:之前使用ItemTouchHelper实现侧滑和拖拽功能时,功能逻辑都是由编写者自行完成的,而此框架已经将其功能封装得比较完全,例如侧滑删除已经写死。倘若想要实现类似QQ那样的置顶之类的功能,可能要对源码进行修改,或者还有其他的方法我还没有发现?
分组布局和多布局
分组布局也是多布局的一种,无非是分组布局只包含两种布局:头布局和内容布局,而多布局则可以包含更多的布局,因此本文将它们整理在一块。此框架中,提供的对分组布局的解决方案是:BaseSectionQuickAdapter,提供的对多布局的解决方案有:BaseMultiItemQuickAdapter、BaseDelegateMultiAdapter和BaseProviderMultiAdapter,它们各有优势,实际使用时根据不同的设计需求合理选择即可。接下来,使用这四种适配器实现同一个功能:对表情进行分类。 为了复用某些类,使用顺序会与列出的顺序有所差异,下面是实现效果:
BaseMultiItemQuickAdapter
说明:适用于类型较少,业务不复杂的场景,便于快速使用。
使用此适配器时,数据类必须实现MultiItemEntity接口并重写getItemType方法来返回类型。这里构建了两个比较简单的构造方法以方便创建实例,又由于RecyclerView的布局管理器采用GridLayoutManager,因此还需要一个spanSize值来指定布局所占的空间。
class MultipleBean implements MultiItemEntity {
public static final int TYPE_TITLE = 1;
public static final int TYPE_CONTENT = 2;
private int itemType;
private String title;
private int imgResId;
private int spanSize;
MultipleBean(String title) {
this.title = title;
this.itemType = TYPE_TITLE;
this.spanSize = 3;
}
MultipleBean(int imgResId) {
this.imgResId = imgResId;
this.itemType = TYPE_CONTENT;
this.spanSize = 1;
}
@Override
public int getItemType() {
return itemType;
}
//省略部分代码
}
有了数据类之后,就可以着手编写适配器类了。适配器类很简单,首先将对应的布局和类型绑定,然后在convert方法中根据类型设置数据即可。
class MultipleAdapter extends BaseMultiItemQuickAdapter<MultipleBean, BaseViewHolder> {
public MultipleAdapter(@Nullable List<MultipleBean> data) {
super(data);
//绑定布局对应的类型
addItemType(MultipleBean.TYPE_TITLE, R.layout.item_title);
addItemType(MultipleBean.TYPE_CONTENT, R.layout.item_content);
}
@Override
protected void convert(@NotNull BaseViewHolder helper, MultipleBean item) {
switch (helper.getItemViewType()) {
//根据类型设置数据
case MultipleBean.TYPE_TITLE:
helper.setText(R.id.title_tv, item.getTitle());
break;
case MultipleBean.TYPE_CONTENT:
helper.setImageResource(R.id.content_iv, item.getImgResId());
break;
default:
break;
}
}
}
最后,为RecyclerView设置适配器即可。此处唯一要注意的就是当使用网格布局时,需要设置一下每种布局的占地空间。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
//初始化RecyclerView
RecyclerView recyclerView = findViewById(R.id.recycler_view);
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 3);
recyclerView.setLayoutManager(gridLayoutManager);
//初始化数据
final List<MultipleBean> multipleData = initMultipleData();
//初始化适配器
MultipleAdapter multipleAdapter = new MultipleAdapter(multipleData);
multipleAdapter.setGridSpanSizeLookup(new GridSpanSizeLookup() {
@Override
public int getSpanSize(@NonNull GridLayoutManager gridLayoutManager, int viewType, int position) {
return multipleData.get(position).getSpanSize();
}
});
//设置适配器
recyclerView.setAdapter(multipleAdapter);
}
private List<MultipleBean> initMultipleData() {
List<MultipleBean> list = new ArrayList<>();
MultipleBean multipleBean1 = new MultipleBean("Smileys");
MultipleBean multipleBean2 = new MultipleBean(R.drawable.pic4);
list.add(multipleBean1);
list.add(multipleBean2);
//省略部分代码
return list;
}
}
BaseSectionQuickAdapter
说明:快速实现带头部的适配器,本质属于多布局,继承自BaseMultiItemQuickAdapter
使用此适配器时,需自定义类继承JSectionEntity抽象类,然后重新封装自己的数据类。为偷懒(bushi),直接使用MultipleBean作为数据类。
class SectionBean extends JSectionEntity {
private boolean isHeader;
private MultipleBean bean;
public SectionBean(boolean isHeader, MultipleBean bean) {
this.isHeader = isHeader;
this.bean = bean;
}
public MultipleBean getBean() {
return bean;
}
@Override
public boolean isHeader() {
return isHeader;
}
}
第二步,还就是那个编写适配器,虽然有点差异,但应该不用多说了吧。
class SectionAdapter extends BaseSectionQuickAdapter<SectionBean, BaseViewHolder> {
public SectionAdapter(int sectionHeadResId, int layoutResId, @Nullable List<SectionBean> data) {
super(sectionHeadResId, layoutResId, data);
}
@Override
protected void convertHeader(@NotNull BaseViewHolder helper, @NotNull SectionBean item) {
helper.setText(R.id.title_tv, item.getBean().getTitle());
}
@Override
protected void convert(@NotNull BaseViewHolder helper, SectionBean item) {
helper.setImageResource(R.id.content_iv, item.getBean().getImgResId());
}
}
最后,设置适配器。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
//初始化RecyclerView
RecyclerView recyclerView = findViewById(R.id.recycler_view);
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 3);
recyclerView.setLayoutManager(gridLayoutManager);
//初始化数据
final List<SectionBean> sectionData = initSectionData();
//初始化适配器
SectionAdapter sectionAdapter = new SectionAdapter(R.layout.item_title, R.layout.item_content, sectionData);
//设置适配器
recyclerView.setAdapter(sectionAdapter);
}
private List<SectionBean> initSectionData(){
List<SectionBean> list = new ArrayList<>();
list.add(new SectionBean(true, new MultipleBean("Smileys")));
list.add(new SectionBean(false, new MultipleBean(R.drawable.pic4)));
//省略部分代码
return list;
}
BaseDelegateMultiAdapter
说明:通过代理类的方式,返回布局id和item类型。此适配器的的数据类型可以为任意类型,适用于实体类不方便拓展的情况。
实体类仍使用MultipleBean,直接看适配器的实现。主要分为三步:
- 通过setMultiTypeDelegate方法返回类型。
- 通过addItemType方法将类型和布局绑定。
- 通过convert方法设置数据。
class DelegateAdapter extends BaseDelegateMultiAdapter<MultipleBean, BaseViewHolder> {
public DelegateAdapter(@Nullable List<MultipleBean> data) {
super(data);
setMultiTypeDelegate(new BaseMultiTypeDelegate<MultipleBean>() {
@Override
public int getItemType(@NotNull List<? extends MultipleBean> list, int i) {
//根据数据,返回对应类型
return list.get(i).getItemType();
//如果实体类没有getItemType之类的方法,也可以这样
//因为此适配器不限制数据类型,所以根据需求自由发挥即可
//MultipleBean bean = list.get(i);
//if (TextUtils.isEmpty(bean.getTitle())) return MultipleBean.TYPE_CONTENT;
//else return MultipleBean.TYPE_TITLE;
}
});
//绑定布局
getMultiTypeDelegate()
.addItemType(MultipleBean.TYPE_TITLE, R.layout.item_title)
.addItemType(MultipleBean.TYPE_CONTENT, R.layout.item_content);
}
@Override
protected void convert(@NotNull BaseViewHolder helper, MultipleBean item) {
switch (helper.getItemViewType()) {
case MultipleBean.TYPE_TITLE:
helper.setText(R.id.title_tv, item.getTitle());
break;
case MultipleBean.TYPE_CONTENT:
helper.setImageResource(R.id.content_iv, item.getImgResId());
break;
default:
break;
}
}
}
最后设置适配器到RecyclerView,依旧不要忘记使用网格布局时的适配。
//初始化适配器
DelegateAdapter delegateAdapter = new DelegateAdapter(multipleData);
delegateAdapter.setGridSpanSizeLookup(new GridSpanSizeLookup() {
@Override
public int getSpanSize(@NonNull GridLayoutManager gridLayoutManager, int viewType, int position) {
return multipleData.get(position).getSpanSize();
}
});
//设置适配器
recyclerView.setAdapter(delegateAdapter);
BaseProviderMultiAdapter
说明:当有多种条目时,避免在convert方法中做大量的业务逻辑,把逻辑抽取到对应的ItemProvider中。此适配器的数据类型可以是任意类型,只需在getItemType方法中返回类型;也不限定ViewHolder类型,可以在ItemProvider中自定义。
这个适配器看起来复杂,实际上就是将每种Item都抽离成为一个单独的类,然后整合到适配器中,以减少适配器中的代码量。根据需求,此处需要抽出两个类:TitleItemProvider和ContentItemProvider。它们的写法类似,因此只展示其一。
public class TitleItemProvider extends BaseItemProvider<MultipleBean> {
@Override
public int getItemViewType() {
return MultipleBean.TYPE_TITLE;
}
@Override
public int getLayoutId() {
return R.layout.item_title;
}
/*
* (可选)
* 重写返回自己的 ViewHolder。
* 默认返回 BaseViewHolder
*/
@NotNull
@Override
public BaseViewHolder onCreateViewHolder(@NotNull ViewGroup parent, int viewType) {
return super.onCreateViewHolder(parent, viewType);
}
@Override
public void convert(@NotNull BaseViewHolder helper, MultipleBean item) {
helper.setText(R.id.title_tv, item.getTitle());
}
}
适配器的代码则会变得相当简洁,只需返回对应类型并添加Provider即可。
class ProviderAdapter extends BaseProviderMultiAdapter<MultipleBean> {
public ProviderAdapter(@Nullable List<MultipleBean> data) {
super(data);
addItemProvider(new TitleItemProvider());
addItemProvider(new ContentItemProvider());
}
@Override
protected int getItemType(@NotNull List<? extends MultipleBean> list, int i) {
return list.get(i).getItemType();
}
}
//初始化适配器
ProviderAdapter providerAdapter = new ProviderAdapter(multipleData);
providerAdapter.setGridSpanSizeLookup(new GridSpanSizeLookup() {
@Override
public int getSpanSize(@NonNull GridLayoutManager gridLayoutManager, int viewType, int position) {
return multipleData.get(position).getSpanSize();
}
});
//设置适配器
recyclerView.setAdapter(providerAdapter);
多布局总结
大致梳理四种适配器的适用情况和使用流程。
- BaseSectionQuickAdapter
适用于分组布局或只有两种类型的情况。需要重新封装数据类(继承JSectionEntity类),类型根据isHeader()
方法确定,再由convert()
和convertHeader()
方法处理不同布局下的数据设置。 - BaseMultiItemQuickAdapter
适用于类型较少,业务不复杂的多布局场景。数据类必须实现MultiItemEntity接口,重写getItemType()
方法返回类型。接着在适配器中通过addItemType(int type, int layoutResId)
方法将类型与布局绑定,最后由convert()
方法根据类型处理数据。 - BaseDelegateMultiAdapter
适用于数据类不方便扩展的多布局场景。数据类无需继承其他类或实现接口,类型在适配器中通过setMultiTypeDelegate()
方法代理 返回。再由getMultiTypeDelegate().addItemType(int type, int layoutResId)
方法将类型与布局绑定,最后由convert()
方法根据类型处理数据。 - BaseProviderMultiAdapter
适用于业务逻辑复杂的多布局场景。数据类无需继承其他类或实现接口,布局、类型、业务逻辑都在对应的ItemProvider中确定,还可以自定义ViewHolder。适配器中只需调用addItemProvider(BaseItemProvider<T> itemProvider)
将ItemProvider添加进来,然后在convert()
方法中根据数据返回对应类型即可。