搭配BRVAH高效使用RecyclerView

学习目标

熟悉使用 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 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 开始。

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 中,添加 OnItemDragListenerOnItemSwipeListener 两个接口,通过 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 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 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

你可能感兴趣的:(recyclerview控件)