RecyclerView框架——BRVAH3.x使用指南
- build.gradle(Module: app)中添加依赖:
-
- 基础使用
- 添加空布局
- 添加头部和尾部
- Item子控件事件
- 添加加载动画
- 上拉加载
- 然后从适配器获取到这个LoadMoreModule并设置监听事件即可:
- 侧滑和拖拽
- 分组布局和多布局
- BaseSectionQuickAdapter
- BaseDelegateMultiAdapter
- BaseProviderMultiAdapter
- 多布局总结
build.gradle(Module: app)中添加依赖:
dependencies {
…
implementation ‘com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4’
}
基础使用
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);
添加头部和尾部
//设置头部和尾部 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子控件事件
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(); } }); } 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);
上拉加载
简单使用
使用此功能首先需要让适配器实现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(); } });
侧滑和拖拽
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) {} });
分组布局和多布局
分组布局也是多布局的一种,无非是分组布局只包含两种布局:头布局和内容布局,而多布局则可以包含更多的布局,因此本文将它们整理在一块。此框架中,提供的对分组布局的解决方案是: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 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 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,直接看适配器的实现。主要分为三步:
1、通过setMultiTypeDelegate方法返回类型。
2、通过addItemType方法将类型和布局绑定。
3、通过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 {
@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 {
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 itemProvider)将ItemProvider添加进来,然后在convert()方法中根据数据返回对应类型即可。