开源项目:Android-PullToRefresh

项目链接:https://github.com/chrisbanes/Android-PullToRefresh

此项目的文件目录是:


核心库是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 2011, 2012 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.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.LinearLayout;

import com.handmark.pulltorefresh.library.internal.FlipLoadingLayout;
import com.handmark.pulltorefresh.library.internal.LoadingLayout;
import com.handmark.pulltorefresh.library.internal.RotateLoadingLayout;
import com.handmark.pulltorefresh.library.internal.Utils;
import com.handmark.pulltorefresh.library.internal.ViewCompat;

/**
 * 这个类是基类,实现了下拉/上拉刷新,并且提供抽象方法给继承类实现:提供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 = true;
	static final boolean USE_HW_LAYERS = false;
	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";

	// ===========================================================
	// Fields
	// ===========================================================

	private int mTouchSlop; // TouchSlop
	private float mLastMotionX, mLastMotionY; // 上一次手势坐标
	private float mInitialMotionX, mInitialMotionY; // 初始手势坐标

	private boolean mIsBeingDragged = false; // 标记正在手势拖动下拉刷新中...
	private State mState = State.RESET; // 默认是重启状态
	private Mode mMode = Mode.getDefault(); // 默认是下拉刷新

	private Mode mCurrentMode; // 当前模式
	T mRefreshableView; // 当前被刷新的View: ListView
	private FrameLayout mRefreshableViewWrapper; // 可刷新View(例如ListView)的包装器

	private boolean mShowViewWhileRefreshing = true; // 当刷新的时候是否允许展示View
	private boolean mScrollingWhileRefreshingEnabled = false; // 当刷新的时候是否允许滚动
	private boolean mFilterTouchEvents = true; // 是否过滤触摸事件
	private boolean mOverScrollEnabled = true; // 是否允许过滚动(弹簧效果)
	private boolean mLayoutVisibilityChangesEnabled = true;

	private Interpolator mScrollAnimationInterpolator;
	private AnimationStyle mLoadingAnimationStyle = AnimationStyle.getDefault();

	private LoadingLayout mHeaderLayout; // Loading头部界面
	private LoadingLayout mFooterLayout; // Loading尾部界面

	private OnRefreshListener<T> mOnRefreshListener;
	private OnRefreshListener2<T> mOnRefreshListener2;
	private OnPullEventListener<T> mOnPullEventListener;

	private SmoothScrollRunnable mCurrentSmoothScrollRunnable;	// 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);
		mMode = mode;
		init(context, null);
	}

	public PullToRefreshBase(Context context, Mode mode, AnimationStyle animStyle) {
		super(context);
		mMode = mode;
		mLoadingAnimationStyle = animStyle;
		init(context, null);
	}

	/**
	 * 这个重写函数非常重要:当该类用于ListView、ScrollView的时候,这些控件组一定会包含子控件,
	 * 因此一定需要重写这个方法,让其孩子加入到真正的控件中。
	 */
	@Override
	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);
		} else {
			throw new UnsupportedOperationException("Refreshable View is not a " +
					"ViewGroup so can't addView");
		}
	}

	@Override
	public final boolean demo() {
		if (mMode.showHeaderLoadingLayout() && isReadyForPullStart()) {
			smoothScrollToAndBack(-getHeaderSize() * 2);
			return true;
		} else if (mMode.showFooterLoadingLayout() && isReadyForPullEnd()) {
			smoothScrollToAndBack(getFooterSize() * 2);
			return true;
		}

		return false;
	}

	/**
	 * 获得当前Pull模式
	 */
	@Override
	public final Mode getCurrentMode() {
		return mCurrentMode;
	}

	@Override
	public final boolean getFilterTouchEvents() {
		return mFilterTouchEvents;
	}

	/**
	 * 获得下拉/上拉Loading布局代理,主要是关于下拉/上拉Loading header or footer
	 * 相关的文字/View等
	 */
	@Override
	public final ILoadingLayout getLoadingLayoutProxy() {
		return getLoadingLayoutProxy(true, true);
	}

	@Override
	public final ILoadingLayout getLoadingLayoutProxy(boolean includeStart, boolean includeEnd) {
		return createLoadingLayoutProxy(includeStart, includeEnd);
	}

	@Override
	public final Mode getMode() {
		return mMode;
	}

	/**
	 * 获得可刷新的View,比如ListView、GridView等
	 */
	@Override
	public final T getRefreshableView() {
		return mRefreshableView;
	}

	@Override
	public final boolean getShowViewWhileRefreshing() {
		return mShowViewWhileRefreshing;
	}

	@Override
	public final State getState() {
		return mState;
	}

	/**
	 * @deprecated See {@link #isScrollingWhileRefreshingEnabled()}.
	 */
	public final boolean isDisableScrollingWhileRefreshing() {
		return !isScrollingWhileRefreshingEnabled();
	}

	/**
	 * 判断是否Pull刷新使能
	 */
	@Override
	public final boolean isPullToRefreshEnabled() {
		return mMode.permitsPullToRefresh();
	}

	/**
	 * 是否允许过滚动效果(弹簧效果)
	 */
	@Override
	public final boolean isPullToRefreshOverScrollEnabled() {
		return VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD && mOverScrollEnabled
				&& OverscrollHelper.isAndroidOverScrollEnabled(mRefreshableView);
	}

	/**
	 * 返回是否正在刷新
	 */
	@Override
	public final boolean isRefreshing() {
		return mState == State.REFRESHING || mState == State.MANUAL_REFRESHING;
	}

	@Override
	public final boolean isScrollingWhileRefreshingEnabled() {
		return mScrollingWhileRefreshingEnabled;
	}

	/**
	 * 重写这个函数的目的就是,拦截上拉/下拉事件给此PullToRefresh的onTouchEvent()函数处理,
	 * 其余事件仍旧传递给子View处理,不改变子View原本的手势处理方式。
	 * 
	 * 重写该方法处理手势事件,实现下拉刷新。在这里过滤下手势事件,该PullToRefresh本身
	 * 只处理满足条件的下拉/上拉刷新事件,其余事件仍旧需要传递给子View(如ListView等)处理。
	 * 
	 * 当手势时拖动中,并且满足下拉刷新或上拉刷新条件的时候,返回true拦截向子View传递
	 * 手势事件,让PullToRefresh自己在onTouchEvent()中处理上拉或下拉刷新事件。
	 */
	@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;
		}
		
		// 如果手势是正在拖动下拉刷新手势,则处理手势(让onTouchEvent()处理)
		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 (!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: // 按下手势不处理,让子View处理
			if (isReadyForPull()) { // 准备好可以上拉刷新或下拉刷新
				mLastMotionY = mInitialMotionY = event.getY();
				mLastMotionX = mInitialMotionX = event.getX();
				mIsBeingDragged = false;
			}
			break;
		}

		return mIsBeingDragged;
	}

	/**
	 * 仅处理上拉/下拉刷新手势事件,其余手势事件在onInterceptTouchEvent()函数中
	 * 已经传递给子View(如ListView等)处理了。这里剩下的就是onInterceptTouchEvent()
	 * 函数过滤过来的上拉/下拉刷新事件。
	 */
	@Override
	public final boolean onTouchEvent(MotionEvent event) {
		if (!isPullToRefreshEnabled()) {
			return false;
		}

		/*
		 * If we're refreshing, and the flag is set. Eat the event
		 * 如果正在刷新中...并且是刷新过程中不允许滚动事件,则进行空处理。
		 */
		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(); // 执行Loading header or footer的滚动事件
				return true;
			}
			break;

		case MotionEvent.ACTION_DOWN:
			if (isReadyForPull()) {
				mLastMotionY = mInitialMotionY = event.getY();
				mLastMotionX = mInitialMotionX = event.getX();
				return true;
			}
			break;

		/*
		 * 因为在onInterceptTouchEvent()函数中手势移动事件被处理了,所以之后的抬起事件仍旧在
		 * 此onTouchEvent()事件中处理。
		 * 开启独立线程执行滚动回滚。
		 */
		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 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;
		}

		return false;
	}
	
	/**
	 * 每次刷新完数据后都调用这个方法重置PullToRefresh
	 */
	@Override
	public final void onRefreshComplete() {
		if (isRefreshing()) {
			setState(State.RESET);
		}
	}

	public final void setScrollingWhileRefreshingEnabled(boolean allowScrollingWhileRefreshing) {
		mScrollingWhileRefreshingEnabled = allowScrollingWhileRefreshing;
	}

	/**
	 * @deprecated See {@link #setScrollingWhileRefreshingEnabled(boolean)}
	 */
	public void setDisableScrollingWhileRefreshing(boolean disableScrollingWhileRefreshing) {
		setScrollingWhileRefreshingEnabled(!disableScrollingWhileRefreshing);
	}

	@Override
	public final void setFilterTouchEvents(boolean filterEvents) {
		mFilterTouchEvents = filterEvents;
	}

	/**
	 * @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);
	}

	@Override
	public void setLongClickable(boolean longClickable) {
		getRefreshableView().setLongClickable(longClickable);
	}

	@Override
	public final void setMode(Mode mode) {
		if (mode != mMode) {
			if (DEBUG) {
				Log.d(LOG_TAG, "Setting mode to: " + mode);
			}
			mMode = mode;
			updateUIForMode(); // 更新PullToRefresh界面
		}
	}

	public void setOnPullEventListener(OnPullEventListener<T> listener) {
		mOnPullEventListener = listener;
	}

	@Override
	public final void setOnRefreshListener(OnRefreshListener<T> listener) {
		mOnRefreshListener = listener;
		mOnRefreshListener2 = null;
	}

	@Override
	public final void setOnRefreshListener(OnRefreshListener2<T> listener) {
		mOnRefreshListener2 = listener;
		mOnRefreshListener = null;
	}

	/**
	 * @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);
	}

	@Override
	public final void setPullToRefreshOverScrollEnabled(boolean enabled) {
		mOverScrollEnabled = enabled;
	}

	/**
	 * 设置正在刷新
	 */
	@Override
	public final void setRefreshing() {
		setRefreshing(true);
	}

	/**
	 * 设置正在刷新
	 */
	@Override
	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 = interpolator;
	}

	@Override
	public final void setShowViewWhileRefreshing(boolean showView) {
		mShowViewWhileRefreshing = showView;
	}

	/**
	 * @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) {
		mState = state;
		if (DEBUG) {
			Log.d(LOG_TAG, "State: " + mState.name());
		}

		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;
		}

		// 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
	 * @return
	 */
	protected LoadingLayout createLoadingLayout(Context context, Mode mode, TypedArray attrs) {
		LoadingLayout layout = mLoadingAnimationStyle.createLoadingLayout(context, mode, 
				getPullToRefreshScrollDirection(), attrs);

		layout.setVisibility(View.INVISIBLE);
		return layout;
	}

	/**
	 * 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界面代理
		}

		return proxy;
	}

	/**
	 * 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.
	 * <p/>
	 * 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 = false;
	}

	protected final LoadingLayout getFooterLayout() {
		return mFooterLayout;
	}

	protected final int getFooterSize() {
		return mFooterLayout.getContentSize();
	}

	protected final LoadingLayout getHeaderLayout() {
		return mHeaderLayout;
	}

	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 mRefreshableViewWrapper;
	}

	/**
	 * 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();
			break;
			
		case PULL_FROM_START:
			mHeaderLayout.pullToRefresh();
			break;
			
		default:
			// NO-OP
			break;
		}
	}

	/**
	 * 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() {
					@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 {
			// 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();
			break;
			
		case PULL_FROM_START:
			mHeaderLayout.releaseToRefresh();
			break;
			
		default:
			// NO-OP
			break;
		}
	}

	/**
	 * Called when the UI has been to be updated to be in the
	 * {@link State#RESET} state.
	 */
	protected void onReset() {
		mIsBeingDragged = false;
		mLayoutVisibilityChangesEnabled = true;

		// Always reset both layouts, just in case...
		mHeaderLayout.reset();
		mFooterLayout.reset();

		smoothScrollTo(0);
	}

	@Override
	protected final void onRestoreInstanceState(Parcelable state) {
		if (state instanceof Bundle) {
			Bundle bundle = (Bundle) state;

			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);
			return;
		}

		super.onRestoreInstanceState(state);
	}

	@Override
	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());

		return bundle;
	}

	@Override
	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() {
			@Override
			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 = -maximumPullScroll;
			} else {
				pLeft = 0;
			}

			if (mMode.showFooterLoadingLayout()) {
				mFooterLayout.setWidth(maximumPullScroll);
				pRight = -maximumPullScroll;
			} else {
				pRight = 0;
			}
			break;

		case VERTICAL:
			if (mMode.showHeaderLoadingLayout()) {
				mHeaderLayout.setHeight(maximumPullScroll); // 设置loading header高度
				pTop = -maximumPullScroll; // 把loading header塞到顶部栏之后的距离参数
			} else {
				pTop = 0;
			}

			if (mMode.showFooterLoadingLayout()) {
				mFooterLayout.setHeight(maximumPullScroll); // 设置loading footer高度
				pBottom = -maximumPullScroll;
			} else {
				pBottom = 0;
			}
			break;
		}

		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 = width;
				mRefreshableViewWrapper.requestLayout();
			}
			break;
			
		case VERTICAL:
			if (lp.height != height) {
				lp.height = height;
				mRefreshableViewWrapper.requestLayout();
			}
			break;
		}
	}

	/**
	 * 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);
			} else {
				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);
			break;
			
		case HORIZONTAL:
			scrollTo(value, 0);
			break;
		}
	}

	/**
	 * 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);
			break;
			
		case VERTICAL:
		default:
			setOrientation(LinearLayout.VERTICAL);
			break;
		}

		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();
		default:
			return false;
		}
	}

	/*
	 * Actions a Pull Event 手势下/上拉事件
	 * @return true if the Event has been handled, false if there has been no
	 * change
	 * 执行滚动事件
	 */
	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); // 设置laoding header滚动到指定位置

		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);
			}
		}
	}

	/*
	 * 获得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:
		default:
			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:
		default:
			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 oldScrollValue;
		switch (getPullToRefreshScrollDirection()) { // TODO
		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);
			}
		}
	}

	private final void smoothScrollToAndBack(int y) {
		smoothScrollTo(y, SMOOTH_SCROLL_DURATION_MS, 0, new OnSmoothScrollFinishedListener() {

			@Override
			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.
		 */
		ROTATE,

		/**
		 * 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.
		 */
		FLIP;

		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
		 * int.
		 * 
		 * @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) {
			case 0x0:
			default:
				return ROTATE;
				
			case 0x1:
				return FLIP;
			}
		}

		/**
		 * 创建旋转Loading界面、flip Loding界面
		 * @param context
		 * @param mode
		 * @param scrollDirection
		 * @param attrs
		 * @return
		 */
		LoadingLayout createLoadingLayout(Context context, Mode mode, 
				Orientation scrollDirection, TypedArray attrs) {
			
			switch (this) {
			case ROTATE:
			default:
				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
		 * int.
		 * 
		 * @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()) {
					return value;
				}
			}

			// If not, return default
			return getDefault();
		}

		static Mode getDefault() {
			return PULL_FROM_START;
		}

		private int mIntValue;

		// The modeInt values need to match those from attrs.xml
		Mode(int modeInt) {
			mIntValue = modeInt;
		}

		/**
		 * @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 mIntValue;
		}

	}

	// ===========================================================
	// 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
		 * end
		 */
		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()) {
					return value;
				}
			}

			// If not, return default
			return RESET;
		}

		private int mIntValue;

		State(int intValue) {
			mIntValue = intValue;
		}

		int getIntValue() {
			return mIntValue;
		}
	}

	/**
	 * 在独立线程中overscroll头部或尾部
	 */
	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() {

			/**
			 * Only set mStartTime if this is the first time we're starting,
			 * else actually calculate the Y delta
			 */
			if (mStartTime == -1) {
				mStartTime = System.currentTimeMillis();
			} else {

				/**
				 * 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)) / 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 we're not at the target Y, keep going...
			if (mContinueRunning && mScrollToY != mCurrentY) {
				ViewCompat.postOnAnimation(PullToRefreshBase.this, this);
			} else {
				if (null != mListener) {
					mListener.onSmoothScrollFinished();
				}
			}
		}

		public void stop() {
			mContinueRunning = false;
			removeCallbacks(this);
		}
	}
	static interface OnSmoothScrollFinishedListener {
		void onSmoothScrollFinished();
	}
}

核心类LoadingLayout代码分析:

/*******************************************************************************
 * Copyright 2011, 2012 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.internal;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.handmark.pulltorefresh.library.ILoadingLayout;
import com.handmark.pulltorefresh.library.PullToRefreshBase.Mode;
import com.handmark.pulltorefresh.library.PullToRefreshBase.Orientation;
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 mInnerLayout; // 该界面布局包含的布局器

	protected final ImageView mHeaderImage; // 左边用于Rotate/Flip动画的图片
	protected final ProgressBar mHeaderProgress; // 左边用于动画的圆形进度条

	private boolean mUseIntrinsicAnimation;	// 使用内置动画的标志

	private final TextView mHeaderText; // 右边主标题
	private final TextView mSubHeaderText; // 右边副标题

	protected final Mode mMode; // 下拉/上拉刷新模式
	protected final Orientation mScrollDirection; // Pull方向

	private CharSequence mPullLabel;		// 拖动手势时上拉/下拉刷新文本
	private CharSequence mRefreshingLabel;	// 正在刷新的文本
	private CharSequence mReleaseLabel;		// 手势上/下拉释放后的文本

	/**
	 * 构造函数:加载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);
		
		mMode = mode;
		mScrollDirection = scrollDirection;

		switch (scrollDirection) {
		case HORIZONTAL:
			LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header_horizontal, this);
			break;
			
		case VERTICAL:
		default: // 从layou的xml中获取自定义界面,并加入到当前界面中(这个this参数就是layou的父类)
			LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header_vertical, this);
			break;
		}
		
		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);
			break;

		case PULL_FROM_START:	// 下拉
		default:
			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);
			break;
		}

		// 设置属性
		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 = null;
		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:
		default:
			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);
			}
			break;

		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);
			}
			break;
		}

		// 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);

		reset();
	}

	public final void setHeight(int height) {
		ViewGroup.LayoutParams lp = (ViewGroup.LayoutParams) getLayoutParams();
		lp.height = height;
		requestLayout();
	}

	public final void setWidth(int width) {
		ViewGroup.LayoutParams lp = (ViewGroup.LayoutParams) getLayoutParams();
		lp.width = width;
		requestLayout();
	}

	/**
	 * 获得Loading View的宽度或高度
	 * @return
	 */
	public final int getContentSize() {
		switch (mScrollDirection) {
		case HORIZONTAL:
			return mInnerLayout.getWidth();
			
		case VERTICAL:
		default:
			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);
			} else {
				mSubHeaderText.setVisibility(View.VISIBLE);
			}
		}
	}

	/**
	 * 设置最近更新文本
	 */
	@Override
	public void setLastUpdatedLabel(CharSequence label) {
		setSubHeaderText(label);
	}

	/**
	 * 设置Loading图片
	 */
	@Override
	public final void setLoadingDrawable(Drawable imageDrawable) {
		mHeaderImage.setImageDrawable(imageDrawable);	// Set Drawable
		mUseIntrinsicAnimation = (imageDrawable instanceof AnimationDrawable);

		// Now call the callback
		onLoadingDrawableSet(imageDrawable);
	}

	/**
	 * 设置下/上拉时候的文本
	 */
	@Override
	public void setPullLabel(CharSequence pullLabel) {
		mPullLabel = pullLabel;
	}

	/**
	 * 设置正在刷新时的文本
	 */
	@Override
	public void setRefreshingLabel(CharSequence refreshingLabel) {
		mRefreshingLabel = refreshingLabel;
	}

	/**
	 * 设置正在释放刷新时的文本
	 */
	@Override
	public void setReleaseLabel(CharSequence releaseLabel) {
		mReleaseLabel = releaseLabel;
	}

	/**
	 * 设置文本字体
	 */
	@Override
	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);
			} else {
				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);
		}
	}
}

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值