Mako Shan

Mako 是一名密码朋克爱好者
这里是我记录生活和成长的地方

联系我的微信号
👏欢迎一起交流学习

开源项目:Android-PullToRefresh源码分析

功能实现

  1. 项目github地址:https://github.com/chrisbanes/Android-PullToRefresh
  2. 项目的目录结构

Image Title

  1. 代码分析

整个项目采用策略模式,把公共的部分实现为抽象类,并提供抽象接口(策略)让具体类来实现:
(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抽象类中提供了几个抽象方法:

 // 子类实现scroll方向
public abstract Orientation getPullToRefreshScrollDirection();
// 子类实现可刷新View,例如ListView、GridView等
protected abstract T createRefreshableView(Context context, AttributeSet attrs); 
 // 子类实现上拉刷新的条件,例如ListView实现上拉刷新的条件是上拉到底部
protected abstract boolean isReadyForPullEnd();
 // 子类实现下拉刷新的条件,例如ListView实现下拉刷新的条件是下拉到最上面第一行处
protected abstract boolean isReadyForPullStart();

(其实,对于继承AbsListView的ListView和GridView来说,该项目又封装了一层PullToRefreshAdapterViewBase)。
LoadingLayout是Loading界面的基类,也是抽象类,该抽象类根据x拉刷新方向,加载生成了一个基本Loading界面,可以让FlipLoadingLayout和RotateLoadingLayout继承,这2个类主要是指定了Loading界面中的图片动画,并实现以下抽象方法,指定各个状态时的图片:

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

核心类PullToRefreshBase,代码分析如下:

/**
 * 这个类是基类,实现了下拉/上拉刷新,并且提供抽象方法给继承类实现:提供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);
        }
    }
}
11
EditText插入字符串到光标所在位置
目录