RecyclerView已经写过两篇文章了,分别是Android 5.X新特性之RecyclerView基本解析及无限复用Android 5.X新特性之为RecyclerView添加HeaderView和FooterView,既然来到这里还没学习的,先去学习下吧。

今天我们的主题是学习为RecyclerView添加下拉刷新和上拉加载功能。

首先,我们先来学习下拉刷新,google公司已经为我们提供的一个很好的包装类,那就是SwipeRefreshLayout,这个类可以支持我们向下滑动并进行监听。那么我们先了解一些基本知识,然后再从源码的角度来解析它。

A. SwipeRefreshLayout 是一个容器,直接继承于ViewGroup。

从其源码中我们可以直接看出,它是直接继承于ViewGroup的,所以它是一个容器,既然是一个容器,那么我们就可以向其中添加View。

B. SwipeRefreshLayout 封装了一些列的方法供我们使用,其中较常用的包括以下几个。

1. setColorSchemeResources: 刷新时动画的颜色,可以设置4个
2. setProgressBackgroundColorSchemeResource: 设置刷新时进度圆环的背景颜色
3. setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener listener): 设置手势滑动监听器。
4. setRefreshing(Boolean refreshing): 设置组件的刷洗状态。
5. setSize(int size):设置进度圈的大小,只有两个值:DEFAULT、LARGE

其中最主要的是setOnRefreshListener,它是用来监听我们下拉手势的回调方法。

C. 接下来我们再从源码的角度来了解这个类:

SwipeRefreshLayout 是一个ViewGroup容器,那在向它添加子View的时候,那首先会去测量各个子View的大小来确定本身的大小,并且还会制定子View的坐标位置,最后绘制View并显示出来。针对ViewGroup的绘制我之前有写过一篇博文,大家可以去参考下Android自定义控件之继承ViewGroup创建新容器(四) ,里面有详细的讲解。而我们今天所要讲解的是从SwipeRefreshLayout 的事件机制来说起,也更符合我们下拉刷新的主题。

在SwipeRefreshLayout 的事件拦截分发器onInterceptTouchEvent中,它是这么定制的,源码如下:

	@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
ensureTarget(); final int action = MotionEventCompat.getActionMasked(ev); if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
mReturningToStart = false;
} if (!isEnabled() || mReturningToStart || canChildScrollUp()
|| mRefreshing || mNestedScrollInProgress) {
// Fail fast if we're not in a state where a swipe is possible
return false;
} switch (action) {
case MotionEvent.ACTION_DOWN:
setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true);
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
mIsBeingDragged = false;
final float initialDownY = getMotionEventY(ev, mActivePointerId);
if (initialDownY == -1) {
return false;
}
mInitialDownY = initialDownY;
break; case MotionEvent.ACTION_MOVE:
if (mActivePointerId == INVALID_POINTER) {
Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");
return false;
} final float y = getMotionEventY(ev, mActivePointerId);
if (y == -1) {
return false;
}
final float yDiff = y - mInitialDownY;
if (yDiff > mTouchSlop && !mIsBeingDragged) {
mInitialMotionY = mInitialDownY + mTouchSlop;
mIsBeingDragged = true;
mProgress.setAlpha(STARTING_PROGRESS_ALPHA);
}
break; case MotionEventCompat.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break; case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mIsBeingDragged = false;
mActivePointerId = INVALID_POINTER;
break;
} return mIsBeingDragged;
}

它最终返回的是代表是否滑动的mIsBeingDragged布尔值。在我们按下,抬起,或取消时mIsBeingDragged的值是false,意思是在这几个动作中,SwipeRefreshLayout 本身是不拦截事件的,而是传递给父类,让父类进行处理。而我们主要来看MotionEvent.ACTION_MOVE:这个动作,它首先判断是否是可用的活动id: mActivePointerId,然后根据得到mActivePointerId来获取滑动的中坐标距离值:Y,然后做出判断:如果Y==-1就代表没滑动,所以直接返回false表示不拦截;如果Y值大于规定的最小滑动距离mTouchSlop值,并且!mIsBeingDragged为真,那么就让mIsBeingDragged == true;并返回,也就是在这种情况下,SwipeRefreshLayout 它自己消化了事件,而不是传递给父类。因此,当我们在向下滑动了一定的距离时,SwipeRefreshLayout 就是捕捉到当前的事件。

那么我们再来看看它是怎么处理当前捕捉到的事件的。请看源码:

	@Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
