学习目标
熟悉使用 BRVAH 解决对应各种 adapter 需求
概述
BRVAH 是 Github 上的一个很棒的开源项目,主要作用是帮助我们更加高效的使用 Recyclerview 控件,处理项目中常见需求的 Adapter,使用起来非常方便,更多介绍可去BRVAH官网查看。
BRVAH 主要是针对 Adapter 来设计的。
BRVAH 为我们提供了一般情况下的BaseQuickAdapter
,和几个特定需求下的Adapter,BaseMultiItemQuickAdapter
用于复杂类布局列表;BaseItemDraggableAdapter
用于拖拽移动和滑动删除类列表; BaseSectionQuickAdapter
用于带 Section 头部 View 的列表。
build.gradle 配置说明
添加资源库
allprojects {
repositories {
...
maven { url "https://jitpack.io" }
}
}
添加依赖
dependencies {
compile 'com.github.CymChad:BaseRecyclerViewAdapterHelper:VERSION_CODE'
}
VERSION_CODE
的最新版本可以参考这里。
功能概要说明
1. 实现Recyclerview的列表加载动画效果
我们只需将自建的 xxAdapter 继承 BRVAH 对应满足需求的 Adapter,然后在 Activity 中实例化,通过openLoadAnimation()
方法完成特定的动画效果。
BRVAH 支持 5 种动画,BaseQuickAdapter.ALPHAIN
淡入、BaseQuickAdapter.SLIDEIN_BOTTOM
从底部入、BaseQuickAdapter.SLIDEIN_LEFT
从左边进入、BaseQuickAdapter.SLIDEIN_RIGHT
从右边进入和自定义动画。
关于自定义的动画,可以通过实现 BaseAnimation
这个类,重写
getAnimators(View view)
方法来完成自定义动画。
2. 实现Recyclerview的复杂布局列表
在实际应用中经常会遇到各种样式的列表、宫格和列表同时存在、分类列表等情况。
2.1 Recyclerview 多样式 item 排列的效果
对于多样式的列表,根据需求创建 type 实体类实现 MultiItemEntity。 xxAdapter继承 BaseMultiItemQuickAdapter
类,在构造方法中调用addItemType ()
方法加入定义的 itemType 和对应布局,在 Activity 中实例化即可。
2.2 Recyclerview 宫格和列表的混排样式
关于 Grid 和 List 的混排样式,Grid 样式是一行有多个,而 List 样式是一行只有一个。我们可以把 List 样式看成是 Grid 样式,它就相当于把一个 Grid 的 item 拉长了的样子。
列表与网格混排的布局效果,我们可以创建 xxAdapter 继承 BaseMultiItemQuickAdapter
添加对应 item 类型的布局文件,在 Activity 中创建 GridLayoutManager
对象,设置 spanSize
属性,通过 Adapter 的 setSpanSizeLookup
方法设置每种 item 类型对应的 spanSize。设置 Recyclerview 的 addItemDecoration()
方法设置添加分割线或设置 item 间距。
代码片段:
...
gridLayoutManager = new GridLayoutManager(this,3);
mRecyclerView.setLayoutManager(gridLayoutManager);
mAdapter = new GridManagerAdapter(getDataType());
// 设置横跨度
// 1:就是横跨1/3
// 2:就是横跨2/3
// 3:就是横跨一整行
mAdapter.setSpanSizeLookup(new BaseQuickAdapter.SpanSizeLookup() {
@Override
public int getSpanSize(GridLayoutManager gridLayoutManager, int position) {
return getDataType().get(position).getSpanSize();
}
});
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
GridLayoutManager.LayoutParams layoutParams = (GridLayoutManager.LayoutParams)
view.getLayoutParams();
int spanSize = layoutParams.getSpanSize();
int spanIndex = layoutParams.getSpanIndex();// 从左到右 0~
if (spanSize == gridLayoutManager.getSpanCount()) {
outRect.top = 20;
}
}
});
// 假设数据
private List<MultipleItem> getDataType() {
int type = 0;
for (int i = 0;i < 13;i++) {
if (i < 9) {
type = 1;
} else {
type = 2;
}
types.add(i,type);
}
List<MultipleItem> list = new ArrayList<>();
for (int j = 0;j < types.size();j++) {
if (types.get(j) == 1) {
list.add(new MultipleItem(1,1));
} else if (types.get(j) == 2) {
list.add(new MultipleItem(2,3));
}
}
return list;
}
...
在创建 GridLayoutManager
对象时,有一个 spanSize
的参数需要设置,它的作用就是使原来一个 item 占满一行变为可以最多三个 item 占满一行。
而设置 setSpanSizeLookup()
方法返回的是对应每种 item 类型返回具体的横跨大小。比如上面代码中 TYPE_GRID
类型的 item 在设置的 spanSize 是 1,而 GridLayoutManager 设置的 spanSize 是 3,那么该类型的 item 就会以 3 个 item 占满一行,相当于每个 item 占一行的 1/3。
在我们使用 addItemDecoration()
添加分割线的方法中对这种混排的列表设置 item 间距的时候,在 getItemOffsets()
方法里,通过 GridLayoutManager.LayoutParams
获取 spanSize 来确定 item 类型设置对应间距,其中 spanIndex
表示当前行 item 对应的下标位置,从左到右依次从 0 开始。
2.3 Recyclerview 嵌套 Recyclerview 的复杂布局
简单的分析下上图的界面布局样式,最外面一个RecyclerView,它里面的每个 item 的布局样式,最上面用 Banner 轮播图控件,下面是各种样式 item 列表的形式,其中有一个可左右滑动的 item,它是嵌套的一个 RecyclerView。
使用 BaseMultiItemQuickAdapter
在重写的 convert()
方法中实例化子 Recyclerview,同样的步骤在进行子 Recyclerview 设置,需要注意的是Recyclerview 嵌套 Recyclerview 会出现子 Recyclerview 抢焦点的问题,导致界面顶部部分控件被挤出,只需在最外面的 Recyclerview 的包裹容器中设置属性 android:descendantFocusability="blocksDescendants"
即可。通过 BRVAH 的 addHeaderView()
方法设置头部布局。
代码片段:
@Override
protected void convert(BaseViewHolder helper, MultiItemEntity item) {
switch (helper.getItemViewType()) {
...
case MultipleItem.DOUBANTIME:
RecyclerView recyclerView = helper.getView(R.id.recyclerview_item_view);
recyclerView.setLayoutManager(new LinearLayoutManager(mContext,
LinearLayoutManager.HORIZONTAL,false));
recyclerView.setHasFixedSize(true);
recyclerView.setAdapter(new RrecyclerViewAdapter(DataServer.getReItemData()));
break;
}
}
2. 实现Recyclerview拖拽滑动删除
添加 RecyclerView 的拖拽和滑动移除很简单,只需 xxAdapter 继承 BaseItemDraggableAdapter
类,在 Activity 中,添加 OnItemDragListener
和 OnItemSwipeListener
两个接口,通过 xxAdapter 配置基本属性即可。
代码片段:
OnItemDragListener listener = new OnItemDragListener() {
@Override
public void onItemDragStart(RecyclerView.ViewHolder viewHolder, int pos) {
// item 拖拽的起始位置
Log.d(TAG,"drag start");
}
@Override
public void onItemDragMoving(RecyclerView.ViewHolder source, int from, RecyclerView.ViewHolder target, int to) {
// item 拖拽的过程位置变化
Log.d(TAG, "move from: " + source.getAdapterPosition() + " to: " + target.getAdapterPosition());
}
@Override
public void onItemDragEnd(RecyclerView.ViewHolder viewHolder, int pos) {
// item 拖拽的终点位置
Log.d(TAG, "drag end");
}
};
OnItemSwipeListener onItemSwipeListener = new OnItemSwipeListener() {
@Override
public void onItemSwipeStart(RecyclerView.ViewHolder viewHolder, int pos) {
// item 滑动的起始位置
Log.d(TAG, "view swiped start: " + pos);
}
@Override
public void clearView(RecyclerView.ViewHolder viewHolder, int pos) {
// item 滑动后的恢复位置
Log.d(TAG, "View reset: " + pos);
}
@Override
public void onItemSwiped(RecyclerView.ViewHolder viewHolder, int pos) {
// item 滑动的移除位置
Log.d(TAG, "View Swiped: " + pos);
}
@Override
public void onItemSwipeMoving(Canvas canvas, RecyclerView.ViewHolder viewHolder,
float dX, float dY, boolean isCurrentlyActive) {
// item 滑动的过程
canvas.drawColor(ContextCompat.getColor(ItemDragAndSwipUseActivity.this,R.color.colorKbtt));
}
};
ItemDragAndSwipeCallback itemDragAndSwipeCallback = new ItemDragAndSwipeCallback(mAdapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemDragAndSwipeCallback);
itemTouchHelper.attachToRecyclerView(mRecyclerView);
mItemDragAndSwipeCallback.setSwipeMoveFlags(ItemTouchHelper.START | ItemTouchHelper.END);
// open slide to delete
mAdapter.enableSwipeItem();
mAdapter.setOnItemSwipeListener(onItemSwipeListener);
// open drag
mAdapter.enableDragItem(itemTouchHelper);
mAdapter.setOnItemDragListener(listener);
mRecyclerView.setAdapter(mAdapter);
3. 实现Recyclerview监听事件
BRVAH为我们提供好了全面的 item 和 item 子 View 的监听事件,我们只需在继承 BRVAH 提供的 Adapter 的基础上,通过 xxAdapter 来调用对应的监听,只是设置监听子 View 前,我们需要在 xxAadapter 中对应的 item 的子 View 注册对应的监听事件。
3.1 对于 item 的监听事件处理
直接在 Activity 中添加
item 点击事件
adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
Log.d(TAG, "onItemClick: ");
Toast.makeText(ItemClickActivity.this, "onItemClick" + position, Toast.LENGTH_SHORT).show();
}
});
item 长按监听事件
adapter.setOnItemLongClickListener(new BaseQuickAdapter.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(BaseQuickAdapter adapter, View view, int position) {
Log.d(TAG, "onItemLongClick: ");
Toast.makeText(ItemClickActivity.this, "onItemLongClick" + position, Toast.LENGTH_SHORT).show();
return false;
}
});
3.2 对 item 中子 View 的监听事件处理
首先需要在 xxAdapter 中注册子 View 的监听事件。
点击监听事件
@Override
protected void convert(BaseViewHolder viewHolder, Status item) {
viewHolder.setText(R.id.tweetName, item.getUserName())
.setText(R.id.tweetText, item.getText())
.setText(R.id.tweetDate, item.getCreatedAt())
.setVisible(R.id.tweetRT, item.isRetweet())
.addOnClickListener(R.id.tweetAvatar)
.addOnClickListener(R.id.tweetName)
.linkify(R.id.tweetText);
}
长按监听事件
@Override
protected void convert(BaseViewHolder helper, Status item) {
helper.setText(R.id.tweetName, item.getUserName())
.setText(R.id.tweetText, item.getText())
.setText(R.id.tweetDate, item.getCreatedAt())
.setVisible(R.id.tweetRT, item.isRetweet())
.addOnLongClickListener(R.id.tweetText)
.linkify(R.id.tweetText);
}
然后在 Activity 中设置对应的子 View 监听事件
点击事件
adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
@Override
public boolean onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
Log.d(TAG, "onItemChildClick: ");
Toast.makeText(ItemClickActivity.this, "onItemChildClick" + position, Toast.LENGTH_SHORT).show();
return false;
}
});
子 View 长按事件
adapter.setOnItemChildLongClickListener(new BaseQuickAdapter.OnItemChildLongClickListener() {
@Override
public void onItemChildLongClick(BaseQuickAdapter adapter, View view, int position) {
Log.d(TAG, "onItemChildLongClick: ");
Toast.makeText(ItemClickActivity.this, "onItemChildLongClick" + position, Toast.LENGTH_SHORT).show();
}
});
4. 实现Recyclerview 刷新
列表下拉刷新和上拉加载是最常用的,BRVAH 虽然也提供了刷新功能,但只是上拉加载更多,而且样式也是固定的,实现下拉刷新还得用 SwipeRefreshLayout 来完成,这也容易理解,因为 BRVAH 主要是对 Adapter 的服务,所以这里只是简单说下 BRVAH 有关上拉加载更多的使用。
在按照 BRVAH 设置完 xxAdapter 后,在 Activity 中,让类实现 BaseQuickAdapter.RequestLoadMoreListener
接口,会重写 onLoadMoreRequested()
方法,在方法中调用 mAdapter.addData()
来添加新的数据,接着设置 mAdapter.loadMoreComplete()
,在数据都加载完后设置 mAdapter.loadMoreEnd(false)
显示数据加载完毕。
@Override
public void onLoadMoreRequested() {
mSwipeRefreshLayout.setEnabled(false);
if (loadCount < 2) {
final List<MultipleItem> data = DataServer.getRefreshItemData(getServerData());
mAdapter.addData(data);
mAdapter.loadMoreComplete();
loadCount+=1;
} else {
mAdapter.loadMoreEnd(false); // 显示加载完毕
}
// 设置下拉刷新实效
mSwipeRefreshLayout.setEnabled(true);
}
还有就是在下拉刷新的时候,使上拉加载更多失效。
mAdapter.setEnableLoadMore(true);
5. 实现Recyclerview 添加 Section 头部 View
设置 MySection 类继承 SectionEntity
,创建不同的构造方法来设置 item 是否有 header。在 adapter 中,增加了 convertHead()
方法来加载 head 数据。在 Activity 中根据数据创建不同的 MySection 对象加入集合,设置给 adapter。
public class MySection extends SectionEntity<String>{
private boolean isMore;
public MySection(boolean isHeader, String header,boolean isMore) {
super(isHeader, header);
this.isMore = isMore;
}
public MySection(String s) {
super(s);
}
public void setMore(boolean more) {
isMore = more;
}
}
在 adapter 中,构造方法需要传入两个不同的布局 id,第一个是 item 的 layout id,第二个是 head,item 的数据加载在 convert()
方法中,head 的数据加载在 convertHead()
方法中。
public class SectionAdpater extends BaseSectionQuickAdapter<MySection,BaseViewHolder> {
public SectionAdpater(int layoutResId, int sectionHeadResId, List<MySection> data) {
super(layoutResId, sectionHeadResId, data);
}
// 加载 head 数据
@Override
protected void convertHead(BaseViewHolder helper, MySection item) {
helper.setText(R.id.title_tv,item.header);
helper.addOnClickListener(R.id.more_btn);
}
// 加载 item 数据
@Override
protected void convert(BaseViewHolder helper, MySection item) {
}
}
在 Activity 中
...
// 部分代码
mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview);
mRecyclerView.setLayoutManager(new GridLayoutManager(this,3));
mData = getData(3);
SectionAdpater sectionAdpater = new SectionAdpater(R.layout.item_section_content,
R.layout.def_section_head, mData);
...
// 假数据
private List<MySection> getData(int size) {
ArrayList<MySection> data = new ArrayList<>(size);
data.add(new MySection(true,"1",true));
for (int i =0;i < size;i++) {
data.add(new MySection("haha"));
}
data.add(new MySection(true,"2",true));
for (int i =0;i < size;i++) {
data.add(new MySection("haha"));
}
data.add(new MySection(true,"3",true));
for (int i =0;i < size;i++) {
data.add(new MySection("haha"));
}
return data;
}
根据数据确定不同的样式,用不同的构造方法设置 item 布局。
重要参考:BRVAH