前言
最近在公司做小项目,需要用到分组功能,一般来讲,可以使用listview嵌套gridview来实现,也可以用recyclerview来实现。博主试过使用listview嵌套gridview,实现是实现了,就是优化不到位,会掉帧。然后使用recyclerview,想了大半天做了出来,可是效果也不好。于是问了下能不能用第三方库,毕竟前段时间发现了BaseRecyclerViewAdapterHelper(下文简称BRVAH)这个优秀的RecyclerViewAdapter高度定制开源库。结果得到了肯定,并且公司鼓励使用优秀的第三方开源库!然后!跟着我一起用吧!
why
依旧这个套路,这里先说下为什么要用BRVAH。
节省大量代码(减少70%)
重复代码,抽取到基类,非重复代码用抽象方法代替,具体让子类实现。
添加了点击事件
item点击、长按事件以及item子控件点击事件
添加了加载动画
一行代码轻松切换5种默认动画,采用策略模式,使你在添加自定义动画时随心应手。
方便添加头部、尾部、下拉刷新、上拉加载
简单到让你感觉回到Listview时代
添加分组功能(本节讲解)
随心定义分组头部
方便自定义不同的item类型
简单配置、无需重写额外方法
方便设置空布局
比Listview的setEmptyView还要好用!
可以拖拽item并附带动画
- 滑动删除item
how
说了那么多,那么如何使用呢?
添加依赖
第三方库第一件事情肯定就是添加依赖了
compile 'com.github.CymChad:BaseRecyclerViewAdapterHelper:v1.9.3'
添加布局
分组所需要两种item,一种是显示分组信息的,另一组是显示普通item的,所以创建两个layout。 分组信息Item(item_title.xml):
非常简单的布局,就一个cardview包裹了一个textview调整下布局就行。 普通Item(item_cardview.xml):
同样是简单的布局,cardview包裹了一个线性布局,里面有放图片的simpleDraweeView(fresco)和一个textview。 PS:这里有个小技巧,调整布局的时候可以先放图片进去试试。
添加实体类
这里需要有两个类,一个是存放普通item数据的,另一个是存放分组信息的。 普通item实体类(BookInfo):
public class BookInfo {
private String bookname;
private String author;
private String booktype;
private String summary;
private String imageurl;
private String zipurl;
private int downloads;
private String subject;
private String grade;
}
分组item实体类(MySection):
public class MySection extends SectionEntity {
public MySection(BookInfo bookInfo) {
super(bookInfo);
}
public MySection(boolean isHeader, String header) {
super(isHeader, header);
}
}
添加Adapter
分组功能的实现靠的就是Adapter根据不同的type使用不同的item布局来实现的,但由于BaseSectionQuickAdapter写了这部分的方法,我们不需要写,只需要继承就可以了。
public class SectionAdapter extends BaseSectionQuickAdapter {
public SectionAdapter(int layoutResId, int sectionHeadResId, List data) {
super(layoutResId, sectionHeadResId, data);
}
protected void convertHead(BaseViewHolder baseViewHolder, MySection mySection) {
baseViewHolder.setText(R.id.tv_item_title, mySection.header);
}
protected void convert(BaseViewHolder baseViewHolder, MySection mySection) {
BookInfo bookInfo = mySection.t;
SimpleDraweeView simpleDraweeView = baseViewHolder.getView(R.id.cv_img);
Uri uri = Uri.parse(bookInfo.getImageurl());
Log.i("test", "adapter");
simpleDraweeView.setImageURI(uri);
baseViewHolder.setText(R.id.cv_nameTv, bookInfo.getBookname());
}
}
创建数据服务类
为什么要创建数据服务类,为了管理方便,需要修改数据获取方式的时候直接修改这个类就行了。
public class DataServer {
private JSONArray jsonArray = new JSONArray();
public List SubjectList = new ArrayList<>();
public String title = "null";
public DataServer() {
}
//根据subject获取数据 这里博主是在fragment中传了个值过来获取固定科目的书数据
public List getSectionData(String subject) {
final List list = new ArrayList<>();
AsyncHttpClient client = new AsyncHttpClient();
client.get("http://192.168.199.152:8088/getbookinterface/"+subject, new AsyncHttpResponseHandler() {
@Override
public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
byte[] data = responseBody;
String s = new String(data);
jsonArray = JSON.parseArray(s);
for (int i = 0; i < jsonArray.size(); i++) {
BookInfo bookInfo = new BookInfo();
JSONObject jsonObject = jsonArray.getJSONObject(i);
bookInfo = JSON.toJavaObject(jsonObject, BookInfo.class);
System.out.println(bookInfo.getBookname());
if (!bookInfo.getGrade().equals(title)) {
title = bookInfo.getGrade();
list.add(new MySection(true, bookInfo.getGrade()));
list.add(new MySection(bookInfo));
} else list.add(new MySection(bookInfo));
}
EventBus.getDefault().post(new BookBus(list));
}
@Override
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
}
});
return list;
}
}
由于是异步获取服务器数据,所以return List肯定是获取不到数据的,因为还没dataserver还没获取完数据就已经被返回了。这里博主用了EventBus(线程通讯神器)把获取完成后的数据传到了fragment中(EventBus使用方法网上也有,或者可以看我稍后的博客)。
绑定Adapter
SectionList = dataServer.getSectionData(subject);//获取数据
sectionAdapter = new SectionAdapter(R.layout.item_cardview, R.layout.item_title, SectionList);
sectionAdapter.setOnRecyclerViewItemClickListener(this);
sectionAdapter.setOnRecyclerViewItemChildClickListener(new BaseQuickAdapter.OnRecyclerViewItemChildClickListener() {
public void onItemChildClick(BaseQuickAdapter baseQuickAdapter, View view, int i) {
Toast.makeText(getActivity(), "你点击了child", Toast.LENGTH_SHORT).show();
}
});
sectionAdapter.openLoadAnimation();
recyclerview.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));
recyclerview.setAdapter(sectionAdapter);
由于数据是通过EventBus坐Bus过来的,所以可能慢一点,当数据到达时,刷新一次就行。
SectionList = bookBus.getBookInfoList();
sectionAdapter.setNewData(SectionList);
好了打完收工!
成功图:
原理分析
这一趴直接转作者(陈哥)的博客,当然也可以看原文–www.jianshu.com/p/87a49f732… 其实头部和内容部分就是通过不同的type来实现的,我们可以查看BaseSectionQuickAdapter源码。
protected int getDefItemViewType(int position) {
return ((SectionEntity) mData.get(position)).isHeader ? SECTION_HEADER_VIEW : 0;
}
它是通过SectionEntity
的isHeader
属性来区别是否是头部的
public abstract class SectionEntity {
public boolean isHeader;
public T t;
public String header;
public SectionEntity(boolean isHeader, String header) {
this.isHeader = isHeader;
this.header = header;
this.t = null;
}
public SectionEntity(T t) {
this.isHeader = false;
this.header = null;
this.t = t;
}
}
这就是为什么要求开发者的实体类必须继承SectionEntity
的原因了,因为需要通过它的isHeader
这个属性来改变type
,onCreateViewHolder
通过不同的type
来加载不同的布局。
@Override
protected BaseViewHolder onCreateDefViewHolder(ViewGroup parent, int viewType) {
if (viewType == SECTION_HEADER_VIEW)
return new BaseViewHolder(getItemView(mSectionHeadResId, parent));
return super.onCreateDefViewHolder(parent, viewType);
}
然后在onBindViewHolder
里面通过type来区分头部和内容部分调用不同的方法
protected void convert(BaseViewHolder holder, Object item) {
switch (holder.getItemViewType()) {
case SECTION_HEADER_VIEW:
setFullSpan(holder);
convertHead(holder, (T) item);
break;
default:
convert(holder, (T) item);
break;
}
}
protected abstract void convertHead(BaseViewHolder helper, T item);
protected abstract void convert(BaseViewHolder helper, T item);
setFullSpan
是填充一行的方法,因为要考虑到多种LayoutManager的情况。
如果还什么疑问都可以在这里进行提问 因为开源项目和技术分享收到 Google 的面试邀请,大家有什么想要讨论的么?
后记
陈哥真的是个很nice的人,技术好,人更好,问问题都会回答,还会在群里和我们交流。BRVAH也是个超级方便的recyclerviewAdapter开源库!陈哥拿到谷歌的面试邀请,在此祝陈哥面试成功。