int pointerIndex = -1; if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
mReturningToStart = false;
} if (!isEnabled() || mReturningToStart || canChildScrollUp() || mNestedScrollInProgress) {
// Fail fast if we're not in a state where a swipe is possible
return false;
} switch (action) {
case MotionEvent.ACTION_DOWN:
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
mIsBeingDragged = false;
break; case MotionEvent.ACTION_MOVE: {
pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
if (pointerIndex < 0) {
Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
return false;
} final float y = MotionEventCompat.getY(ev, pointerIndex);
final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
if (mIsBeingDragged) {
if (overscrollTop > 0) {
moveSpinner(overscrollTop);
} else {
return false;
}
}
break;
}
case MotionEventCompat.ACTION_POINTER_DOWN: {
pointerIndex = MotionEventCompat.getActionIndex(ev);
if (pointerIndex < 0) {
Log.e(LOG_TAG, "Got ACTION_POINTER_DOWN event but have an invalid action index.");
return false;
}
mActivePointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
break;
} case MotionEventCompat.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break; case MotionEvent.ACTION_UP: {
pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
if (pointerIndex < 0) {
Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id.");
return false;
} final float y = MotionEventCompat.getY(ev, pointerIndex);
final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
mIsBeingDragged = false;
finishSpinner(overscrollTop);
mActivePointerId = INVALID_POINTER;
return false;
}
case MotionEvent.ACTION_CANCEL:
return false;
} return true;
}

同样的道理在MotionEvent.ACTION_DOWN和case MotionEvent.ACTION_CANCEL时不处理事件,交给父类处理。而在MotionEvent.ACTION_MOVE:中获取到与顶端窗口的overscrollTop,如果overscrollTop值大于0就调用moveSpinner(overscrollTop);方法来初始化mCircleView旋转的。最后在MotionEvent.ACTION_UP:抬起事件中,同样获取overscrollTop,且调用finishSpinner(overscrollTop);方法来完成mCircleView的旋转事件并回复一些属性配置值。

然后我们再来看看finishSpinner(overscrollTop);方法中是怎么处理的。

private void finishSpinner(float overscrollTop) {
if (overscrollTop > mTotalDragDistance) {
setRefreshing(true, true /* notify */);
} else {
// cancel refresh
mRefreshing = false;
mProgress.setStartEndTrim(0f, 0f);
Animation.AnimationListener listener = null;
if (!mScale) {
listener = new Animation.AnimationListener() { @Override
public void onAnimationStart(Animation animation) {
} @Override
public void onAnimationEnd(Animation animation) {
if (!mScale) {
startScaleDownAnimation(null);
}
} @Override
public void onAnimationRepeat(Animation animation) {
} };
}
animateOffsetToStartPosition(mCurrentTargetOffsetTop, listener);
mProgress.showArrow(false);
}
}

方法里面很简单,if (overscrollTop > mTotalDragDistance) 就调用setRefreshing(true, true /* notify */);用来设置刷新事件的,否则就回复初始前的属性配置值。

再来看看setRefreshing(true, true)方法:

private void setRefreshing(boolean refreshing, final boolean notify) {
if (mRefreshing != refreshing) {
mNotify = notify;
ensureTarget();
mRefreshing = refreshing;
if (mRefreshing) {
animateOffsetToCorrectPosition(mCurrentTargetOffsetTop, mRefreshListener);
} else {
startScaleDownAnimation(mRefreshListener);
}
}
}

也很好理解,因为传进来的refreshing值为true,所以它会调用animateOffsetToCorrectPosition(mCurrentTargetOffsetTop, mRefreshListener);来开启mCircleView的动画展示,并传进了mRefreshListener监听器,这个监听器是什么呢?来看看

private Animation.AnimationListener mRefreshListener = new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
} @Override
public void onAnimationRepeat(Animation animation) {
} @Override
public void onAnimationEnd(Animation animation) {
if (mRefreshing) {
// Make sure the progress view is fully visible
mProgress.setAlpha(MAX_ALPHA);
mProgress.start();
if (mNotify) {
if (mListener != null) {
mListener.onRefresh();
}
}
mCurrentTargetOffsetTop = mCircleView.getTop();
} else {
reset();
}
}
};

它是一个动画监听器,在动画结束时调用mListener.onRefresh();而mListener是一个接口,里面封装了一个onRefresh()的方法,并且它暴露了对外调用的方法setOnRefreshListener(),所以我们可以在Activity中调用该方法可以实现我们自己的逻辑业务。

ok,到这里,相信大家都知道了wipeRefreshLayout.setOnRefreshListener();的工作原理,那么我们现在来实现我们的刷新功能吧;

首先,我们的布局文件先把RecyclerView放到SwipeRefreshLayout容器中:

recycer_view.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/srl_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
custom:listDividerSize="2dp"
custom:listDividerBackgroundColor="#FF0000"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>

然后RecycerActivity中配置一些SwipeRefreshLayout属性值,并调用setOnRefreshListener方法并在onRefresh()实现自己的逻辑业务:

