BRVAH 小白笔记之分组篇

3,180 阅读5分钟
原文链接: jcmels.top

前言

最近在公司做小项目,需要用到分组功能,一般来讲,可以使用listview嵌套gridview来实现,也可以用recyclerview来实现。博主试过使用listview嵌套gridview,实现是实现了,就是优化不到位,会掉帧。然后使用recyclerview,想了大半天做了出来,可是效果也不好。于是问了下能不能用第三方库,毕竟前段时间发现了BaseRecyclerViewAdapterHelper(下文简称BRVAH)这个优秀的RecyclerViewAdapter高度定制开源库。结果得到了肯定,并且公司鼓励使用优秀的第三方开源库!然后!跟着我一起用吧!

why

依旧这个套路,这里先说下为什么要用BRVAH。

  1. 节省大量代码(减少70%)

    重复代码,抽取到基类,非重复代码用抽象方法代替,具体让子类实现。

  2. 添加了点击事件

    item点击、长按事件以及item子控件点击事件

  3. 添加了加载动画

    一行代码轻松切换5种默认动画,采用策略模式,使你在添加自定义动画时随心应手。

  4. 方便添加头部、尾部、下拉刷新、上拉加载

    简单到让你感觉回到Listview时代

  5. 添加分组功能(本节讲解)

    随心定义分组头部

  6. 方便自定义不同的item类型

    简单配置、无需重写额外方法

  7. 方便设置空布局

    比Listview的setEmptyView还要好用!

  8. 可以拖拽item并附带动画

  9. 滑动删除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;
    }
    

它是通过SectionEntityisHeader属性来区别是否是头部的

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这个属性来改变typeonCreateViewHolder通过不同的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开源库!陈哥拿到谷歌的面试邀请,在此祝陈哥面试成功。