转载请标明出处:
http://blog.csdn.net/hai_qing_xu_kong/article/details/73802453
本文出自:【顾林海的博客】
个人开发的微信小程序,目前功能是书籍推荐,后续会完善一些新功能,希望大家多多支持!
Android-PullToRefresh是一款非常出名的上拉加载和下拉刷新控件,相信同学们都使用过这个控件,Android-PullToRefresh控件内部是如何实现的呢,我们通过阅读源码来一窥究竟。这是它的github的地址,下载下来后可以看到无论是ListView、SrollView、WebView等,都是继承自PullToRefreshBase这个抽象类,这个抽象类在整个实现过程中起着举足轻重的作用,并且该控件使用了模板方法模式,封装了上拉加载和下拉刷新的逻辑以及事件的回调,同时将触发这些操作的条件交由子类来实现(这里面很容易看到的是,我们在判断控件是否滑动到顶部或是底部时的具体逻辑,是通过子类实现父类的抽象方法来实现的),也就是说,我们可以很方便的实现一个新的刷新控件(只要实现模板定义的抽象方法或是重写相关方法)。
public abstract class PullToRefreshBase extends LinearLayout implements IPullToRefresh {}
public abstract Orientation getPullToRefreshScrollDirection();
private void init(Context context, AttributeSet attrs) {
//(1)通过getPullToRefreshScrollDirection方法判断是垂直滑动还是水平滑动。
switch (getPullToRefreshScrollDirection()) {
case HORIZONTAL:
setOrientation(LinearLayout.HORIZONTAL);
break;
case VERTICAL:
default:
setOrientation(LinearLayout.VERTICAL);
break;
}
//(2)设置内容居中。
setGravity(Gravity.CENTER);
//(3)使用 ViewConfiguration 获取用户手指滑动距离,用于判定滑动依据。
ViewConfiguration config = ViewConfiguration.get(context);
mTouchSlop = config.getScaledTouchSlop();
// (4)获取自定义属性
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PullToRefresh);
//获取刷新的模式
if (a.hasValue(R.styleable.PullToRefresh_ptrMode)) {
mMode = Mode.mapIntToValue(a.getInteger(R.styleable.PullToRefresh_ptrMode, 0));
}
//获取刷新动画样式
if (a.hasValue(R.styleable.PullToRefresh_ptrAnimationStyle)) {
mLoadingAnimationStyle = AnimationStyle.mapIntToValue(a.getInteger(
R.styleable.PullToRefresh_ptrAnimationStyle, 0));
}
//(5)创建真正刷新的控件
mRefreshableView = createRefreshableView(context, attrs);
//(6)添加刷新控件
addRefreshableView(context, mRefreshableView);
//(7)根据刷新和加载的模式分别创建头部刷新控件和底部加载控件
mHeaderLayout = createLoadingLayout(context, Mode.PULL_FROM_START, a);
mFooterLayout = createLoadingLayout(context, Mode.PULL_FROM_END, a);
//(8)获取自定义属性
if (a.hasValue(R.styleable.PullToRefresh_ptrRefreshableViewBackground)) {
//背景
Drawable background = a.getDrawable(R.styleable.PullToRefresh_ptrRefreshableViewBackground);
if (null != background) {
mRefreshableView.setBackgroundDrawable(background);
}
} else if (a.hasValue(R.styleable.PullToRefresh_ptrAdapterViewBackground)) {
//背景
Utils.warnDeprecation("ptrAdapterViewBackground", "ptrRefreshableViewBackground");
Drawable background = a.getDrawable(R.styleable.PullToRefresh_ptrAdapterViewBackground);
if (null != background) {
mRefreshableView.setBackgroundDrawable(background);
}
}
if (a.hasValue(R.styleable.PullToRefresh_ptrOverScroll)) {
mOverScrollEnabled = a.getBoolean(R.styleable.PullToRefresh_ptrOverScroll, true);
}
if (a.hasValue(R.styleable.PullToRefresh_ptrScrollingWhileRefreshingEnabled)) {
mScrollingWhileRefreshingEnabled = a.getBoolean(
R.styleable.PullToRefresh_ptrScrollingWhileRefreshingEnabled, false);
}
//(9)自定义属性派生给子类
handleStyledAttributes(a);
a.recycle();
//(10)根据mode刷新UI
updateUIForMode();
}
private AnimationStyle mLoadingAnimationStyle = AnimationStyle.getDefault();
protected LoadingLayout createLoadingLayout(Context context, Mode mode, TypedArray attrs) {
LoadingLayout layout = mLoadingAnimationStyle.createLoadingLayout(context, mode,
getPullToRefreshScrollDirection(), attrs);
layout.setVisibility(View.INVISIBLE);
return layout;
}
protected void updateUIForMode() {
final LayoutParams lp = getLoadingLayoutLayoutParams();
if (this == mHeaderLayout.getParent()) {
removeView(mHeaderLayout);
}
if (mMode.showHeaderLoadingLayout()) {
addViewInternal(mHeaderLayout, 0, lp);
}
if (this == mFooterLayout.getParent()) {
removeView(mFooterLayout);
}
if (mMode.showFooterLoadingLayout()) {
addViewInternal(mFooterLayout, lp);
}
refreshLoadingViewsSize();
mCurrentMode = (mMode != Mode.BOTH) ? mMode : Mode.PULL_FROM_START;
}
无论是下拉刷新(右拉)还是上拉加载(左拉),比如下拉刷新时,我们的手指在屏幕上能一直往下拖动一段距离,在上面的refreshLoadingViewSize方法中设置了这个拖动的一个距离:
protected final void refreshLoadingViewsSize() {
final int maximumPullScroll = (int) (getMaximumPullScroll() * 1.2f);
int pLeft = getPaddingLeft();
int pTop = getPaddingTop();
int pRight = getPaddingRight();
int pBottom = getPaddingBottom();
switch (getPullToRefreshScrollDirection()) {
case HORIZONTAL:
if (mMode.showHeaderLoadingLayout()) {
mHeaderLayout.setWidth(maximumPullScroll);
pLeft = -maximumPullScroll;
} else {
pLeft = 0;
}
if (mMode.showFooterLoadingLayout()) {
mFooterLayout.setWidth(maximumPullScroll);
pRight = -maximumPullScroll;
} else {
pRight = 0;
}
break;
case VERTICAL:
if (mMode.showHeaderLoadingLayout()) {
mHeaderLayout.setHeight(maximumPullScroll);
pTop = -maximumPullScroll;
} else {
pTop = 0;
}
if (mMode.showFooterLoadingLayout()) {
mFooterLayout.setHeight(maximumPullScroll);
pBottom = -maximumPullScroll;
} else {
pBottom = 0;
}
break;
}
setPadding(pLeft, pTop, pRight, pBottom);
}
PullToRefresh内部的布局已经讲解清楚,接下来具体分析一下触摸事件的处理,查看源码我们知道PullToRefreshBase继承自LinearLayout,也就是说PullToRefreshBase本身就是一个容器,用于存放顶部和底部的LoadingLayout以及中间的T(ListView、SrollView、WebView ),那么问题来了,当我们手指触摸PullToRefreshBase容器时,整个容器的拖动何时处理,内部View的滚动何时处理,这就需要进行相应的事件传递。PullToRefreshBase内部重写了onIterceptTouchEvent和onTouchEvent方法,下面我们先对这两个有事件相关的回调函数进行扫盲。
onIterceptTouchEvent方法,我们称它为事件的拦截,默认返回super.onInterceptTouchEvent(ev),事件默认会被拦截,并将拦截的事件交由当前View的onTouchEvent进行处理;如果返回true,事件的处理和返回super.onInterceptTouchEvent(ev)的事件处理一样,交由当前View的onTouchEvent进行处理;如果返回false,表示事件放行(不拦截),当前View上的事件会被传递到子View上,由子View的onTouchEvent进行处理。
onTouchEvent方法,我们称它为事件的响应,默认返回super.onTouchEvent(ev),同事事件会从当前View向上传递,由上层View的onTouchEvent来接收处理;如果返回false,事件处理与返回super.onTouchEvent(ev)的事件处理一样,交由上层处理;如果返回true,说明当前的事件被接收并被消费。
接下来我们从onIterceptTouchEvent方法也就是事件的拦截来说起。
@Override
public final boolean onInterceptTouchEvent(MotionEvent event) {
/*
T1:当不支持上拉加载和下拉刷新操作时,不进行事件的拦截,交由子View(我们的刷新控件,如:ListView、ScrollView等)来处理。
*/
if (!isPullToRefreshEnabled()) {
return false;
}
final int action = event.getAction();
/*
T2:当手指取消触摸或是离开屏幕时,说明一次刷新的操作结束(无论是否真正进行了刷新和加载的操作),
最后将事件的处理交由子View(T)来处理。
*/
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mIsBeingDragged = false;
return false;
}
/*
T3:当我们手指在屏幕上拖动时(mIsBeingDragged标志位为true,说明当前满足了刷新和加载操作),进行
事件的拦截,交由自身onTouchEvent来处理。
*/
if (action != MotionEvent.ACTION_DOWN && mIsBeingDragged) {
return true;//由自身onTouchEvent消费
}
/*
T4:mIsBeingDragged标志位什么时候为true?需要手指触摸该控件时进行判断:当前的子View(T)是否到达了顶部以满足下拉刷新的条件,或是
当前的子View(T)是否滑动到了底部以满足上拉加载的条件。
*/
switch (action) {
case MotionEvent.ACTION_MOVE: {
/*
T5:mScrollingWhileRefreshingEnabled标志位用于说明在刷新过程中,手指再次滑动控件,这时控件是否能滑动,当mScrollingWhileRefreshingEnabled为
false时,说明刷新状态下控件不能被滑动,这时就需要事件的拦截。这里的isRefreshing用于判断当前是否处于刷新状态。
*/
if (!mScrollingWhileRefreshingEnabled && isRefreshing()) {
return true;
}
/*
T6:isReadyForPull方法的作用是检查当前控件是否满足刷新和加载操作条件,可以看的isReadyForPull方法中调用了isReadyForPullStart和isReadyForPullEnd
抽象方法,这两个方法交由实现类来实现。
*/
if (isReadyForPull()) {
final float y = event.getY(), x = event.getX();
final float diff, oppositeDiff, absDiff;
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()) {
//T7:下拉刷新
mLastMotionY = y;
mLastMotionX = x;
mIsBeingDragged = true;
if (mMode == Mode.BOTH) {
mCurrentMode = Mode.PULL_FROM_START;
}
} else if (mMode.showFooterLoadingLayout() && diff <= -1f && isReadyForPullEnd()) {
//T8:上拉加载
mLastMotionY = y;
mLastMotionX = x;
mIsBeingDragged = true;
if (mMode == Mode.BOTH) {
mCurrentMode = Mode.PULL_FROM_END;
}
}
}
}
break;
}
case MotionEvent.ACTION_DOWN: {
if (isReadyForPull()) {
/*
T9:当我们手指按下屏幕时,试想下如果当前子View(T)位于顶部或是底部时,我们拦截事件会导致T无法上拉或下拉操作,因此
通过isReadyForPull方法检查当前T是否位于顶部或是滑动到了底部位置,这时将mIsBeingDragged设置为false,不进行事件的拦截,交由
T来处理。
*/
mLastMotionY = mInitialMotionY = event.getY();
mLastMotionX = mInitialMotionX = event.getX();
mIsBeingDragged = false;
}
break;
}
}
return mIsBeingDragged;
}
针对第一条:我们在上面的事件拦截方法的第一部分T1中可以看到一个条件判断语句,isPullToRefreshEnabled方法用于判断当前控件是否支持手动拖动来触发上拉加载和下拉刷新操作,也就是说当控件不支持上拉加载和下拉刷新时,需要将事件传递给它的子View(T)来处理,这样我们的T(ListView、SrollView…)才能处理自身的触摸事件。针对第二条:T2手指离开屏幕或是取消触摸事件交由T来处理。针对第三条:在手指触摸按下或是触摸时(前提在设置Mode为FULL_FROM_START或是FULL_FROM_END亦或是BOTH),依据Mode判断T是否需要刷新或加载,具体看以下代码:
/**
* 何时下拉刷新和上拉加载,交由子类来实现
*/
private boolean isReadyForPull() {
switch (mMode) {
case PULL_FROM_START:
return isReadyForPullStart();
case PULL_FROM_END:
return isReadyForPullEnd();
case BOTH:
return isReadyForPullEnd() || isReadyForPullStart();
default:
return false;
}
}
调用的抽象方法交由子类来实现,isReadyForPullStart方法用于说明当前控件是否做好下拉刷新的准备,isReadyForPullEnd方法用于说明当前控件是否做好上拉加载的准备。最后我们看在执行到MotionEvent.ACTION_DOWN,也就是手指按下屏幕时,如果加载或刷新都已经准备好了,这里面的mIsBeingDraggd标志位设置为false,最后return mIsBeingDraggd,既然加载或刷新都准备好了,为什么还要进行事件的拦截呢?在这里举个例子:如果我们进入页面,此时ListView已经滑动到了顶部,这时我们想往下滑,如果在onIterceptTouchEvent方法中返回了true,What? 页面怎么滑动不了了,这是因为我们将事件拦截了,所以在手指按下屏幕是并且满足刷新或加载条件时,事件还是交由T来处理。
事件的拦截已经讲解完毕,接下来聊聊事件的响应onTouchEvent方法的实现:
@Override
public final boolean onTouchEvent(MotionEvent event) {
if (!isPullToRefreshEnabled()) {
return false;
}
if (!mScrollingWhileRefreshingEnabled && isRefreshing()) {
return true;
}
if (event.getAction() == MotionEvent.ACTION_DOWN && event.getEdgeFlags() != 0) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE: {
if (mIsBeingDragged) {
mLastMotionY = event.getY();
mLastMotionX = event.getX();
pullEvent();
return true;
}
break;
}
case MotionEvent.ACTION_DOWN: {
if (isReadyForPull()) {
mLastMotionY = mInitialMotionY = event.getY();
mLastMotionX = mInitialMotionX = event.getX();
return true;
}
break;
}
case MotionEvent.ACTION_CANCEL:
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 (isRefreshing()) {
smoothScrollTo(0);
return true;
}
setState(State.RESET);
return true;
}
break;
}
}
return false;
}
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()) {
/*
计算出下拉或上拉时 刷新View显示的占比
*/
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)) {
/*
头部和底部刷新View显示的高度低于刷新View的高度,说明还没有到达刷新状态
*/
setState(State.PULL_TO_REFRESH);
} else if (mState == State.PULL_TO_REFRESH && itemDimension < Math.abs(newScrollValue)) {
/*
头部和底部刷新View显示的高度高于刷新View的高度,说明手指松开执行刷新
*/
setState(State.RELEASE_TO_REFRESH);
}
}
}
protected final void setHeaderScroll(int value) {
final int maximumPullScroll = getMaximumPullScroll();
value = Math.min(maximumPullScroll, Math.max(-maximumPullScroll, value));
if (mLayoutVisibilityChangesEnabled) {
if (value < 0) {
mHeaderLayout.setVisibility(View.VISIBLE);
} else if (value > 0) {
mFooterLayout.setVisibility(View.VISIBLE);
} else {
mHeaderLayout.setVisibility(View.INVISIBLE);
mFooterLayout.setVisibility(View.INVISIBLE);
}
}
if (USE_HW_LAYERS) {
ViewCompat.setLayerType(mRefreshableViewWrapper, value != 0 ? View.LAYER_TYPE_HARDWARE
: View.LAYER_TYPE_NONE);
}
switch (getPullToRefreshScrollDirection()) {
case VERTICAL:
scrollTo(0, value);
break;
case HORIZONTAL:
scrollTo(value, 0);
break;
}
}
通过scrollTo方法来实现HeaderLayout和FooterLayout 的移动,scrollTo 通过拖动的距离移动整个控件的位置从而达到拖动HeaderLayout和FooterLayout 的效果。在pullEvent方法中,通过HeaderLayout和FooterLayout显示的区域来判断当前是否处于刷新状态(也就是HeaderLayout和FooterLayout完全被显示出来),当State状态为PULL_TO_REFRESH时还没有到达刷新状态,当State状态为RELEASE_TO_REFRESH时说明已经处于刷新状态。手指离开屏幕时可以根据该状态做出相应的操作。
case MotionEvent.ACTION_CANCEL:
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 (isRefreshing()) {
smoothScrollTo(0);
return true;
}
setState(State.RESET);
return true;
}
break;
}
从上面的代码中可以看到当State状态为RELEASE_TO_REFRESH时说明已经处于刷新状态,这时调用setState(State.REFRESHING ,true)方法将State设置成REFRESHING,说明当前处于刷新状态:
final void setState(State state, final boolean... params) {
mState = state;
switch (mState) {
case RESET:
onReset();
break;
case PULL_TO_REFRESH:
onPullToRefresh();
break;
case RELEASE_TO_REFRESH:
onReleaseToRefresh();
break;
case REFRESHING:
case MANUAL_REFRESHING:
onRefreshing(params[0]);
break;
case OVERSCROLLING:
// NO-OP
break;
}
if (null != mOnPullEventListener) {
mOnPullEventListener.onPullEvent(this, mState, mCurrentMode);
}
}
protected void onRefreshing(final boolean doScroll) {
/*
通知给相应的Layout当前处于刷新状态。
*/
if (mMode.showHeaderLoadingLayout()) {
mHeaderLayout.refreshing();
}
if (mMode.showFooterLoadingLayout()) {
mFooterLayout.refreshing();
}
if (doScroll) {
if (mShowViewWhileRefreshing) {
OnSmoothScrollFinishedListener listener = new OnSmoothScrollFinishedListener() {
@Override
public void onSmoothScrollFinished() {
/*
刷新和加载事件的回调
*/
callRefreshListener();
}
};
switch (mCurrentMode) {
case MANUAL_REFRESH_ONLY:
case PULL_FROM_END:
smoothScrollTo(getFooterSize(), listener);
break;
default:
case PULL_FROM_START:
smoothScrollTo(-getHeaderSize(), listener);
break;
}
} else {
smoothScrollTo(0);
}
} else {
callRefreshListener();
}
}
protected final void smoothScrollTo(int scrollValue, OnSmoothScrollFinishedListener listener) {
smoothScrollTo(scrollValue, getPullToRefreshScrollDuration(), 0, listener);
}
private final void smoothScrollTo(int newScrollValue, long duration, long delayMillis,
OnSmoothScrollFinishedListener listener) {
if (null != mCurrentSmoothScrollRunnable) {
mCurrentSmoothScrollRunnable.stop();
}
final int oldScrollValue;
switch (getPullToRefreshScrollDirection()) {
case HORIZONTAL:
oldScrollValue = getScrollX();
break;
case VERTICAL:
default:
oldScrollValue = getScrollY();
break;
}
if (oldScrollValue != newScrollValue) {
if (null == mScrollAnimationInterpolator) {
// Default interpolator is a Decelerate Interpolator
mScrollAnimationInterpolator = new DecelerateInterpolator();
}
mCurrentSmoothScrollRunnable = new SmoothScrollRunnable(oldScrollValue, newScrollValue, duration, listener);
if (delayMillis > 0) {
postDelayed(mCurrentSmoothScrollRunnable, delayMillis);
} else {
post(mCurrentSmoothScrollRunnable);
}
}
}
final class SmoothScrollRunnable implements Runnable {
private final Interpolator mInterpolator;
private final int mScrollToY;
private final int mScrollFromY;
private final long mDuration;
private OnSmoothScrollFinishedListener mListener;
private boolean mContinueRunning = true;
private long mStartTime = -1;
private int mCurrentY = -1;
public SmoothScrollRunnable(int fromY, int toY, long duration, OnSmoothScrollFinishedListener listener) {
mScrollFromY = fromY;
mScrollToY = toY;
mInterpolator = mScrollAnimationInterpolator;
mDuration = duration;
mListener = listener;
}
@Override
public void run() {
if (mStartTime == -1) {
mStartTime = System.currentTimeMillis();
} else {
long normalizedTime = (1000 * (System.currentTimeMillis() - mStartTime)) / mDuration;
normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0);
final int deltaY = Math.round((mScrollFromY - mScrollToY)
* mInterpolator.getInterpolation(normalizedTime / 1000f));
mCurrentY = mScrollFromY - deltaY;
setHeaderScroll(mCurrentY);
}
if (mContinueRunning && mScrollToY != mCurrentY) {
ViewCompat.postOnAnimation(PullToRefreshBase.this, this);
} else {
if (null != mListener) {
mListener.onSmoothScrollFinished();
}
}
}
public void stop() {
mContinueRunning = false;
removeCallbacks(this);
}
}
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);
}
}
}
到这里事件的处理已经讲解完毕,总结来说,PullToRefreshBase主要负责以下几件事:
PullToRefreshBase这个抽象类的职责已经讲解了差不多了,现在我们就来自定义一个ScrollView的上拉加载和下拉刷新的控件,首先实现PullToRefreshBase抽线类定以的几个方法
@Override
public Orientation getPullToRefreshScrollDirection() {
return null;
}
@Override
protected ScrollView createRefreshableView(Context context, AttributeSet attrs) {
return null;
}
@Override
protected boolean isReadyForPullEnd() {
return false;
}
@Override
protected boolean isReadyForPullStart() {
return false;
}
public class PullToRefreshScrollView extends PullToRefreshBase {
public PullToRefreshScrollView(Context context) {
super(context);
}
public PullToRefreshScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PullToRefreshScrollView(Context context, Mode mode) {
super(context, mode);
}
public PullToRefreshScrollView(Context context, Mode mode, AnimationStyle style) {
super(context, mode, style);
}
@Override
public final Orientation getPullToRefreshScrollDirection() {
return Orientation.VERTICAL;
}
@Override
protected ScrollView createRefreshableView(Context context, AttributeSet attrs) {
ScrollView scrollView;
scrollView = new ScrollView(context, attrs);
scrollView.setId(R.id.scrollview);
return scrollView;
}
@Override
protected boolean isReadyForPullStart() {
return mRefreshableView.getScrollY() == 0;
}
@Override
protected boolean isReadyForPullEnd() {
View scrollViewChild = mRefreshableView.getChildAt(0);
if (null != scrollViewChild) {
return mRefreshableView.getScrollY() >= (scrollViewChild.getHeight() - getHeight());
}
return false;
}
}