srl_refresh.setColorSchemeResources(android.R.color.holo_blue_light,
android.R.color.holo_red_light,android.R.color.holo_orange_light,
android.R.color.holo_green_light);
srl_refresh.setProgressBackgroundColorSchemeResource(android.R.color.white);
srl_refresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
List<String> newDatas = new ArrayList<String>();
for (int i = 0; i <5; i++) {
int index = i + 1;
newDatas.add("new item" + index);
}
mBaseRecyclerAdapter.addDatas(newDatas);
srl_refresh.setRefreshing(false);
Toast.makeText(RecycerActivity.this, "更新了五条数据...", Toast.LENGTH_SHORT).show();
}
}, 5000);
}
});

来看看结果吧

好了,RecyclerView利用SwipeRefreshLayout实现上拉刷新我们已经实现了,并且也带大家看过它的实现原理了,相信大家一定能更好的掌握它了,那么接下来我们就来实现上拉加载了。

在上一讲中,我们已经实现了在底部添加上了一个FooterView,那么我们现在可以利用它来实现我们的上拉加载。

其思想我们可以这样设计,当我们滑动到最后一个ItemView时,让它去加载数据,那怎么获取到列表的最后一个ItemView呢?所幸的是,在RecyclerView中封装的LayoutManger子类中有这样的方法可以供我们获取到最后一个ItemView,该方法是findLastVisibleItemPosition();那我们又该怎么监听RecyclerView滑动呢?可以调用它的addOnScrollListener()方法,由此我们找到了解决方案

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if(newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem + 1 == mBaseRecyclerAdapter.getItemCount()){
mBaseRecyclerAdapter.changeStatus(BaseRecyclerAdapter.LOADING_MORE);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
List<String> newDatas = new ArrayList<String>();
for (int i = 0; i< 5; i++) {
int index = i +1;
newDatas.add("more item" + index);
}
if(newDatas == null){
mBaseRecyclerAdapter.changeStatus(BaseRecyclerAdapter.LOADED_MORE);
return;
}
mBaseRecyclerAdapter.addMoreDatas(newDatas);
mBaseRecyclerAdapter.changeStatus(BaseRecyclerAdapter.LOAD_MORE);
Toast.makeText(RecycerActivity.this,"已加载了数据", Toast.LENGTH_SHORT).show();
}
},1000);
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
lastVisibleItem = linearLayoutManger.findLastVisibleItemPosition();
}
});

代码解释:首先我们会在onScrolled方法中回去到最后一行的ItenView,然后再onScrollStateChanged方法中进行必要的判断,如果lastVisibleItem + 1 == mBaseRecyclerAdapter.getItemCount(),那么就可以确定给ItemView是最后一个ItemView,然后就可以用来实现我们的业务逻辑了,在这里我让它新加了5条数据,然后更新Adapter。

最后在onBindViewHolder稍作修改,如下

 @Override
public void onBindViewHolder(BaseViewHolderHelper holder, int position) {
//把每一个itemView设置一个标签,方便以后根据标签获取到该itemView以便做其他事项,比较点击事件
if(getItemViewType(position) == TYPE_HEADER){
return;
}else if(getItemViewType(position) == TYPE_FOOTER){
FooterViewHolder footViewHolder=(FooterViewHolder)holder;
footViewHolder.footView.setText("上拉加载更多...");
switch (status){
case LOAD_MORE:
footViewHolder.footView.setText("上拉加载更多...");
break;
case LOADING_MORE:
footViewHolder.footView.setText("正在加载中...");
break;
case LOADED_MORE:
footViewHolder.footView.setText("已加载完毕");
break;
}
} else{
...
}
}

ok,来看看结果吧:

好了,已经实现了上拉加载的功能了,相信大家也都可以做很多事情了。

总结:本节主题是为RecyclerView添加下拉刷新和上拉加载的功能,基本的思路也都已讲清楚了,而且着重的讲解了一下利用SwipeRefreshLayout实现下拉刷新的实现原理,相信大家通过这节更能学到一些原理性的东西,ok,今天就讲到这里吧。祝大家学习愉快。

更多资讯请关注微信平台,有博客更新会及时通知。爱学习爱技术。

