前言
上篇文章介绍了 PullToRefresh 的继承关系。本片开始将介绍 PullToRefresh 的源码。由于时间的关系,关于PullToRefresh 的源码笔者也将分成两篇来介绍。本篇文章主要介绍 PullToRefresh 的下拉刷新是如何实现的。在下一篇中,将介绍如何扩展 PullToRefresh,并编写 demo。
PullToRefreshBase
从上篇文章中我们知道 IPullToRefresh 是 PullToRefresh 的基类,PullToRefreshBase 实现了 IPullToRefresh。所以我猜测 PullToRefresh 的下拉刷新是在 PullToRefreshBase 中实现的。要实现下拉刷新的效果就牵扯到 View 的事件传递机制。所以我们先从 onInterceptTouchEvent 、onTouchEvent 和 dispatchTouchEvent 方法分析。但是在 PullToRefreshBase 中只有 onInterceptTouchEvent 和 onTouchEvent 。我们先看 onInterceptTouchEvent。
onInterceptTouchEvent
@Override public final boolean onInterceptTouchEvent(MotionEvent event) {
if (!isPullToRefreshEnabled()) {
return false;
}
final int action = event.getAction();
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mIsBeingDragged = false;
return false;
}
if (action != MotionEvent.ACTION_DOWN && mIsBeingDragged) {
return true;
}
switch (action) {
case MotionEvent.ACTION_MOVE: {
// If we're refreshing, and the flag is set. Eat all MOVE events
//if (!isCanMove){
// break;
//}
if (!mScrollingWhileRefreshingEnabled && isRefreshing()) {
return true;
}
if (isReadyForPull()) {
final float y = event.getY(), x = event.getX();
final float diff, oppositeDiff, absDiff;
// We need to use the correct values, based on scroll
// direction
switch (getPullToRefreshScrollDirection()) {
case HORIZONTAL:
diff = x - mLastMotionX;
oppositeDiff = y - mLastMotionY;
break;
case VERTICAL:
default:
diff = y - mLastMotionY;
oppositeDiff = x - mLastMotionX;
break;
}
absDiff = Math.abs(diff);
if (absDiff > mTouchSlop && (!mFilterTouchEvents || absDiff > Math.abs(oppositeDiff))) {
if (mMode.showHeaderLoadingLayout() && diff >= 1f && isReadyForPullStart()) {
mLastMotionY = y;
mLastMotionX = x;
mIsBeingDragged = true;
if (mMode == Mode.BOTH) {
mCurrentMode = Mode.PULL_FROM_START;
}
} else if (mMode.showFooterLoadingLayout() && diff <= -1f && isReadyForPullEnd()) {
mLastMotionY = y;
mLastMotionX = x;
mIsBeingDragged = true;
if (mMode == Mode.BOTH) {
mCurrentMode = Mode.PULL_FROM_END;
}
}
}
}
break;
}
case MotionEvent.ACTION_DOWN: {
if (isReadyForPull()) {
mLastMotionY = mInitialMotionY = event.getY();
mLastMotionX = mInitialMotionX = event.getX();
mIsBeingDragged = false;
}
break;
}
}
return mIsBeingDragged;
}
在 onInterceptTouchEvent 方法中,主要是判断是否满足下拉或者上拉的条件。其中 isReadyForPullStart() 方法和 isReadyForPullEnd() 方法都是在具体或者某一类的刷新控件中实现的,他们返回 true 的条件就是滑动到顶端或者低端。比如 PullToRefreshAdapterViewBase 中,PullToRefreshAdapterViewBase 是 AbsListView 的子类现实下拉刷新时的父类。如果满足下拉或者上拉的条件时 mIsBeingDragged 为 true。即触摸事件被 PullToRefreshBase 拦截。此时触摸事件将有 PullToRefreshBase 的 onTouchEvent 来处理。接下来请看 onTouchEvent。
onTouchEvent
在 onTouchEvent 的 ACTION_MOVE 分之里会调用 pullEvent 方法来处理 ACTION_MOVE 事件,实现滑动的效果。
private void pullEvent() {
final int newScrollValue;
final int itemDimension;
final float initialMotionValue, lastMotionValue;
switch (getPullToRefreshScrollDirection()) {
case HORIZONTAL:
initialMotionValue = mInitialMotionX;
lastMotionValue = mLastMotionX;
break;
case VERTICAL:
default:
initialMotionValue = mInitialMotionY;
lastMotionValue = mLastMotionY;
break;
}
switch (mCurrentMode) {
case PULL_FROM_END:
newScrollValue = Math.round(Math.max(initialMotionValue - lastMotionValue, 0) / FRICTION);
itemDimension = getFooterSize();
break;
case PULL_FROM_START:
default:
newScrollValue = Math.round(Math.min(initialMotionValue - lastMotionValue, 0) / FRICTION);
itemDimension = getHeaderSize();
break;
}
setHeaderScroll(newScrollValue);
if (newScrollValue != 0 && !isRefreshing()) {
float scale = Math.abs(newScrollValue) / (float) itemDimension;
switch (mCurrentMode) {
case PULL_FROM_END:
mFooterLayout.onPull(scale);
break;
case PULL_FROM_START:
default:
mHeaderLayout.onPull(scale);
break;
}
if (mState != State.PULL_TO_REFRESH && itemDimension >= Math.abs(newScrollValue)) {
setState(State.PULL_TO_REFRESH);
} else if (mState == State.PULL_TO_REFRESH && itemDimension < Math.abs(newScrollValue)) {
setState(State.RELEASE_TO_REFRESH);
}
}
}
第 30 行,实现了 PullToRefreshBase 的滑动,是使用 View 的 scrollTo 方法来实现的。
第 32 到 42 行,调用相应得头部或者底部布局的 onPull 方法实现滑动时的动画效果。
第 44 到 48 行,对滑动状态的改变 mState = State.RELEASE_TO_REFRESH。
接下来看 onTouchEvent 的 ACTION_UP 分支
case MotionEvent.ACTION_UP: {
if (mIsBeingDragged) {
mIsBeingDragged = false;
if (mState == State.RELEASE_TO_REFRESH && (null != mOnRefreshListener
|| null != mOnRefreshListener2)) {
setState(State.REFRESHING, true);
return true;
}
// If we're already refreshing, just scroll back to the top
if (isRefreshing()) {
smoothScrollTo(0);
return true;
}
// If we haven't returned by here, then we're not in a state
// to pull, so just reset
setState(State.RESET);
return true;
}
break;
}
第 7 行,条件满足调用 setState 方法。在 setState 方法里 State.REFRESHING 分支将调用 onRefreshing 方法。在 onRefreshing 方法里会监听是否滑动到了头部的顶端或者底部的底端。如果是,将调用 callRefreshListener 方法。请看 callRefreshListener 方法。
private void callRefreshListener() {
if (null != mOnRefreshListener) {
mOnRefreshListener.onRefresh(this);
} else if (null != mOnRefreshListener2) {
if (mCurrentMode == Mode.PULL_FROM_START) {
mOnRefreshListener2.onPullDownToRefresh(this);
} else if (mCurrentMode == Mode.PULL_FROM_END) {
mOnRefreshListener2.onPullUpToRefresh(this);
}
}
}
在 callRefreshListener 方法中调用了一系列的刷新的监听者的回调方法。这些监听者正是我们自己实现的。在这些方法里可以完成我们需要的刷新操作,比如,重写请求数据。当我们完成这些操作后要主动调用 onRefreshComplete 方法。在 onRefreshComplete 方法中回调用状态重置的方法。重置 PullToRefresh 的状态。
结语
本篇文章就介绍到这里,下篇文章将介绍如何扩展 PullToRefresh。