RecyclerView下拉刷新与上拉更多

前言

在原来的文章中我提及了如何使用RecyclerView添加headerfooter,今天我们来更深入的扩展一下使用RecyclerView实现常用的下拉刷新与上拉加载更多的功能。当然这些功能的实现也是基于前面的RecyclerView添加header与footer为基础来实现的,不是很了解的可以先看看前面的文章可能能更好的帮助理解。

依赖

为了方法大家的使用我已经把他上传到Jcenter中了,所以大家可以调用下面的代码了直接获取使用:

compile 'com.idisfkj.enchancerecyclerview:mylibrary:1.1.1'

EnhanceRecyclerView

我将这个扩展的RecyclerView命名为EnhanceRecyclerView,继承RecyclerView。我们知道既然要实现下拉刷新与上拉更多自然先要实现头部与尾部的布局,所以我们先利用前面的知识来为EnhanceRecycleView添加headerfooter

public void initView() {
        View headerView = LayoutInflater.from(getContext()).inflate(R.layout.head_layout, null);
        View footerView = LayoutInflater.from(getContext()).inflate(R.layout.footer_layout, null);
        addHeaderView(headerView);
        addFooterView(footerView);
    }

其中的布局文件就不多说了,至于addHeaderView与addFooterView方法可以查看我前面的那篇文章,有详细的介绍

设置监听器

既然要实现下拉刷新与上拉加载,自然少不了对监听器的处理,所以下面来详细介绍下对监听器OnScrollListenerOnTouchListener的处理。

OnScrollListener

EnhanceRecyclerView添加addOnScrollListener实现其中的onScrollStateChangedonScrolled方法。

onScrolled

onScrolled中我们主要做的是获取EnhanceRcyclerViewitem的总数量、视图显示中的第一个itemEnhanceRecyclerView中所处的位置与视图显示中最后一个itemEnhanceRecyclerView中所处的位置。

对于item的总数量很好获取直接调用

totalCount = getLayoutManager().getItemCount();

由于RecyclerView能实现LinearLayoutManagerGridLayoutManagerStaggeredGridLayoutManager不同的布局,所以另外两个要根据不同的manager来获取,还是看具体代码吧

if (getLayoutManager() instanceof LinearLayoutManager) {
                    lastItem = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();
                    firstVisible = ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition();
                } else {
                    into = ((StaggeredGridLayoutManager) getLayoutManager()).findLastVisibleItemPositions(into);
                    firstInto = ((StaggeredGridLayoutManager) getLayoutManager()).findFirstVisibleItemPositions(firstInto);
                    lastItem = into[0];
                    firstVisible = firstInto[0];
                }

onScrollStateChanged

获取到了那三个关键数据以后,就可以在onScrollStateChanged中实现具体的逻辑,在这个方法中主要实现的是对上拉加载更多的处理

if (lastItem == adapter.getItemCount() + 1 && newState == RecyclerView.SCROLL_STATE_IDLE && !isLoad) {
                    ViewGroup.LayoutParams params = getFooterView(0).getLayoutParams();
                    params.width = RecyclerView.LayoutParams.MATCH_PARENT;
                    params.height = RecyclerView.LayoutParams.WRAP_CONTENT;
                    getFooterView(0).setLayoutParams(params);
                    getFooterView(0).setVisibility(View.VISIBLE);
                    smoothScrollToPosition(totalCount);
                    isLoad = true;
                    loadMoreListener.onLoadMore();
                }
                if (firstVisible == 0) {
                    isTop = true;
                } else {
                    isTop = false;
                    RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) getHeaderView(0).getLayoutParams();
                    params.width = RecyclerView.LayoutParams.MATCH_PARENT;
                    params.height = RecyclerView.LayoutParams.WRAP_CONTENT;
                    params.setMargins(0, -getHeaderView(0).getHeight(), 0, 0);
                    getHeaderView(0).setLayoutParams(params);
                }

简单说明下,核心就是判断lastItem是否处在最后的位置,如果是的话就继续加载更多的操作,这里提供了一个对数据处理的接口所以只要实现loadMoreListener.onLoadMore();即可。

上拉加载更多核心就是这么多,其它的可以查看源码

OnTouchListener

这个监听器主要是对下拉刷新进行处理。我们要分别对其中我们所熟悉的MotionEvent.ACTION_DOWNMotionEvent.ACTION_MOVEMotionEvent.ACTION_UP进行处理。ACTION_DOWN就是简单的获取按下的坐标位置,这里就不多说了,下面主要的针对另外的两个进行简单说明。

ACTION_MOVE

这做的逻辑就是对触摸后的处理,根据滑动的距离来动态的改变header的文本与布局视图的显示。

