学习目标
熟悉使用 BRVAH 解决对应各种 adapter 需求
BRVAH 是 Github 上的一个很棒的开源项目,主要作用是帮助我们更加高效的使用 Recyclerview 控件,处理项目中常见需求的 Adapter,使用起来非常方便,更多介绍可去BRVAH官网查看。
BRVAH 主要是针对 Adapter 来设计的。
BRVAH 为我们提供了一般情况下的BaseQuickAdapter
,和几个特定需求下的Adapter,BaseMultiItemQuickAdapter
用于复杂类布局列表;BaseItemDraggableAdapter
用于拖拽移动和滑动删除类列表; BaseSectionQuickAdapter
用于带 Section 头部 View 的列表。
添加资源库
allprojects {
repositories {
...
maven { url "https://jitpack.io" }
}
}
添加依赖
dependencies {
compile 'com.github.CymChad:BaseRecyclerViewAdapterHelper:VERSION_CODE'
}
VERSION_CODE
的最新版本可以参考这里。
我们只需将自建的 xxAdapter 继承 BRVAH 对应满足需求的 Adapter,然后在 Activity 中实例化,通过openLoadAnimation()
方法完成特定的动画效果。
BRVAH 支持 5 种动画,BaseQuickAdapter.ALPHAIN
淡入、BaseQuickAdapter.SLIDEIN_BOTTOM
从底部入、BaseQuickAdapter.SLIDEIN_LEFT
从左边进入、BaseQuickAdapter.SLIDEIN_RIGHT
从右边进入和自定义动画。
关于自定义的动画,可以通过实现 BaseAnimation
这个类,重写
getAnimators(View view)
方法来完成自定义动画。
在实际应用中经常会遇到各种样式的列表、宫格和列表同时存在、分类列表等情况。
对于多样式的列表,根据需求创建 type 实体类实现 MultiItemEntity。 xxAdapter继承 BaseMultiItemQuickAdapter
类,在构造方法中调用addItemType ()
方法加入定义的 itemType 和对应布局,在 Activity 中实例化即可。
关于 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 getDataType() {
int type = 0;
for (int i = 0;i < 13;i++) {
if (i < 9) {
type = 1;
} else {
type = 2;
}
types.add(i,type);
}
List 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 开始。
简单的分析下上图的界面布局样式,最外面一个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;
}
}
添加 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);
BRVAH为我们提供好了全面的 item 和 item 子 View 的监听事件,我们只需在继承 BRVAH 提供的 Adapter 的基础上,通过 xxAdapter 来调用对应的监听,只是设置监听子 View 前,我们需要在 xxAadapter 中对应的 item 的子 View 注册对应的监听事件。
直接在 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;
}
});
首先需要在 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();
}
});
列表下拉刷新和上拉加载是最常用的,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 data = DataServer.getRefreshItemData(getServerData());
mAdapter.addData(data);
mAdapter.loadMoreComplete();
loadCount+=1;
} else {
mAdapter.loadMoreEnd(false); // 显示加载完毕
}
// 设置下拉刷新实效
mSwipeRefreshLayout.setEnabled(true);
}
还有就是在下拉刷新的时候,使上拉加载更多失效。
mAdapter.setEnableLoadMore(true);
设置 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 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