简单实用的Android下拉刷新和上拉加载控件PullRecyclerView

项目地址:https://github.com/HolenZhou/PullRecyclerView

效果展示

PullRecyclerView

简介

RecyclerView已经出来两年多了,但直到今年4月份,我们的项目里才开始逐渐将原来的ListView替换为RecyclerView,当时项目里使用的是PullToRefresh这个开源里面的XListView,简单实用地实现了ListView的下拉刷新和上拉加载,不会引入其他多余的代码,同时配合使用base-adapter-helper这个项目提供的adapter及其辅助类,使得ListView的使用更加简单,代码更加精简。所以当初替换的时候,我的思路就是需要找到一个跟XListView一样独立可使用的封装了下拉和上拉的RecyclerView控件,并且最好能够保留当前这个好用的adapter,以使替换的工作量降到最低。
当时在github上看了很多但都觉得不太满意,有的是功能太多,需要引入太多无关的代码,有的是效果不满足我的预期。加之当时听了Stay大神的课程,看到他对RecyclerView的简单封装,所以就以Stay的源码为雏形,结合自己项目中实际使用的需要,重新封装了一个PullRecyclerView,Stay关于封装RecyclerView的代码github上也有,想学习的可以去瞅瞅,不但可以看到对于RecyclerView的封装,还可以看到如何封装一些好用的Base类。同时还借鉴了另外一个项目BaseRecyclerViewAdapterHelper,当时这个项目还只有不到1千个star,短短几个月,已经快达到5千个star,而且这个项目一直在精心维护中,现在的代码跟我当初看到的代码比起来进行了很多修改和优化,它的宗旨是提供一个“Powerful and flexible RecyclerAdapter”,所有相关的功能(下拉、上拉、header、footer、空页面等)都是通过它提供的adapter来实现的,没有对RecyclerView本身做任何的封装。其实不用封装RecyclerView确实完全可以达到任何需求,因为RecyclerView的下拉刷新不需要它本身实现,而是通过官方提供的SwipeRefreshLayout来实现的,而上拉、header、footer和空页面这些功能,则只通过adapter的操作就可以完全实现。但对于我来讲,封装也是有好处的,将SwipeRefreshLayout和RecyclerView组合成一个控件可以让我们使用起来更加简洁和方便,另外对于加载更多的效果,个人认为通过RecyclerView的滚动来触发比通过adapter来触发在效果上更加自然。所以最终我没有直接使用BaseRecyclerViewAdapterHelper项目,而是借鉴了一些它里面对于adapter的封装和使用,自己进行了更加简洁精小的封装。
我只加入了最核心和最常用的功能封装:
1.下拉刷新和上拉加载更多(支持普通列表和Grid列表,暂不支持瀑布流);
2.添加Header和Footer;
3.设置列表无数据时需要展示的空页面
4.非常好用的BaseRecyclerAdapter和BaseViewHolder;
5.只支持上拉加载更多的LoadMoreRecyclerView(直接继承自RecyclerView,适用于需要将SwipeRefreshLayout和RecyclerView分开使用的场景)。
从4月份加入我们公司的项目至今,已经历经了三个版本和大量用户的考验,所以想要使用的话是可以直接用于实际项目的。欢迎感兴趣的童鞋们star我的PullRecyclerView,也欢迎你们提出问题和建议!

使用方法

一. 下拉刷新和上拉加载更多

1.初始化,跟RecyclerView的是使用方法一致

mPullRecyclerView = (PullRecyclerView) findViewById(R.id.pull_recycler_view);
// 设置LayoutManager(这里需要使用lib里面提供的XLinearLayoutManager或XGridLayoutManager,默认为XLinearLayoutManager)
mPullRecyclerView.setLayoutManager(new XLinearLayoutManager(this));
// 创建adapter,adapter的使用方法看后面
mAdapter = new CommonListAdapter(this, R.layout.list_item, mDataList);
// 设置adapter
mPullRecyclerView.setAdapter(mAdapter);
// 设置下拉刷新的旋转圆圈的颜色(根据自己的需求设置)
mPullRecyclerView.setColorSchemeResources(R.color.colorAccent); 
// 设置ItemDecoration,可选
mPullRecyclerView.addItemDecoration(...);
// 设置当数据全部加载完成时,是否在列表底部展示提示语,可选,默认无提示
mPullRecyclerView.enableLoadDoneTip(true, R.string.load_done_tip);

2.下拉刷新

 // 开启下拉刷新,默认即为true,可不用设置
mPullRecyclerView.enablePullRefresh(true);
// 下拉刷新成功后更新UI,结束下拉刷新
mPullRecyclerView.stopRefresh();

3.上拉加载更多

 // 开启上拉加载更多,默认为false,当前有更多数据需要加载时需开启
mPullRecyclerView.enableLoadMore(true);
// 上拉加载成功后更新UI,结束上拉加载
mPullRecyclerView.stopLoadMore();

4.上拉、下拉事件监听

mPullRecyclerView.setOnRecyclerRefreshListener(new PullRecyclerView.OnRecyclerRefreshListener() {
    @Override
    public void onPullRefresh() {
        // 下拉刷新事件被触发
    }

    @Override
    public void onLoadMore() {
        // 上拉加载更多事件被触发
    }
});