public void touchMove(MotionEvent event) {
        endY = event.getY();
        moveY = endY - startY;
        //防止item向上滑出
        if (moveY > 0 && !isRefreshing) {
            //防止回退文本显示异常
            scrollToPosition(0);

            if (getHeaderView(0).getVisibility() == GONE)
                getHeaderView(0).setVisibility(VISIBLE);

            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) getHeaderView(0).getLayoutParams();
            params.width = RecyclerView.LayoutParams.MATCH_PARENT;
            params.height = RecyclerView.LayoutParams.WRAP_CONTENT;
            //使header随moveY的值从顶部渐渐出现
            if (moveY >= 400) {
                moveY = 100 + moveY / 4;
            } else {
                moveY = moveY / 2;
            }
            viewHeight = getHeaderView(0).getHeight();
            if (viewHeight <= 0)
                viewHeight = 130;
            moveY = moveY - viewHeight;
            params.setMargins(0, (int) moveY, 0, 0);
            getHeaderView(0).setLayoutParams(params);
            if (moveY > 80) {
                text.setText(getResources().getString(R.string.release_to_refresh));
            } else {
                text.setText(getResources().getString(R.string.pull_to_refresh));
            }
        } else {
            if (getHeaderView(0).getVisibility() != GONE && !isRefreshing) {
                getHeaderView(0).setVisibility(GONE);
            }
        }
    }

至于下拉时与顶部的距离变化是通过设置margin来动态改变的。

ACTION_UP

最后的触摸处理就是在离开屏幕时根据滑动的距离,是否调用加载数据的接口,或者隐藏下拉刷新头部,具体还是看代码吧。

public void touchUp() {
        if (!isRefreshing && (endY -startY) != 0 ) {

            RecyclerView.LayoutParams params1 = (RecyclerView.LayoutParams) getHeaderView(0).getLayoutParams();
            params1.width = RecyclerView.LayoutParams.MATCH_PARENT;
            params1.height = RecyclerView.LayoutParams.WRAP_CONTENT;

            if (moveY >= 80) {
                text.setText(getResources().getString(R.string.refreshing));
                params1.setMargins(0, 0, 0, 0);
                isRefreshing = true;
                //刷新数据
                pullToRefresh.onRefreshing();
            } else {
                if (viewHeight <= 0)
                    viewHeight = 130;
                params1.setMargins(0, -viewHeight, 0, 0);
                getHeaderView(0).setVisibility(GONE);
            }
            getHeaderView(0).setLayoutParams(params1);
        }
    }

代码中重要的地方都有指出相信都能看懂,这样下拉与上拉的逻辑就基本实现了,下面来看接口的设计吧

下拉与上拉接口

public interface PullToRefreshListener {
        void onRefreshing();
    }

    public void setPullToRefreshListener(PullToRefreshListener pullToRefresh) {
        if (loadMoreListener == null) {
            initListener();
        }
        this.pullToRefresh = pullToRefresh;
    }

    public interface LoadMoreListener {
        void onLoadMore();
    }

    public void setLoadMoreListener(LoadMoreListener loadMoreListener) {
        if (pullToRefresh == null) {
            initListener();
        }
        this.loadMoreListener = loadMoreListener;
    }

在运用是添加接口监听时初始化前面为EnhanceRecyclerView所设置的监听。

状态重置设置

在调用下拉刷新或者上拉加载更多之后,我们为其构造通用方法实现,状态的重置与数据的更新,方便统一调用。

 public void setLoadMoreComplete() {
        RecyclerView.LayoutParams params = (LayoutParams) getFooterView(0).getLayoutParams();
        params.width = 0;
        params.height = 0;
        getFooterView(0).setLayoutParams(params);
        getFooterView(0).setVisibility(View.GONE);
        this.getAdapter().notifyDataSetChanged();
        isLoad = false;
    }

    public void setRefreshComplete() {
        RecyclerView.LayoutParams params1 = (RecyclerView.LayoutParams) getHeaderView(0).getLayoutParams();
        params1.width = RecyclerView.LayoutParams.MATCH_PARENT;
        params1.height = RecyclerView.LayoutParams.WRAP_CONTENT;
        params1.setMargins(0, -getHeaderView(0).getHeight(), 0, 0);
        getHeaderView(0).setLayoutParams(params1);
        getHeaderView(0).setVisibility(GONE);
        this.getAdapter().notifyDataSetChanged();
        isRefreshing = false;
    }

所用工作已经完成下面来做个调用示范

使用

xml中引用

<com.idisfkj.mylibrary.EnhanceRecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
</com.idisfkj.mylibrary.EnhanceRecyclerView>

设置监听

 mRecyclerView.setPullToRefreshListener(new com.idisfkj.mylibrary.EnhanceRecyclerView.PullToRefreshListener() {
            @Override
            public void onRefreshing() {
                refreshData();
            }
        });
        mRecyclerView.setLoadMoreListener(new EnhanceRecyclerView.LoadMoreListener() {
            @Override
            public void onLoadMore() {
                loadMoreData();
            }
        });

refreshData()loadMoreData()加载数据的逻辑就不展示了,只是要记住在请求网络数据完之后要在他们中调用相应的mRecyclerView.setRefreshComplete()mRecyclerView.setLoadMoreComplete()来重置状态。

至于其他的AdapterLayoutManager等的设置就不多说了,与原生的RecyclerView是一样的。

效果

效果图

总结

其实总的来说难点有两个

  • 添加headerfooter。这个前面已经攻克了,而且原理也相对简单
  • 实现触摸与滑动监听逻辑。这个主要是对逻辑的理解,对整个刷新的过程做个整体分析,就能很好的理解上面的代码。对其中视图的动态显示做相应的变化与接口的调用就能很好的处理这些工程。

当然上面的实现可能还有瑕疵,希望指出,我会相应的做修改或者你们修改后可以提交给我,我统一做修改,谢谢!

项目地址:https://github.com/idisfkj/EnhanceRecyclerView

关注

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

推荐阅读更多精彩内容