谁知道getDrawable()未知方法错误通常怎么解决private voidsetUpshade shadow

3489人阅读
Android(262)
项目链接:
此项目的文件目录是:
核心库是library,其余3个目录是使用pulltorefresh库的例子。
分别写了10个实例供用户开发参考:ListView、GridView、ScrollView、ExpandListView、ListFragment、ViewPager、HorizontalScrollView、ListView in ViewPager、WebView、WebView2。
2. 核心库library:
功能实现总览:
整个项目采用策略模式,把公共的部分实现为抽象类,并提供抽象接口(策略)让具体类来实现:
(1)PullToRefreshBase抽象类,实现的功能:scrollview、下拉/上拉逻辑,并把需要PullToRefresh的View(如:ListView、ScrollView等)提供接口给独立出来,让实现者提供,并把在哪里需要上拉和下拉也提供出接口、提供支持上下滚动或左右滚动的接口。
(2)LoadingLayout抽象类,实现的功能:基本Loading界面框架,但是把界面的元素(子View)独立出来,提供成接口,让实现者提供。
overscroll(弹簧阻尼效果)是通过ViewGroup的onInterceptTouchEvent()和onTouchEvent()函数通过过滤手势、捕捉手势,并且使用View的scrollTo()函数来实现的,当手势向下滑动或向上滑动的时候,使用scrollTo()函数做overscroll运动,当松开手势的时候,利用一个独立线程来scrollTo()滚动动画(采用View的post(Runnable)函数实现)到header,当refresh完成后,scrollTo()到0(不显示header/footer);上拉/下拉刷新的footer/header是通过ViewGroup的addView根据是否是下拉/上拉或双向来加入的,header或footer是在PullToRefreshBase初始化的时候一开始就被addView()到要PollToRefresh的View(ListView或GradView)之前和之后的,也就是说header/footer刚开始时是塞在了ListView或GridView之前的顶部栏/之后的底部栏后隐藏的,所以下拉/上拉刷新的时候直接把隐藏的header/footer从顶部栏/底部栏之后拉出来了。
这3个子目录分别是com.handmark.pulltorefresh.library是客户端开发人员直接使用的类,./extras也是客户端开发人员直接使用的类,是对library的补充,./interal是library和./extras内部使用的自定义View类。其中,在library包中,PullToRefreshBase是基类(抽象类),PullToRefreshListView等是客户端类,ILoadingLayout接口是针对Loading界面的回调接口,策略模式用于设置Loading界面的图像和文字,是被LoadingLayout继承实现的;IPullToRefresh是刷新动作回调接口,只在PullToRefreshBase中实现,IPullToRefresh类写成接口的目的应该是为了更加通用,目的是即使再重写一个PullToRefreshBase类都是可以的,并且PullToRefreshBase抽象类中提供了几个抽象方法:
public abstract Orientation getPullToRefreshScrollDirection(); // 子类实现scroll方向
protected abstract T createRefreshableView(Context context, AttributeSet attrs); // 子类实现可刷新View,例如ListView、GridView等
protected abstract boolean isReadyForPullEnd(); // 子类实现上拉刷新的条件,例如ListView实现上拉刷新的条件是上拉到底部
protected abstract boolean isReadyForPullStart(); // 子类实现下拉刷新的条件,例如ListView实现下拉刷新的条件是下拉到最上面第一行处
(其实,对于继承AbsListView的ListView和GridView来说,该项目又封装了一层PullToRefreshAdapterViewBase)。
LoadingLayout是Loading界面的基类,也是抽象类,该抽象类根据x拉刷新方向,加载生成了一个基本Loading界面,可以让FlipLoadingLayout和RotateLoadingLayout继承,这2个类主要是指定了Loading界面中的图片动画,并实现以下抽象方法,指定各个状态时的图片:
protected abstract int getDefaultDrawableResId(); // If we don't have a user defined drawable, load the default
protected abstract void onLoadingDrawableSet(Drawable imageDrawable); // 设置Loading图片时候的回调
protected abstract void onPullImpl(float scaleOfLayout); // 在手势下/上拉时候回调
protected abstract void pullToRefreshImpl(); // 下/上拉手势正在执行时的:刷新后的回调
protected abstract void refreshingImpl(); // 正在下/上拉刷新中...的回调
protected abstract void releaseToRefreshImpl(); // 手势释放后的回调
protected abstract void resetImpl(); // Loading View重置后的回调
3. PullToRefreshListFragment目录是pulltoRefresh在ListFragment中使用的例子,供用户参考使用。
4.&PullToRefreshViewPager目录是pulltoRefresh在ViewPager中使用的例子,供用户参考使用。
核心类PullToRefreshBase,代码分析如下:
/*******************************************************************************
* Copyright
Chris Banes.
* Licensed under the Apache License, Version 2.0 (the &License&);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an &AS IS& BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package com.handmark.pulltorefresh.
import android.content.C
import android.content.res.TypedA
import android.graphics.drawable.D
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.B
import android.os.P
import android.util.AttributeS
import android.util.L
import android.view.G
import android.view.MotionE
import android.view.V
import android.view.ViewC
import android.view.ViewG
import android.view.animation.DecelerateI
import android.view.animation.I
import android.widget.FrameL
import android.widget.LinearL
import com.handmark.pulltorefresh.library.internal.FlipLoadingL
import com.handmark.pulltorefresh.library.internal.LoadingL
import com.handmark.pulltorefresh.library.internal.RotateLoadingL
import com.handmark.pulltorefresh.library.internal.U
import com.handmark.pulltorefresh.library.internal.ViewC
* 这个类是基类,实现了下拉/上拉刷新,并且提供抽象方法给继承类实现:提供Refreshable View、
* 提供上拉/下拉刷新的时机、支持纵向或横向刷新。
* (1)此抽象类初始化的界面是:加入refreshable view(如:ListView、ScrollView等),加入loading header
* 或footer到界面中,然后把header或footer高度或宽度设置为整个界面高度或宽度的一半,之后把整个header或
* footer塞到顶部栏或底部栏之后,使整个界面只看到了refreshable view,当下拉或上拉的时候才能看到header
* 或footer,这也是overscroll(弹簧阻尼效果)实现方案。
* (2)重写onInterceptTouchEvent()和onTouchEvent实现下拉/上拉刷新,当移动手势的时,使用scrollTo()
* 函数滚动界面,当放开手势的时候,开启一个独立线程执行scrollTo()函数来滚动界面回滚,然后等刷新完成的时候,
* 界面滚动到初始状态。【我们自定义的View及其子类的滚动动画基本上都是使用scrollTo()配合Animation及其子类来完成的】。
* 这个类可用于一切需要overscroll效果的view。
* @param &T& Refreshable View,比如:ListView ScrollView等
public abstract class PullToRefreshBase&T extends View& extends LinearLayout
implements IPullToRefresh&T& {
// ===========================================================
// Constants
// ===========================================================
static final String LOG_TAG = &PullToRefresh&;
static final boolean DEBUG =
static final boolean USE_HW_LAYERS =
static final float FRICTION = 2.0f;
public static final int SMOOTH_SCROLL_DURATION_MS = 200;
public static final int SMOOTH_SCROLL_LONG_DURATION_MS = 325;
static final int DEMO_SCROLL_INTERVAL = 225;
static final String STATE_STATE = &ptr_state&;
static final String STATE_MODE = &ptr_mode&;
static final String STATE_CURRENT_MODE = &ptr_current_mode&;
static final String STATE_SCROLLING_REFRESHING_ENABLED = &ptr_disable_scrolling&;
static final String STATE_SHOW_REFRESHING_VIEW = &ptr_show_refreshing_view&;
static final String STATE_SUPER = &ptr_super&;
// ===========================================================
// ===========================================================
private int mTouchS // TouchSlop
private float mLastMotionX, mLastMotionY; // 上一次手势坐标
private float mInitialMotionX, mInitialMotionY; // 初始手势坐标
private boolean mIsBeingDragged = // 标记正在手势拖动下拉刷新中...
private State mState = State.RESET; // 默认是重启状态
private Mode mMode = Mode.getDefault(); // 默认是下拉刷新
private Mode mCurrentM // 当前模式
T mRefreshableV // 当前被刷新的View: ListView
private FrameLayout mRefreshableViewW // 可刷新View(例如ListView)的包装器
private boolean mShowViewWhileRefreshing = // 当刷新的时候是否允许展示View
private boolean mScrollingWhileRefreshingEnabled = // 当刷新的时候是否允许滚动
private boolean mFilterTouchEvents = // 是否过滤触摸事件
private boolean mOverScrollEnabled = // 是否允许过滚动(弹簧效果)
private boolean mLayoutVisibilityChangesEnabled =
private Interpolator mScrollAnimationI
private AnimationStyle mLoadingAnimationStyle = AnimationStyle.getDefault();
private LoadingLayout mHeaderL // Loading头部界面
private LoadingLayout mFooterL // Loading尾部界面
private OnRefreshListener&T& mOnRefreshL
private OnRefreshListener2&T& mOnRefreshListener2;
private OnPullEventListener&T& mOnPullEventL
private SmoothScrollRunnable mCurrentSmoothScrollR // x拉刷新滚动子线程
// ===========================================================
// Constructors
// ===========================================================
public PullToRefreshBase(Context context) {
super(context);
init(context, null);
public PullToRefreshBase(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
public PullToRefreshBase(Context context, Mode mode) {
super(context);
init(context, null);
public PullToRefreshBase(Context context, Mode mode, AnimationStyle animStyle) {
super(context);
mLoadingAnimationStyle = animS
init(context, null);
* 这个重写函数非常重要:当该类用于ListView、ScrollView的时候,这些控件组一定会包含子控件,
* 因此一定需要重写这个方法,让其孩子加入到真正的控件中。
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (DEBUG) {
Log.d(LOG_TAG, &addView: & + child.getClass().getSimpleName());
final T refreshableView = getRefreshableView();
if (refreshableView instanceof ViewGroup) {
((ViewGroup) refreshableView).addView(child, index, params);
throw new UnsupportedOperationException(&Refreshable View is not a & +
&ViewGroup so can't addView&);
public final boolean demo() {
if (mMode.showHeaderLoadingLayout() && isReadyForPullStart()) {
smoothScrollToAndBack(-getHeaderSize() * 2);
} else if (mMode.showFooterLoadingLayout() && isReadyForPullEnd()) {
smoothScrollToAndBack(getFooterSize() * 2);
* 获得当前Pull模式
public final Mode getCurrentMode() {
return mCurrentM
public final boolean getFilterTouchEvents() {
return mFilterTouchE
* 获得下拉/上拉Loading布局代理,主要是关于下拉/上拉Loading header or footer
* 相关的文字/View等
public final ILoadingLayout getLoadingLayoutProxy() {
return getLoadingLayoutProxy(true, true);
public final ILoadingLayout getLoadingLayoutProxy(boolean includeStart, boolean includeEnd) {
return createLoadingLayoutProxy(includeStart, includeEnd);
public final Mode getMode() {
* 获得可刷新的View,比如ListView、GridView等
public final T getRefreshableView() {
return mRefreshableV
public final boolean getShowViewWhileRefreshing() {
return mShowViewWhileR
public final State getState() {
* @deprecated See {@link #isScrollingWhileRefreshingEnabled()}.
public final boolean isDisableScrollingWhileRefreshing() {
return !isScrollingWhileRefreshingEnabled();
* 判断是否Pull刷新使能
public final boolean isPullToRefreshEnabled() {
return mMode.permitsPullToRefresh();
* 是否允许过滚动效果(弹簧效果)
public final boolean isPullToRefreshOverScrollEnabled() {
return VERSION.SDK_INT &= VERSION_CODES.GINGERBREAD && mOverScrollEnabled
&& OverscrollHelper.isAndroidOverScrollEnabled(mRefreshableView);
* 返回是否正在刷新
public final boolean isRefreshing() {
return mState == State.REFRESHING || mState == State.MANUAL_REFRESHING;
public final boolean isScrollingWhileRefreshingEnabled() {
return mScrollingWhileRefreshingE
* 重写这个函数的目的就是,拦截上拉/下拉事件给此PullToRefresh的onTouchEvent()函数处理,
* 其余事件仍旧传递给子View处理,不改变子View原本的手势处理方式。
* 重写该方法处理手势事件,实现下拉刷新。在这里过滤下手势事件,该PullToRefresh本身
* 只处理满足条件的下拉/上拉刷新事件,其余事件仍旧需要传递给子View(如ListView等)处理。
* 当手势时拖动中,并且满足下拉刷新或上拉刷新条件的时候,返回true拦截向子View传递
* 手势事件,让PullToRefresh自己在onTouchEvent()中处理上拉或下拉刷新事件。
public final boolean onInterceptTouchEvent(MotionEvent event) {
if (!isPullToRefreshEnabled()) { // 如果不支持上拉/下拉刷新功能
// 不处理手势事件,向下传递
final int action = event.getAction();
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mIsBeingDragged =
// 如果手势是正在拖动下拉刷新手势,则处理手势(让onTouchEvent()处理)
if (action != MotionEvent.ACTION_DOWN && mIsBeingDragged) {
switch (action) {
case MotionEvent.ACTION_MOVE:
* If we're refreshing, and the flag is set. Eat all MOVE events
* 如果正在刷新中...并且不支持刷新过程中滚动事件,则处理
if (!mScrollingWhileRefreshingEnabled && isRefreshing()) {
if (isReadyForPull()) { // 准备好可以下拉刷新或上拉刷新
final float y = event.getY(), x = event.getX();
final float diff, oppositeDiff, absD
* We need to use the correct values, based on scroll direction
switch (getPullToRefreshScrollDirection()) {
case HORIZONTAL:
diff = x - mLastMotionX;
oppositeDiff = y - mLastMotionY;
case VERTICAL:
diff = y - mLastMotionY;
oppositeDiff = x - mLastMotionX;
absDiff = Math.abs(diff);
if (absDiff & mTouchSlop && (!mFilterTouchEvents || absDiff & Math.abs(oppositeDiff))) {
// 允许下拉刷新,且条件准备好可以下拉刷新了
if (mMode.showHeaderLoadingLayout() && diff &= 1f && isReadyForPullStart()) {
mLastMotionY =
mLastMotionX =
mIsBeingDragged =
if (mMode == Mode.BOTH) {
mCurrentMode = Mode.PULL_FROM_START;
} else if (mMode.showFooterLoadingLayout() && diff &= -1f && isReadyForPullEnd()) {
mLastMotionY =
mLastMotionX =
mIsBeingDragged =
if (mMode == Mode.BOTH) {
mCurrentMode = Mode.PULL_FROM_END;
case MotionEvent.ACTION_DOWN: // 按下手势不处理,让子View处理
if (isReadyForPull()) { // 准备好可以上拉刷新或下拉刷新
mLastMotionY = mInitialMotionY = event.getY();
mLastMotionX = mInitialMotionX = event.getX();
mIsBeingDragged =
return mIsBeingD
* 仅处理上拉/下拉刷新手势事件,其余手势事件在onInterceptTouchEvent()函数中
* 已经传递给子View(如ListView等)处理了。这里剩下的就是onInterceptTouchEvent()
* 函数过滤过来的上拉/下拉刷新事件。
public final boolean onTouchEvent(MotionEvent event) {
if (!isPullToRefreshEnabled()) {
* If we're refreshing, and the flag is set. Eat the event
* 如果正在刷新中...并且是刷新过程中不允许滚动事件,则进行空处理。
if (!mScrollingWhileRefreshingEnabled && isRefreshing()) {
if (event.getAction() == MotionEvent.ACTION_DOWN && event.getEdgeFlags() != 0) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
if (mIsBeingDragged) {
mLastMotionY = event.getY();
mLastMotionX = event.getX();
pullEvent(); // 执行Loading header or footer的滚动事件
case MotionEvent.ACTION_DOWN:
if (isReadyForPull()) {
mLastMotionY = mInitialMotionY = event.getY();
mLastMotionX = mInitialMotionX = event.getX();
* 因为在onInterceptTouchEvent()函数中手势移动事件被处理了,所以之后的抬起事件仍旧在
* 此onTouchEvent()事件中处理。
* 开启独立线程执行滚动回滚。
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (mIsBeingDragged) {
mIsBeingDragged =
if (mState == State.RELEASE_TO_REFRESH
&& (null != mOnRefreshListener || null != mOnRefreshListener2)) {
setState(State.REFRESHING, true);
// If we're already refreshing, just scroll back to the top
if (isRefreshing()) {
smoothScrollTo(0);
// If we haven't returned by here, then we're not in a state
// to pull, so just reset
setState(State.RESET);
* 每次刷新完数据后都调用这个方法重置PullToRefresh
public final void onRefreshComplete() {
if (isRefreshing()) {
setState(State.RESET);
public final void setScrollingWhileRefreshingEnabled(boolean allowScrollingWhileRefreshing) {
mScrollingWhileRefreshingEnabled = allowScrollingWhileR
* @deprecated See {@link #setScrollingWhileRefreshingEnabled(boolean)}
public void setDisableScrollingWhileRefreshing(boolean disableScrollingWhileRefreshing) {
setScrollingWhileRefreshingEnabled(!disableScrollingWhileRefreshing);
public final void setFilterTouchEvents(boolean filterEvents) {
mFilterTouchEvents = filterE
* @deprecated You should now call this method on the result of
{@link #getLoadingLayoutProxy()}.
public void setLastUpdatedLabel(CharSequence label) {
getLoadingLayoutProxy().setLastUpdatedLabel(label);
* @deprecated You should now call this method on the result of
{@link #getLoadingLayoutProxy()}.
public void setLoadingDrawable(Drawable drawable) {
getLoadingLayoutProxy().setLoadingDrawable(drawable);
* @deprecated You should now call this method on the result of
{@link #getLoadingLayoutProxy(boolean, boolean)}.
public void setLoadingDrawable(Drawable drawable, Mode mode) {
getLoadingLayoutProxy(mode.showHeaderLoadingLayout(), mode.showFooterLoadingLayout())
.setLoadingDrawable(drawable);
public void setLongClickable(boolean longClickable) {
getRefreshableView().setLongClickable(longClickable);
public final void setMode(Mode mode) {
if (mode != mMode) {
if (DEBUG) {
Log.d(LOG_TAG, &Setting mode to: & + mode);
updateUIForMode(); // 更新PullToRefresh界面
public void setOnPullEventListener(OnPullEventListener&T& listener) {
mOnPullEventListener =
public final void setOnRefreshListener(OnRefreshListener&T& listener) {
mOnRefreshListener =
mOnRefreshListener2 =
public final void setOnRefreshListener(OnRefreshListener2&T& listener) {
mOnRefreshListener2 =
mOnRefreshListener =
* @deprecated You should now call this method on the result of
{@link #getLoadingLayoutProxy()}.
public void setPullLabel(CharSequence pullLabel) {
getLoadingLayoutProxy().setPullLabel(pullLabel);
* @deprecated You should now call this method on the result of
{@link #getLoadingLayoutProxy(boolean, boolean)}.
public void setPullLabel(CharSequence pullLabel, Mode mode) {
getLoadingLayoutProxy(mode.showHeaderLoadingLayout(), mode.showFooterLoadingLayout())
.setPullLabel(pullLabel);
* @param enable Whether Pull-To-Refresh should be used
* @deprecated This simple calls setMode with an appropriate mode based on
the passed value.
public final void setPullToRefreshEnabled(boolean enable) {
setMode(enable ? Mode.getDefault() : Mode.DISABLED);
public final void setPullToRefreshOverScrollEnabled(boolean enabled) {
mOverScrollEnabled =
* 设置正在刷新
public final void setRefreshing() {
setRefreshing(true);
* 设置正在刷新
public final void setRefreshing(boolean doScroll) {
if (!isRefreshing()) {
setState(State.MANUAL_REFRESHING, doScroll);
* @deprecated You should now call this method on the result of
{@link #getLoadingLayoutProxy()}.
public void setRefreshingLabel(CharSequence refreshingLabel) {
getLoadingLayoutProxy().setRefreshingLabel(refreshingLabel);
* @deprecated You should now call this method on the result of
{@link #getLoadingLayoutProxy(boolean, boolean)}.
public void setRefreshingLabel(CharSequence refreshingLabel, Mode mode) {
getLoadingLayoutProxy(mode.showHeaderLoadingLayout(), mode.showFooterLoadingLayout())
.setRefreshingLabel(refreshingLabel);
* @deprecated You should now call this method on the result of
{@link #getLoadingLayoutProxy()}.
public void setReleaseLabel(CharSequence releaseLabel) {
setReleaseLabel(releaseLabel, Mode.BOTH);
* @deprecated You should now call this method on the result of
{@link #getLoadingLayoutProxy(boolean, boolean)}.
public void setReleaseLabel(CharSequence releaseLabel, Mode mode) {
getLoadingLayoutProxy(mode.showHeaderLoadingLayout(), mode.showFooterLoadingLayout())
.setReleaseLabel(releaseLabel);
public void setScrollAnimationInterpolator(Interpolator interpolator) {
mScrollAnimationInterpolator =
public final void setShowViewWhileRefreshing(boolean showView) {
mShowViewWhileRefreshing = showV
* @return Either {@link Orientation#VERTICAL} or
{@link Orientation#HORIZONTAL} depending on the scroll direction.
public abstract Orientation getPullToRefreshScrollDirection(); // TODO
final void setState(State state, final boolean... params) {
if (DEBUG) {
Log.d(LOG_TAG, &State: & + mState.name());
switch (mState) {
case RESET:
onReset();
case PULL_TO_REFRESH: // 手势拖动更新
onPullToRefresh();
case RELEASE_TO_REFRESH: // 释放手势拖动更新
onReleaseToRefresh();
case REFRESHING: // 正在刷新中...
case MANUAL_REFRESHING:
onRefreshing(params[0]);
case OVERSCROLLING: // NO-OP
// Call OnPullEventListener
if (null != mOnPullEventListener) {
mOnPullEventListener.onPullEvent(this, mState, mCurrentMode);
* Used internally for adding view. Need because we override addView to
* pass-through to the Refreshable View
* 用于把Loading header或loading footer加入到PullToRefresh中
protected final void addViewInternal(View child, int index, ViewGroup.LayoutParams params) {
super.addView(child, index, params);
* Used internally for adding view. Need because we override addView to
* pass-through to the Refreshable View
* 用于把Refreshable加入到PullToRefresh中,加入到孩子之后
protected final void addViewInternal(View child, ViewGroup.LayoutParams params) {
super.addView(child, -1, params);
* 创建loading header and loading footer
* @param context
* @param mode 根据mode创建flip样式或rotate样式的loading界面
* @param attrs
protected LoadingLayout createLoadingLayout(Context context, Mode mode, TypedArray attrs) {
LoadingLayout layout = mLoadingAnimationStyle.createLoadingLayout(context, mode,
getPullToRefreshScrollDirection(), attrs);
layout.setVisibility(View.INVISIBLE);
* Used internally for {@link #getLoadingLayoutProxy(boolean, boolean)}.
* Allows derivative classes to include any extra LoadingLayouts.
* 创建Loading界面代理
* 获得真正的Loading界面(继承LoadingLayout)
protected LoadingLayoutProxy createLoadingLayoutProxy(final boolean includeStart,
final boolean includeEnd) {
LoadingLayoutProxy proxy = new LoadingLayoutProxy();
if (includeStart && mMode.showHeaderLoadingLayout()) {
proxy.addLayout(mHeaderLayout); // 把loading header加入Loading界面代理
if (includeEnd && mMode.showFooterLoadingLayout()) {
proxy.addLayout(mFooterLayout); // 把loading footer加入Loading界面代理
* This is implemented by derived classes to return the created View. If you
* need to use a custom View (such as a custom ListView), override this
* method and return an instance of your custom class.
* Be sure to set the ID of the view in this method, especially if you're
* using a ListActivity or ListFragment.
* @param context Context to create view with
* @param attrs AttributeSet from wrapped class. Means that anything you
include in the XML layout declaration will be routed to the
created View
* @return New instance of the Refreshable View
protected abstract T createRefreshableView(Context context, AttributeSet attrs); // TODO
protected final void disableLoadingLayoutVisibilityChanges() {
mLayoutVisibilityChangesEnabled =
protected final LoadingLayout getFooterLayout() {
return mFooterL
protected final int getFooterSize() {
return mFooterLayout.getContentSize();
protected final LoadingLayout getHeaderLayout() {
return mHeaderL
protected final int getHeaderSize() {
return mHeaderLayout.getContentSize();
protected int getPullToRefreshScrollDuration() {
return SMOOTH_SCROLL_DURATION_MS;
protected int getPullToRefreshScrollDurationLonger() {
return SMOOTH_SCROLL_LONG_DURATION_MS;
protected FrameLayout getRefreshableViewWrapper() {
return mRefreshableViewW
* Allows Derivative classes to handle the XML Attrs without creating a
* TypedArray themsevles
* @param a - TypedArray of PullToRefresh Attributes
protected void handleStyledAttributes(TypedArray a) {
* Implemented by derived class to return whether the View is in a state
* where the user can Pull to Refresh by scrolling from the end.
* @return true if the View is currently in the correct state (for example,
bottom of a ListView)
protected abstract boolean isReadyForPullEnd();
* Implemented by derived class to return whether the View is in a state
* where the user can Pull to Refresh by scrolling from the start.
* @return true if the View is currently the correct state (for example, top
of a ListView)
protected abstract boolean isReadyForPullStart();
* Called by {@link #onRestoreInstanceState(Parcelable)} so that derivative
* classes can handle their saved instance state.
* @param savedInstanceState - Bundle which contains saved instance state.
protected void onPtrRestoreInstanceState(Bundle savedInstanceState) {
* Called by {@link #onSaveInstanceState()} so that derivative classes can
* save their instance state.
* @param saveState - Bundle to be updated with saved state.
protected void onPtrSaveInstanceState(Bundle saveState) {
* Called when the UI has been to be updated to be in the
* {@link State#PULL_TO_REFRESH} state.
protected void onPullToRefresh() {
switch (mCurrentMode) {
case PULL_FROM_END:
mFooterLayout.pullToRefresh();
case PULL_FROM_START:
mHeaderLayout.pullToRefresh();
* Called when the UI has been to be updated to be in the
* {@link State#REFRESHING} or {@link State#MANUAL_REFRESHING} state.
* 正在刷新的回调函数
* @param doScroll - Whether the UI should scroll for this event.
protected void onRefreshing(final boolean doScroll) {
if (mMode.showHeaderLoadingLayout()) { // 允许下拉刷新
mHeaderLayout.refreshing();
if (mMode.showFooterLoadingLayout()) { // 允许上拉刷新
mFooterLayout.refreshing();
if (doScroll) { // 上/下拉刷新的时候允许UI滚动事件
if (mShowViewWhileRefreshing) {
// Call Refresh Listener when the Scroll has finished
OnSmoothScrollFinishedListener listener = new OnSmoothScrollFinishedListener() {
public void onSmoothScrollFinished() {
callRefreshListener();
switch (mCurrentMode) {
case MANUAL_REFRESH_ONLY:
case PULL_FROM_END: // 子线程中的滚动动画
smoothScrollTo(getFooterSize(), listener);
case PULL_FROM_START:
smoothScrollTo(-getHeaderSize(), listener);
smoothScrollTo(0);
// We're not scrolling, so just call Refresh Listener now
callRefreshListener();
* Called when the UI has been to be updated to be in the
* {@link State#RELEASE_TO_REFRESH} state.
protected void onReleaseToRefresh() {
switch (mCurrentMode) {
case PULL_FROM_END:
mFooterLayout.releaseToRefresh();
case PULL_FROM_START:
mHeaderLayout.releaseToRefresh();
* Called when the UI has been to be updated to be in the
* {@link State#RESET} state.
protected void onReset() {
mIsBeingDragged =
mLayoutVisibilityChangesEnabled =
// Always reset both layouts, just in case...
mHeaderLayout.reset();
mFooterLayout.reset();
smoothScrollTo(0);
protected final void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle)
setMode(Mode.mapIntToValue(bundle.getInt(STATE_MODE, 0)));
mCurrentMode = Mode.mapIntToValue(bundle.getInt(STATE_CURRENT_MODE, 0));
mScrollingWhileRefreshingEnabled = bundle.getBoolean(STATE_SCROLLING_REFRESHING_ENABLED, false);
mShowViewWhileRefreshing = bundle.getBoolean(STATE_SHOW_REFRESHING_VIEW, true);
// Let super Restore Itself
super.onRestoreInstanceState(bundle.getParcelable(STATE_SUPER));
State viewState = State.mapIntToValue(bundle.getInt(STATE_STATE, 0));
if (viewState == State.REFRESHING || viewState == State.MANUAL_REFRESHING) {
setState(viewState, true);
// Now let derivative classes restore their state
onPtrRestoreInstanceState(bundle);
super.onRestoreInstanceState(state);
protected final Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
// Let derivative classes get a chance to save state first, that way we
// can make sure they don't overrite any of our values
onPtrSaveInstanceState(bundle);
bundle.putInt(STATE_STATE, mState.getIntValue());
bundle.putInt(STATE_MODE, mMode.getIntValue());
bundle.putInt(STATE_CURRENT_MODE, mCurrentMode.getIntValue());
bundle.putBoolean(STATE_SCROLLING_REFRESHING_ENABLED, mScrollingWhileRefreshingEnabled);
bundle.putBoolean(STATE_SHOW_REFRESHING_VIEW, mShowViewWhileRefreshing);
bundle.putParcelable(STATE_SUPER, super.onSaveInstanceState());
protected final void onSizeChanged(int w, int h, int oldw, int oldh) {
if (DEBUG) {
Log.d(LOG_TAG, String.format(&onSizeChanged. W: %d, H: %d&, w, h));
super.onSizeChanged(w, h, oldw, oldh);
// We need to update the header/footer when our size changes
refreshLoadingViewsSize();
// Update the Refreshable View layout
refreshRefreshableViewSize(w, h);
* As we're currently in a Layout Pass, we need to schedule another one
* to layout any changes we've made here
post(new Runnable() {
public void run() {
requestLayout();
* Re-measure the Loading Views height, and adjust internal padding as
* necessary
* 设置loading header and footer视图大小、隐藏它们到顶部栏or底部栏后面
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()) { // TODO
case HORIZONTAL:
if (mMode.showHeaderLoadingLayout()) {
mHeaderLayout.setWidth(maximumPullScroll);
pLeft = -maximumPullS
pLeft = 0;
if (mMode.showFooterLoadingLayout()) {
mFooterLayout.setWidth(maximumPullScroll);
pRight = -maximumPullS
pRight = 0;
case VERTICAL:
if (mMode.showHeaderLoadingLayout()) {
mHeaderLayout.setHeight(maximumPullScroll); // 设置loading header高度
pTop = -maximumPullS // 把loading header塞到顶部栏之后的距离参数
if (mMode.showFooterLoadingLayout()) {
mFooterLayout.setHeight(maximumPullScroll); // 设置loading footer高度
pBottom = -maximumPullS
pBottom = 0;
if (DEBUG) {
Log.d(LOG_TAG, String.format(&Setting Padding. L: %d, T: %d, R: %d, B: %d&,
pLeft, pTop, pRight, pBottom));
setPadding(pLeft, pTop, pRight, pBottom); // 把loading header or footer塞到顶部栏or底部栏之后
protected final void refreshRefreshableViewSize(int width, int height) {
// We need to set the Height of the Refreshable View to the same as
// this layout
LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) mRefreshableViewWrapper.getLayoutParams();
switch (getPullToRefreshScrollDirection()) {
case HORIZONTAL:
if (lp.width != width) {
lp.width =
mRefreshableViewWrapper.requestLayout();
case VERTICAL:
if (lp.height != height) {
lp.height =
mRefreshableViewWrapper.requestLayout();
* Helper method which just calls scrollTo() in the correct scrolling
* direction.
* 设置界面滚动到合适的位置,当手势拖动时直接调用这个函数;当释放拖动手势时把
* 该函数放在一个独立线程中执行。
* @param value - New Scroll value
protected final void setHeaderScroll(int value) {
if (DEBUG) {
Log.d(LOG_TAG, &setHeaderScroll: & + value);
// Clamp value to with pull scroll range
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);
mHeaderLayout.setVisibility(View.INVISIBLE);
mFooterLayout.setVisibility(View.INVISIBLE);
if (USE_HW_LAYERS) {
* Use a Hardware Layer on the Refreshable View if we've scrolled at
* all. We don't use them on the Header/Footer Views as they change
* often, which would negate any HW layer performance boost.
ViewCompat.setLayerType(mRefreshableViewWrapper, value != 0 ?
View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE);
switch (getPullToRefreshScrollDirection()) {
case VERTICAL:
scrollTo(0, value);
case HORIZONTAL:
scrollTo(value, 0);
* Smooth Scroll to position using the default duration of
* {@value #SMOOTH_SCROLL_DURATION_MS} ms.
* @param scrollValue - Position to scroll to
protected final void smoothScrollTo(int scrollValue) {
smoothScrollTo(scrollValue, getPullToRefreshScrollDuration());
* Smooth Scroll to position using the default duration of
* {@value #SMOOTH_SCROLL_DURATION_MS} ms.
* @param scrollValue - Position to scroll to
* @param listener - Listener for scroll
protected final void smoothScrollTo(int scrollValue, OnSmoothScrollFinishedListener listener) {
smoothScrollTo(scrollValue, getPullToRefreshScrollDuration(), 0, listener);
* Smooth Scroll to position using the longer default duration of
* {@value #SMOOTH_SCROLL_LONG_DURATION_MS} ms.
* @param scrollValue - Position to scroll to
protected final void smoothScrollToLonger(int scrollValue) {
smoothScrollTo(scrollValue, getPullToRefreshScrollDurationLonger());
* Updates the View State when the mode has been set. This does not do any
* checking that the mode is different to current state so always updates.
* 更新PullToRefresh界面
protected void updateUIForMode() {
// We need to use the correct LayoutParam values, based on scroll
// direction
final LinearLayout.LayoutParams lp = getLoadingLayoutLayoutParams();
// Remove Header, and then add Header Loading View again if needed
if (this == mHeaderLayout.getParent()) {
removeView(mHeaderLayout);
if (mMode.showHeaderLoadingLayout()) {
addViewInternal(mHeaderLayout, 0, lp); // 把loading header加入到PullToRefresh
// Remove Footer, and then add Footer Loading View again if needed
if (this == mFooterLayout.getParent()) {
removeView(mFooterLayout);
if (mMode.showFooterLoadingLayout()) {
addViewInternal(mFooterLayout, lp); // 把loading footer加入到PullToRefresh
// Hide Loading Views 把loading header or footer隐藏到顶部栏or底部栏之后
refreshLoadingViewsSize();
// If we're not using Mode.BOTH, set mCurrentMode to mMode, otherwise
// set it to pull down
mCurrentMode = (mMode != Mode.BOTH) ? mMode : Mode.PULL_FROM_START;
* 把Refresh View(ListView/GridView等)加入到PullToRefresh中
* @param context
* @param refreshableView
private void addRefreshableView(Context context, T refreshableView) {
mRefreshableViewWrapper = new FrameLayout(context);
mRefreshableViewWrapper.addView(refreshableView, ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
addViewInternal(mRefreshableViewWrapper,
new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
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);
* 初始化PullToRefresh界面基本框架
@SuppressWarnings(&deprecation&)
private void init(Context context, AttributeSet attrs) {
switch (getPullToRefreshScrollDirection()) {
case HORIZONTAL:
setOrientation(LinearLayout.HORIZONTAL);
case VERTICAL:
setOrientation(LinearLayout.VERTICAL);
setGravity(Gravity.CENTER);
// 拖动手势的最小识别距离
ViewConfiguration config = ViewConfiguration.get(context);
mTouchSlop = config.getScaledTouchSlop();
// Styleables from XML
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));
* Refreshable View,把Refreshable View加入到PullToRefresh界面(LinearLayout)中,
* By passing the attrs, we can add ListView/GridView params via XML
mRefreshableView = createRefreshableView(context, attrs); // TODO
addRefreshableView(context, mRefreshableView);
* We need to create now layouts now,这两个在刚开始创建PullToRefresh的时,就已经
* 根据Mode方向将其加入到了PullToRefresh中了,只是被隐藏在了顶部栏或底部栏之后了。
* 因此上拉或下拉的时,只是把loading header或loading footer从其中显示出来而已。
mHeaderLayout = createLoadingLayout(context, Mode.PULL_FROM_START, a);
mFooterLayout = createLoadingLayout(context, Mode.PULL_FROM_END, a);
* Styleables from XML
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);
// Let the derivative classes have a go at handling attributes, then
// recycle them...
handleStyledAttributes(a);
a.recycle();
// Finally update the UI for the modes,上面是获得相关属性和子界面,这里是更新PullToRefresh界面
updateUIForMode();
* 控件是否可以下拉刷新或上拉刷新
private boolean isReadyForPull() {
switch (mMode) {
case PULL_FROM_START:
return isReadyForPullStart(); // TODO: 让子类来实现下拉刷新的时机
case PULL_FROM_END:
return isReadyForPullEnd(); // TODO: 让子类来实现上拉刷新的时机
case BOTH:
return isReadyForPullEnd() || isReadyForPullStart();
* Actions a Pull Event 手势下/上拉事件
* @return true if the Event has been handled, false if there has been no
* 执行滚动事件
private void pullEvent() {
final int newScrollV
final int itemD
final float initialMotionValue, lastMotionV
switch (getPullToRefreshScrollDirection()) {
case HORIZONTAL:
initialMotionValue = mInitialMotionX;
lastMotionValue = mLastMotionX;
case VERTICAL:
initialMotionValue = mInitialMotionY;
lastMotionValue = mLastMotionY;
switch (mCurrentMode) {
case PULL_FROM_END:
newScrollValue = Math.round(Math.max(initialMotionValue - lastMotionValue, 0) / FRICTION);
itemDimension = getFooterSize();
case PULL_FROM_START:
newScrollValue = Math.round(Math.min(initialMotionValue - lastMotionValue, 0) / FRICTION);
itemDimension = getHeaderSize();
setHeaderScroll(newScrollValue); // 设置laoding header滚动到指定位置
if (newScrollValue != 0 && !isRefreshing()) {
float scale = Math.abs(newScrollValue) / (float) itemD
switch (mCurrentMode) {
case PULL_FROM_END:
mFooterLayout.onPull(scale);
case PULL_FROM_START:
mHeaderLayout.onPull(scale);
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);
* 获得loading header or footer布局参数
private LinearLayout.LayoutParams getLoadingLayoutLayoutParams() {
switch (getPullToRefreshScrollDirection()) { // TODO
case HORIZONTAL:
return new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.MATCH_PARENT);
case VERTICAL:
return new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
* 获取上拉和下拉最大滚动距离
private int getMaximumPullScroll() {
switch (getPullToRefreshScrollDirection()) { //TODO
case HORIZONTAL:
return Math.round(getWidth() / FRICTION); // 最大滚动距离是宽度的一半
case VERTICAL:
return Math.round(getHeight() / FRICTION); // 最大滚动距离是高度的一半
* Smooth Scroll to position using the specific duration
* @param scrollValue - Position to scroll to
* @param duration - Duration of animation in milliseconds
private final void smoothScrollTo(int scrollValue, long duration) {
smoothScrollTo(scrollValue, duration, 0, null);
* 平滑滚动,在独立线程中执行
private final void smoothScrollTo(int newScrollValue, long duration, long delayMillis,
OnSmoothScrollFinishedListener listener) {
if (null != mCurrentSmoothScrollRunnable) {
mCurrentSmoothScrollRunnable.stop();
final int oldScrollV
switch (getPullToRefreshScrollDirection()) { // TODO
case HORIZONTAL:
oldScrollValue = getScrollX();
case VERTICAL:
oldScrollValue = getScrollY();
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);
post(mCurrentSmoothScrollRunnable);
private final void smoothScrollToAndBack(int y) {
smoothScrollTo(y, SMOOTH_SCROLL_DURATION_MS, 0, new OnSmoothScrollFinishedListener() {
public void onSmoothScrollFinished() {
smoothScrollTo(0, SMOOTH_SCROLL_DURATION_MS, DEMO_SCROLL_INTERVAL, null);
* 动画样式
public static enum AnimationStyle {
* This is the default for Android-PullToRefresh. Allows you to use any
* drawable, which is automatically rotated and used as a Progress Bar.
* This is the old default, and what is commonly used on iOS. Uses an
* arrow image which flips depending on where the user has scrolled.
static AnimationStyle getDefault() {
return ROTATE;
* Maps an int to a specific mode. This is needed when saving state, or
* inflating the view from XML where the mode is given through a attr
* @param modeInt - int to map a Mode to
* @return Mode that modeInt maps to, or ROTATE by default.
static AnimationStyle mapIntToValue(int modeInt) {
switch (modeInt) {
return ROTATE;
return FLIP;
* 创建旋转Loading界面、flip Loding界面
* @param context
* @param mode
* @param scrollDirection
* @param attrs
LoadingLayout createLoadingLayout(Context context, Mode mode,
Orientation scrollDirection, TypedArray attrs) {
switch (this) {
case ROTATE:
return new RotateLoadingLayout(context, mode, scrollDirection, attrs);
case FLIP:
return new FlipLoadingLayout(context, mode, scrollDirection, attrs);
* 下拉刷新模式、上拉刷新模式、双向刷新模式
public static enum Mode {
* Disable all Pull-to-Refresh gesture and Refreshing handling
* 关闭上/下拉刷新和正在刷新的处理
DISABLED(0x0),
* Only allow the user to Pull from the start of the Refreshable View to
* refresh. The start is either the Top or Left, depending on the
* scrolling direction.
* 只允许从上/左边下拉刷新
PULL_FROM_START(0x1),
* Only allow the user to Pull from the end of the Refreshable View to
* refresh. The start is either the Bottom or Right, depending on the
* scrolling direction.
* 只允许从下/又边上拉刷新
PULL_FROM_END(0x2),
* Allow the user to both Pull from the start, from the end to refresh.
* 允许两边上/下拉刷新
BOTH(0x3),
* Disables Pull-to-Refresh gesture handling, but allows manually
* setting the Refresh state via
* {@link PullToRefreshBase#setRefreshing() setRefreshing()}.
* 关闭上/下拉刷新手势处理,但是允许通过代码执行下/上拉刷新。
MANUAL_REFRESH_ONLY(0x4);
* @deprecated Use {@link #PULL_FROM_START} from now on.
public static Mode PULL_DOWN_TO_REFRESH = Mode.PULL_FROM_START;
* @deprecated Use {@link #PULL_FROM_END} from now on.
public static Mode PULL_UP_TO_REFRESH = Mode.PULL_FROM_END;
* Maps an int to a specific mode. This is needed when saving state, or
* inflating the view from XML where the mode is given through a attr
* @param modeInt - int to map a Mode to
* @return Mode that modeInt maps to, or PULL_FROM_START by default.
static Mode mapIntToValue(final int modeInt) {
for (Mode value : Mode.values()) {
if (modeInt == value.getIntValue()) {
// If not, return default
return getDefault();
static Mode getDefault() {
return PULL_FROM_START;
private int mIntV
// The modeInt values need to match those from attrs.xml
Mode(int modeInt) {
mIntValue = modeI
* @return true if the mode permits Pull-to-Refresh
boolean permitsPullToRefresh() {
return !(this == DISABLED || this == MANUAL_REFRESH_ONLY);
* @return true if this mode wants the Loading Layout Header to be shown
* 返回是否允许下拉刷新
public boolean showHeaderLoadingLayout() {
return this == PULL_FROM_START || this == BOTH;
* @return true if this mode wants the Loading Layout Footer to be shown
* 返回是否允许上拉刷新
public boolean showFooterLoadingLayout() {
return this == PULL_FROM_END || this == BOTH || this == MANUAL_REFRESH_ONLY;
int getIntValue() {
return mIntV
// ===========================================================
// Inner, Anonymous Classes, and Enumerations
// ===========================================================
* Simple Listener that allows you to be notified when the user has scrolled
* to the end of the AdapterView. See (
* {@link PullToRefreshAdapterViewBase#setOnLastItemVisibleListener}.
* @author Chris Banes
public static interface OnLastItemVisibleListener {
* Called when the user has scrolled to the end of the list
public void onLastItemVisible();
* Listener that allows you to be notified when the user has started or
* finished a touch event. Useful when you want to append extra UI events
* (such as sounds). See (
* {@link PullToRefreshAdapterViewBase#setOnPullEventListener}.
* @author Chris Banes
public static interface OnPullEventListener&V extends View& {
* Called when the internal state has been changed, usually by the user
* pulling.
* @param refreshView - View which has had it's state change.
* @param state - The new state of View.
* @param direction - One of {@link Mode#PULL_FROM_START} or
{@link Mode#PULL_FROM_END} depending on which direction
the user is pulling. Only useful when &var&state&/var& is
{@link State#PULL_TO_REFRESH} or
{@link State#RELEASE_TO_REFRESH}.
public void onPullEvent(final PullToRefreshBase&V& refreshView, State state, Mode direction);
* Simple Listener to listen for any callbacks to Refresh.
* @author Chris Banes
public static interface OnRefreshListener&V extends View& {
* onRefresh will be called for both a Pull from start, and Pull from
public void onRefresh(final PullToRefreshBase&V& refreshView);
* An advanced version of the Listener to listen for callbacks to Refresh.
* This listener is different as it allows you to differentiate between Pull
* Ups, and Pull Downs.
* @author Chris Banes
public static interface OnRefreshListener2&V extends View& {
// TODO These methods need renaming to START/END rather than DOWN/UP
* onPullDownToRefresh will be called only when the user has Pulled from
* the start, and released.
public void onPullDownToRefresh(final PullToRefreshBase&V& refreshView);
* onPullUpToRefresh will be called only when the user has Pulled from
* the end, and released.
public void onPullUpToRefresh(final PullToRefreshBase&V& refreshView);
public static enum Orientation {
VERTICAL, HORIZONTAL;
* 状态:重置状态、拖动手势状态、释放拖动手势状态、正在刷新模式等
public static enum State {
* When the UI is in a state which means that user is not interacting
* with the Pull-to-Refresh function.
* 用户还没有和PullToRefresh交互
RESET(0x0),
* When the UI is being pulled by the user, but has not been pulled far
* enough so that it refreshes when released.
* 用户正在拖动刷新操作
PULL_TO_REFRESH(0x1),
* When the UI is being pulled by the user, and &strong&has&/strong&
* been pulled far enough so that it will refresh when released.
* 用户拖动释放后
RELEASE_TO_REFRESH(0x2),
* When the UI is currently refreshing, caused by a pull gesture.
* 正在刷新
REFRESHING(0x8),
* When the UI is currently refreshing, caused by a call to
* {@link PullToRefreshBase#setRefreshing() setRefreshing()}.
* 代码中指定正在刷新中...
MANUAL_REFRESHING(0x9),
* When the UI is currently overscrolling, caused by a fling on the
* Refreshable View.
* 正在弹簧滚动中...
OVERSCROLLING(0x10);
* Maps an int to a specific state. This is needed when saving state.
* @param stateInt - int to map a State to
* @return State that stateInt maps to
static State mapIntToValue(final int stateInt) {
for (State value : State.values()) {
if (stateInt == value.getIntValue()) {
// If not, return default
return RESET;
private int mIntV
State(int intValue) {
mIntValue = intV
int getIntValue() {
return mIntV
* 在独立线程中overscroll头部或尾部
final class SmoothScrollRunnable implements Runnable {
private final Interpolator mI
private final int mScrollToY;
private final int mScrollFromY;
private final long mD
private OnSmoothScrollFinishedListener mL
private boolean mContinueRunning =
private long mStartTime = -1;
private int mCurrentY = -1;
public SmoothScrollRunnable(int fromY, int toY, long duration,
OnSmoothScrollFinishedListener listener) {
mScrollFromY = fromY;
mScrollToY = toY;
mInterpolator = mScrollAnimationI
mDuration =
mListener =
public void run() {
* Only set mStartTime if this is the first time we're starting,
* else actually calculate the Y delta
if (mStartTime == -1) {
mStartTime = System.currentTimeMillis();
* We do do all calculations in long to reduce software float
* calculations. We use 1000 as it gives us good accuracy and
* small rounding errors
long normalizedTime = (1000 * (System.currentTimeMillis() - mStartTime)) / mD
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 we're not at the target Y, keep going...
if (mContinueRunning && mScrollToY != mCurrentY) {
ViewCompat.postOnAnimation(PullToRefreshBase.this, this);
if (null != mListener) {
mListener.onSmoothScrollFinished();
public void stop() {
mContinueRunning =
removeCallbacks(this);
static interface OnSmoothScrollFinishedListener {
void onSmoothScrollFinished();
核心类LoadingLayout代码分析:
/*******************************************************************************
* Copyright
Chris Banes.
* Licensed under the Apache License, Version 2.0 (the &License&);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an &AS IS& BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package com.handmark.pulltorefresh.library.
import android.annotation.SuppressL
import android.content.C
import android.content.res.ColorStateL
import android.content.res.TypedA
import android.graphics.T
import android.graphics.drawable.AnimationD
import android.graphics.drawable.D
import android.text.TextU
import android.util.TypedV
import android.view.G
import android.view.LayoutI
import android.view.V
import android.view.ViewG
import android.view.animation.I
import android.view.animation.LinearI
import android.widget.FrameL
import android.widget.ImageV
import android.widget.ProgressB
import android.widget.TextV
import com.handmark.pulltorefresh.library.ILoadingL
import com.handmark.pulltorefresh.library.PullToRefreshBase.M
import com.handmark.pulltorefresh.library.PullToRefreshBase.O
import com.handmark.pulltorefresh.library.R;
* 自定义Loading控件,这个是用户自定义Loading控件的基类,用户自定义的Loading控件
* 必须继承这个类,比如:FlipLoadingLayout、RotateLoadingLayout。这个类完成了Loading控件
* 的界面框架,其子类中还可以指定图片动画方式等。
@SuppressLint(&ViewConstructor&)
public abstract class LoadingLayout extends FrameLayout implements ILoadingLayout {
static final String LOG_TAG = &PullToRefresh-LoadingLayout&;
static final Interpolator ANIMATION_INTERPOLATOR = new LinearInterpolator();
private FrameLayout mInnerL // 该界面布局包含的布局器
protected final ImageView mHeaderI // 左边用于Rotate/Flip动画的图片
protected final ProgressBar mHeaderP // 左边用于动画的圆形进度条
private boolean mUseIntrinsicA // 使用内置动画的标志
private final TextView mHeaderT // 右边主标题
private final TextView mSubHeaderT // 右边副标题
protected final Mode mM // 下拉/上拉刷新模式
protected final Orientation mScrollD // Pull方向
private CharSequence mPullL
// 拖动手势时上拉/下拉刷新文本
private CharSequence mRefreshingL // 正在刷新的文本
private CharSequence mReleaseL
// 手势上/下拉释放后的文本
* 构造函数:加载Loading header或footer,并根据xml属性设置Loading header或footer,
* 最后根据客户端代码中指定的Loading header或footer样式(通过ILoadingLayout),再次
* 设置Loading View。
* @param context
* @param mode
* @param scrollDirection 滚动方向
* @param attrs 从PullToRefresh View传递过来的xml属性集合
public LoadingLayout(Context context, final Mode mode,
final Orientation scrollDirection, TypedArray attrs) {
super(context);
mScrollDirection = scrollD
switch (scrollDirection) {
case HORIZONTAL:
LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header_horizontal, this);
case VERTICAL:
default: // 从layou的xml中获取自定义界面,并加入到当前界面中(这个this参数就是layou的父类)
LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header_vertical, this);
mInnerLayout = (FrameLayout) findViewById(R.id.fl_inner);
mHeaderText = (TextView) mInnerLayout.findViewById(R.id.pull_to_refresh_text);
mHeaderProgress = (ProgressBar) mInnerLayout.findViewById(R.id.pull_to_refresh_progress);
mSubHeaderText = (TextView) mInnerLayout.findViewById(R.id.pull_to_refresh_sub_text);
mHeaderImage = (ImageView) mInnerLayout.findViewById(R.id.pull_to_refresh_image);
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) mInnerLayout.getLayoutParams();
switch (mode) {
case PULL_FROM_END: // 上拉
layoutParams.gravity = scrollDirection == Orientation.VERTICAL ? Gravity.TOP : Gravity.LEFT;
// Load in labels
mPullLabel = context.getString(R.string.pull_to_refresh_from_bottom_pull_label);
mRefreshingLabel = context.getString(R.string.pull_to_refresh_from_bottom_refreshing_label);
mReleaseLabel = context.getString(R.string.pull_to_refresh_from_bottom_release_label);
case PULL_FROM_START: // 下拉
layoutParams.gravity = scrollDirection == Orientation.VERTICAL ? Gravity.BOTTOM : Gravity.RIGHT;
// Load in labels
mPullLabel = context.getString(R.string.pull_to_refresh_pull_label);
mRefreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label);
mReleaseLabel = context.getString(R.string.pull_to_refresh_release_label);
// 设置属性
if (attrs.hasValue(R.styleable.PullToRefresh_ptrHeaderBackground)) {
Drawable background = attrs.getDrawable(R.styleable.PullToRefresh_ptrHeaderBackground);
if (null != background) {
ViewCompat.setBackground(this, background);
if (attrs.hasValue(R.styleable.PullToRefresh_ptrHeaderTextAppearance)) {
TypedValue styleID = new TypedValue();
attrs.getValue(R.styleable.PullToRefresh_ptrHeaderTextAppearance, styleID);
setTextAppearance(styleID.data);
if (attrs.hasValue(R.styleable.PullToRefresh_ptrSubHeaderTextAppearance)) {
TypedValue styleID = new TypedValue();
attrs.getValue(R.styleable.PullToRefresh_ptrSubHeaderTextAppearance, styleID);
setSubTextAppearance(styleID.data);
// Text Color attrs need to be set after TextAppearance attrs
if (attrs.hasValue(R.styleable.PullToRefresh_ptrHeaderTextColor)) {
ColorStateList colors = attrs.getColorStateList(R.styleable.PullToRefresh_ptrHeaderTextColor);
if (null != colors) {
setTextColor(colors);
if (attrs.hasValue(R.styleable.PullToRefresh_ptrHeaderSubTextColor)) {
ColorStateList colors = attrs.getColorStateList(R.styleable.PullToRefresh_ptrHeaderSubTextColor);
if (null != colors) {
setSubTextColor(colors);
// Try and get defined drawable from Attrs
Drawable imageDrawable =
if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawable)) {
imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawable);
// Check Specific Drawable from Attrs, these overrite the generic
// drawable attr above
switch (mode) {
case PULL_FROM_START:
if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawableStart)) {
imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawableStart);
} else if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawableTop)) {
Utils.warnDeprecation(&ptrDrawableTop&, &ptrDrawableStart&);
imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawableTop);
case PULL_FROM_END:
if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawableEnd)) {
imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawableEnd);
} else if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawableBottom)) {
Utils.warnDeprecation(&ptrDrawableBottom&, &ptrDrawableEnd&);
imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawableBottom);
// If we don't have a user defined drawable, load the default
if (null == imageDrawable) {
imageDrawable = context.getResources().getDrawable(getDefaultDrawableResId());
// Set Drawable, and save width/height
setLoadingDrawable(imageDrawable);
public final void setHeight(int height) {
ViewGroup.LayoutParams lp = (ViewGroup.LayoutParams) getLayoutParams();
lp.height =
requestLayout();
public final void setWidth(int width) {
ViewGroup.LayoutParams lp = (ViewGroup.LayoutParams) getLayoutParams();
lp.width =
requestLayout();
* 获得Loading View的宽度或高度
public final int getContentSize() {
switch (mScrollDirection) {
case HORIZONTAL:
return mInnerLayout.getWidth();
case VERTICAL:
return mInnerLayout.getHeight();
* 隐藏Loading View
public final void hideAllViews() {
if (View.VISIBLE == mHeaderText.getVisibility()) {
mHeaderText.setVisibility(View.INVISIBLE);
if (View.VISIBLE == mHeaderProgress.getVisibility()) {
mHeaderProgress.setVisibility(View.INVISIBLE);
if (View.VISIBLE == mHeaderImage.getVisibility()) {
mHeaderImage.setVisibility(View.INVISIBLE);
if (View.VISIBLE == mSubHeaderText.getVisibility()) {
mSubHeaderText.setVisibility(View.INVISIBLE);
* 在手势下/上拉时候回调
* @param scaleOfLayout
public final void onPull(float scaleOfLayout) {
if (!mUseIntrinsicAnimation) {
onPullImpl(scaleOfLayout);
* 下/上拉手势正在执行时的:刷新后的回调
public final void pullToRefresh() {
if (null != mHeaderText) {
mHeaderText.setText(mPullLabel);
// Now call the callback
pullToRefreshImpl();
* 正在下/上拉刷新中...
public final void refreshing() {
if (null != mHeaderText) {
mHeaderText.setText(mRefreshingLabel);
if (mUseIntrinsicAnimation) {
((AnimationDrawable) mHeaderImage.getDrawable()).start();
} else { // Now call the callback
refreshingImpl();
if (null != mSubHeaderText) {
mSubHeaderText.setVisibility(View.GONE);
public final void releaseToRefresh() {
if (null != mHeaderText) {
mHeaderText.setText(mReleaseLabel);
// Now call the callback
releaseToRefreshImpl();
* 重新设置Loading View
public final void reset() {
if (null != mHeaderText) {
mHeaderText.setText(mPullLabel);
mHeaderImage.setVisibility(View.VISIBLE);
if (mUseIntrinsicAnimation) {
((AnimationDrawable) mHeaderImage.getDrawable()).stop();
} else { // Now call the callback
resetImpl();
if (null != mSubHeaderText) {
if (TextUtils.isEmpty(mSubHeaderText.getText())) {
mSubHeaderText.setVisibility(View.GONE);
mSubHeaderText.setVisibility(View.VISIBLE);
* 设置最近更新文本
public void setLastUpdatedLabel(CharSequence label) {
setSubHeaderText(label);
* 设置Loading图片
public final void setLoadingDrawable(Drawable imageDrawable) {
mHeaderImage.setImageDrawable(imageDrawable); // Set Drawable
mUseIntrinsicAnimation = (imageDrawable instanceof AnimationDrawable);
// Now call the callback
onLoadingDrawableSet(imageDrawable);
* 设置下/上拉时候的文本
public void setPullLabel(CharSequence pullLabel) {
mPullLabel = pullL
* 设置正在刷新时的文本
public void setRefreshingLabel(CharSequence refreshingLabel) {
mRefreshingLabel = refreshingL
* 设置正在释放刷新时的文本
public void setReleaseLabel(CharSequence releaseLabel) {
mReleaseLabel = releaseL
* 设置文本字体
public void setTextTypeface(Typeface tf) {
mHeaderText.setTypeface(tf);
public final void showInvisibleViews() {
if (View.INVISIBLE == mHeaderText.getVisibility()) {
mHeaderText.setVisibility(View.VISIBLE);
if (View.INVISIBLE == mHeaderProgress.getVisibility()) {
mHeaderProgress.setVisibility(View.VISIBLE);
if (View.INVISIBLE == mHeaderImage.getVisibility()) {
mHeaderImage.setVisibility(View.VISIBLE);
if (View.INVISIBLE == mSubHeaderText.getVisibility()) {
mSubHeaderText.setVisibility(View.VISIBLE);
* Callbacks for derivative Layouts
// If we don't have a user defined drawable, load the default
protected abstract int getDefaultDrawableResId();
// 设置Loading图片时候的回调
protected abstract void onLoadingDrawableSet(Drawable imageDrawable);
// 在手势下/上拉时候回调
protected abstract void onPullImpl(float scaleOfLayout);
// 下/上拉手势正在执行时的:刷新后的回调
protected abstract void pullToRefreshImpl();
// 正在下/上拉刷新中...的回调
protected abstract void refreshingImpl();
// 手势释放后的回调
protected abstract void releaseToRefreshImpl();
// Loading View重置后的回调
protected abstract void resetImpl();
* 设置头部副标题
private void setSubHeaderText(CharSequence label) {
if (null != mSubHeaderText) {
if (TextUtils.isEmpty(label)) {
mSubHeaderText.setVisibility(View.GONE);
mSubHeaderText.setText(label);
// Only set it to Visible if we're GONE, otherwise VISIBLE will
// be set soon
if (View.GONE == mSubHeaderText.getVisibility()) {
mSubHeaderText.setVisibility(View.VISIBLE);
private void setSubTextAppearance(int value) {
if (null != mSubHeaderText) {
mSubHeaderText.setTextAppearance(getContext(), value);
private void setSubTextColor(ColorStateList color) {
if (null != mSubHeaderText) {
mSubHeaderText.setTextColor(color);
private void setTextAppearance(int value) {
if (null != mHeaderText) {
mHeaderText.setTextAppearance(getContext(), value);
if (null != mSubHeaderText) {
mSubHeaderText.setTextAppearance(getContext(), value);
private void setTextColor(ColorStateList color) {
if (null != mHeaderText) {
mHeaderText.setTextColor(color);
if (null != mSubHeaderText) {
mSubHeaderText.setTextColor(color);
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:209360次
积分:3758
积分:3758
排名:第5747名
原创:143篇
转载:211篇
评论:14条
(1)(3)(3)(6)(1)(7)(9)(13)(8)(45)(49)(35)(35)(48)(40)(51)(1)

我要回帖

更多关于 shade 的文章

 

随机推荐