5.代码触发列表刷新

// 触发PullRecyclerView的下拉刷新,会展示下拉旋转圆圈
mPullRecyclerView.postRefreshing();
// 直接刷新列表(回调监听事件中的onPullRefresh方法),不展示下拉旋转圆圈
mPullRecyclerView.refreshNoPull();

6.item点击事件监听

// 通过adapter设置item的点击事件
mAdapter.setOnRecyclerItemClickListener(new BaseRecyclerAdapter.OnRecyclerItemClickListener() {
    @Override
    public void onItemClick(View view, int position) {

    }
});
// 通过adapter设置item的长按事件
mAdapter.setOnRecyclerItemLongClickListener(new BaseRecyclerAdapter.OnRecyclerItemLongClickListener() {
    @Override
    public boolean onItemLongClick(View view, int position) {
        return false;
    }
});

二. 添加Header和Footer

添加header和footer功能是通过BaseRecyclerAdapter类实现的,所以最终会调用到BaseRecyclerAdapter中的方法,但是为了方便,我在PullRecyclerView中也添加了同样的方法,注意使用PullRecyclerView中的添加Header和Footer的方法时,必须在给PullRecyclerView设置了adapter之后再进行调用

1.添加Header(两种方法任选其一)

// 添加header,传入header对应的View
mPullRecyclerView.addHeaderView(headView);
// 添加header,传入header对应的布局资源文件
mPullRecyclerView.addHeaderView(R.layout.layout_list_header);

2.添加Footer(添加了Footer,上拉加载的功能就不再可用)

// 添加footer,传入footer对应的View
mPullRecyclerView.addFooterView(footerView);
// 添加footer,传入footer对应的布局资源文件
mPullRecyclerView.addFooterView(R.layout.layout_list_footer);
添加了Footer的效果

三. 设置空页面

空页面的实现原理跟添加header和footer是一样的,所以同样注意使用PullRecyclerView中的设置空页面的方法时,必须在给PullRecyclerView设置了adapter之后再进行调用

1.无数据且无Header时展示空页面(两种方法任选其一)

mPullRecyclerView.setEmptyView(emptyView);
mPullRecyclerView.setEmptyView(R.layout.layout_empty_view);

2.有Header时也展示的空页面(两种方法任选其一)

// 第一个参数为true表示有Header时也显示空页面,不设置默认为false
mPullRecyclerView.setEmptyView(true, emptyView);
mPullRecyclerView.setEmptyView(true, R.layout.layout_empty_view);

四. BaseRecyclerAdapter和BaseViewHolder的使用

BaseRecyclerAdapter非常好用,它继承于RecyclerView.Adapter,配套BaseViewHolder,当我们需要一个adapter时,只需要继承它,实现其抽象方法convert(...),然后我们仅仅需要关心如何在这个方法中填充View的内容就够了,其他的都无需操心。

public class CommonListAdapter extends BaseRecyclerAdapter<CheesesItem> {

    public CommonListAdapter(Context context, int layoutResId, List<CheesesItem> data) {
        super(context, layoutResId, data);
    }

    @Override
    protected void convert(final BaseViewHolder holder, final CheesesItem item) {
        ImageView avatarView = holder.getView(R.id.avatar);
        Glide.with(mContext)
                .load(item.avatar)
                .fitCenter()
                .into(avatarView);
        holder.setText(android.R.id.text1, item.name);
        holder.getView().setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(holder.getView(), item.name, Snackbar.LENGTH_SHORT).show();
            }
        });
    }
}

convert(...)方法会传递进来一个BaseViewHolder,我们通过这个BaseViewHolder可以很方便地找到需要填充的View进行填充,无需关心复用的问题,BaseViewHolder内部已经帮忙搞定。

// 通过id找到我们需要的View,无需强转
ImageView avatarView = holder.getView(R.id.avatar);
// 获取当前整个Item的View对象
holder.getView();
// 一些简单的属性可以直接设置
holder.setText(textViewId, item.name);
holder.setImageResource(ImageViewId, imageResId);
holder.setBackgroundColor(viewId, color);

BaseRecyclerAdapter中也提供了一些操作数据源的方法

// 获取当前数据源
mAdapter.getCount();
// 向当前数据源中添加一个元素并刷新(内部会调用notifyItemInserted)
mAdapter.add(...);
// 向当前数据源添加一组元素并刷新列表
mAdapter.addAll(...);
// 清空当前数据源并刷新列表
mAdapter.clear();

// 更多方法请自行查看
……

如果需要在填充View时使用到item的position,可以使用convert(...)的重载方法

@Override
protected void convert(BaseViewHolder holder, CheesesItem item, int position) {

}

上面convert(...)中传来的position是考虑了存在header的情况的,如果列表没有header,也可以直接使用holder.getLayoutPosition()方法来获取当前item的position。

感谢

1.Stay大神的课程;
2.BaseRecyclerViewAdapterHelper项目;
3.列表模拟数据是从cheesesquare项目中借用的

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容