Android 5.X新特性之为RecyclerView添加下拉刷新和上拉加载及SwipeRefreshLayout实现原理的更多相关文章

  1. Android 5.X新特性之为RecyclerView添加HeaderView和FooterView

    上一节我们讲到了 Android 5.X新特性之RecyclerView基本解析及无限复用 相信大家也应该熟悉了RecyclerView的基本使用,这一节我们来学习下,为RecyclerView添加H ...

  2. Android实现RecyclerView的下拉刷新和上拉载入很多其它

    需求 先上效果图, Material Design风格的下拉刷新和上拉载入很多其它. 源代码地址(欢迎star) https://github.com/studychen/SeeNewsV2 假设对于 ...

  3. Android 使用PullToRefresh实现下拉刷新和上拉加载(ExpandableListView)

    PullToRefresh是一套实现非常好的下拉刷新库,它支持: 1.ListView 2.ExpandableListView 3.GridView 4.WebView 等多种常用的需要刷新的Vie ...

  4. 实现RecyclerView下拉刷新和上拉加载更多以及RecyclerView线性、网格、瀑布流效果演示

    实现RecyclerView下拉刷新和上拉加载更多以及RecyclerView线性.网格.瀑布流效果演示 效果预览 实例APP 小米应用商店 使用方法 build.gradle文件 dependenc ...

  5. 使用SwipeRefreshLayout和RecyclerView实现仿“简书”下拉刷新和上拉载入很多其它

    一.概述 本篇博客介绍的是怎样使用SwipeRefreshLayout和RecyclerView实现高仿简书Android端的下拉刷新和上拉载入很多其它的效果. 依据效果图能够发现,本案例实现了例如以 ...

  6. 手把手教你实现RecyclerView的下拉刷新和上拉加载更多

    手把手教你实现RecyclerView的下拉刷新和上拉加载更多     版权声明:本文为博主原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接和本声明. 本文链接:https:// ...

  7. RecyclerView下拉刷新和上拉加载更多实现

    RecyclerView下拉刷新和上拉加载更多实现 转 https://www.jianshu.com/p/4ea7c2d95ecf   在Android开发中,RecyclerView算是使用频率非 ...

  8. Android 实现下拉刷新和上拉加载更多的RECYCLERVIEW和SCROLLVIEW

    PullRefreshRecyclerView.java /** * 类说明:下拉刷新上拉加载更多的RecyclerView * Author: gaobaiq * Date: 2016/5/9 18 ...

  9. Android 下拉刷新上啦加载SmartRefreshLayout + RecyclerView

    在弄android刷新的时候,可算是耗费了一番功夫,最后发觉有现成的控件,并且非常好用,这里记录一下. 原文是 https://blog.csdn.net/huangxin112/article/de ...

随机推荐

  1. 揭秘Windows10 UWP中的httpclient接口[2]

    阅读目录: 概述 如何选择 System.Net.Http Windows.Web.Http HTTP的常用功能 修改http头部 设置超时 使用身份验证凭据 使用客户端证书 cookie处理 概述 ...

  2. Android Secret Code

    我们很多人应该都做过这样的操作,打开拨号键盘输入*#*#4636#*#*等字符就会弹出一个界面显示手机相关的一些信息,这个功能在Android中被称为android secret code,除了这些系 ...

  3. [翻译]AKKA笔记 - 有限状态机 -1

    原文地址:http://rerun.me/2016/05/21/akka-notes-finite-state-machines-1/ 我最近有个机会在工作上使用了Akka FSM,是个非常有趣的例子 ...

  4. ★Kali信息收集~ 1.Google Hacking + Github Hacking

    一.google hacking site site:cnblogs.com 毒逆天 intitle intitle:login allintitle allintitle:index of alli ...

  5. Git(远程仓库:git@oschina)-V2.0

    1.注册git@osc(也就是“码云”) 这里会提示注册密码==push密码,反正一定要记住的东西.   2.安装git 这里要设置个人信息 git config --list //查看git信息 g ...

  6. JavaScript权威设计--Window对象(简要学习笔记十三)

    1.Window对象是所有客户端JavaScript特性和API的主要接入点. Window对象中的一个重要属性是document,它引用Document对象. JavaScript程序可以通过Doc ...

  7. LOCK TABLES和UNLOCK TABLES与Transactions的交互

    LOCK TABLES对事务不安全,并且在试图锁定表之前隐式提交任何活动事务. UNLOCK TABLES只有在LOCK TABLES已经获取到表锁时,会隐式提交任何活动事务.对于下面的一组语句,UN ...

  8. 开源Word读写组件DocX 的深入研究和问题总结

    一. 前言 前两天看到了asxinyu大神的[原创]开源Word读写组件DocX介绍与入门,正好我也有类似的自动生成word文档得需求,于是便仔细的研究了这个DocX. 我也把它融入到我的项目当中并进 ...

  9. ubuntu14.04安装及web环境的搭建

    在进行接下来的工作之前首先得准备一个Ubuntu的启动U盘以及腾出一个至少50G的系统盘(在网上看到有人用20G的,不知道效果怎么样).关于Ubuntu启动U盘大家可以参考这个帖子 http://ti ...

  10. OCP考点实战演练02-日常维护篇

    本系列宗旨:真正掌握OCP考试中所考察的技能,坚决不做Paper OCP! 实验环境:RHEL 6.4 + Oracle 11.2.0.4 OCP考点实战演练02-日常维护篇 1.数据库体系结构和AS ...