com.limxing.library.PullToRefresh.SwipeRefreshLayout.java Source code

Java tutorial

Introduction

Here is the source code for com.limxing.library.PullToRefresh.SwipeRefreshLayout.java

Source

/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * 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.limxing.library.PullToRefresh;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
import android.widget.AbsListView;

import com.limxing.library.R;
import com.limxing.library.utils.DisplayUtil;

/**
 *
 *
 * The SwipeRefreshLayout should be used whenever the user can refresh the
 * contents of a view via a vertical swipe gesture. The activity that
 * instantiates this view should add an OnRefreshListener to be notified
 * whenever the swipe to refresh gesture is completed. The SwipeRefreshLayout
 * will notify the listener each and every time the gesture is completed again;
 * the listener is responsible for correctly determining when to actually
 * initiate a refresh of its content. If the listener determines there should
 * not be a refresh, it must call setRefreshing(false) to cancel any visual
 * indication of a refresh. If an activity wishes to show just the progress
 * animation, it should call setRefreshing(true). To disable the gesture and
 * progress animation, call setEnabled(false) on the view.
 * 
 *
 * This layout should be made the parent of the view that will be refreshed as a
 * result of the gesture and can only support one direct child. This view will
 * also be made the target of the gesture and will be forced to match both the
 * width and the height supplied in this layout. The SwipeRefreshLayout does not
 * provide accessibility events; instead, a menu item must be provided to allow
 * refresh of the content wherever this gesture is used.
 *
 */
//<com.limxing.library.PullToRefresh.SwipeRefreshLayout
//      android:id="@+id/main_fresh"
//      android:layout_width="match_parent"
//      android:layout_height="match_parent"
//      android:layout_below="@+id/main_title"
//      swiperefresh:srlAnimationStyle="rotate"
//      swiperefresh:srlTextSize="16sp" >
//
//      <ListView
//      android:id="@+id/main_listview"
//      android:layout_width="match_parent"
//      android:layout_height="match_parent"
//      android:background="#ffffff"
//      android:dividerHeight="1dp" >
//      </ListView>
//      </com.limxing.library.PullToRefresh.SwipeRefreshLayout>
public class SwipeRefreshLayout extends ViewGroup {
    private static final String LOG_TAG = SwipeRefreshLayout.class.getSimpleName();

    private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
    private static final float MAX_SWIPE_DISTANCE_FACTOR = .6f;
    private static final int REFRESH_TRIGGER_DISTANCE = 200;
    private static final int INVALID_POINTER = -1;
    private static final int DEFAULT_TYPE = 0x0;

    private static final int PULL_TO_REFRESH = 0x0;
    private static final int RELEASE_TO_REFRESH = 0x1;

    private SwipeProgressBar mProgressBar; // the thing that shows progress is
    // going
    private View mTarget; // the content that gets pulled down
    private int mOriginalOffsetTop;
    private int mOriginalOffsetTopBackup;
    private OnRefreshListener mListener;
    private OnLoadListener mLoadListener;
    private int mFrom;
    private boolean mRefreshing = false;
    private boolean tmpRefreshing = false;
    private boolean mLoading = false;
    private boolean tmpLoading = false;
    private int mTouchSlop;
    private float mDistanceToTriggerSync = -1;
    private int mMediumAnimationDuration;
    private int mProgreesBarWidth;
    private int mCurrentTargetOffsetTop;

    private float mInitialMotionY;
    private float mInitialMotionX;
    // private float mLastMotionY;
    private boolean mIsBeingDragged;
    private int mActivePointerId = INVALID_POINTER;

    // Target is returning to its start offset because it was cancelled or a
    // refresh was triggered.
    private boolean mReturningToStart;
    private final DecelerateInterpolator mDecelerateInterpolator;
    // private static final int[] LAYOUT_ATTRS = new int[] {
    // android.R.attr.state_enabled // state_enabled
    // };

    // customs
    private int headerHeight = -1;
    // private int screenWidth;
    private String defaultText = "";
    private String latestRefreshTime = "";
    private String latestLoadTime = "";
    private SimpleDateFormat formatter = new SimpleDateFormat("MM  dd  HH:mm", Locale.CHINA);

    /**
     * ? 01
     */
    private int direction;

    private int lastDiff;

    private boolean up;
    private boolean down;

    private static final float DEFAULT_TIPS_TEXTSIZE = 18;
    private static final int DEFAULT_TEXT_COLOR = 0xFF808080;

    private int ptr_drawable;
    private int ptr_flip_drawable;
    private int finished_drawable;
    private boolean pull2refresh;
    private boolean pull2load;
    private float textSize;
    private int textColor;

    private static final String DEFAULT_PULL_DOWN_LABEL = "";
    private static final String DEFAULT_REFRESHING_LABEL = "...";
    private static final String DEFAULT_RELEASE_DOWN_LABEL = "?";

    private static final String DEFAULT_PULL_UP_LABEL = "";
    private static final String DEFAULT_LOADING_LABEL = "...";
    private static final String DEFAULT_RELEASE_UP_LABEL = "?";

    private String pullDownLabel;
    private String refreshingLabel;
    private String releaseDownLabel;

    private String pullUpLabel;
    private String loadingLabel;
    private String releaseUpLabel;

    /**
     * 0:rotate 1:flip
     */
    private int type;

    private int state;

    private double trigger_angle;

    private final Animation mAnimateToStartPosition = new Animation() {
        @Override
        public void applyTransformation(float interpolatedTime, Transformation t) {
            int targetTop = 0;

            if (mFrom != mOriginalOffsetTop) {
                targetTop = (mFrom + (int) ((mOriginalOffsetTop - mFrom) * interpolatedTime));
            }
            int offset = targetTop - mTarget.getTop();
            final int currentTop = mTarget.getTop();
            setTargetOffsetTopAndBottom(offset);
            setProgressBarProperty(currentTop);
        }
    };

    private Animation mShrinkTrigger = new Animation() {
        @Override
        public void applyTransformation(float interpolatedTime, Transformation t) {
            setProgressBarProperty(mTarget.getTop());
        }
    };

    private final AnimationListener mReturnToStartPositionListener = new BaseAnimationListener() {
        @Override
        public void onAnimationEnd(Animation animation) {
            // Once the target content has returned to its start position, reset
            // the target offset to 0
            if (isRefreshing()) {
                mCurrentTargetOffsetTop = headerHeight;
            } else if (mLoading) {
                mCurrentTargetOffsetTop = -headerHeight;
            } else {
                mCurrentTargetOffsetTop = 0;
            }
            lastDiff = 0;
            if (!tmpRefreshing) {
                mRefreshing = false;
            }
            if (!tmpLoading) {
                mLoading = false;
            }
            /*
             * if(!isRefreshing()) { mRefreshing = false; } if(!mLoading) {
             * mLoading = false; }
             */
        }
    };

    private final AnimationListener mShrinkAnimationListener = new BaseAnimationListener() {
        @Override
        public void onAnimationEnd(Animation animation) {
            // mCurrPercentage = 0;
        }
    };

    private final Runnable mReturnToStartPosition = new Runnable() {

        @Override
        public void run() {
            mReturningToStart = true;
            animateOffsetToStartPosition(mCurrentTargetOffsetTop + getPaddingTop(), mReturnToStartPositionListener);

        }

    };

    // Cancel the refresh gesture and animate everything back to its original
    // state.
    private final Runnable mCancel = new Runnable() {

        @Override
        public void run() {
            mReturningToStart = true;
            // Timeout fired since the user last moved their finger; animate the
            // trigger to 0 and put the target back at its original position
            if (mProgressBar != null) {
                mShrinkTrigger.setDuration(mMediumAnimationDuration);
                mShrinkTrigger.setAnimationListener(mShrinkAnimationListener);
                mShrinkTrigger.reset();
                mShrinkTrigger.setInterpolator(mDecelerateInterpolator);
                startAnimation(mShrinkTrigger);
            }
            // setTriggerPercentage(0, mCurrentTargetOffsetTop +
            // getPaddingTop());
            animateOffsetToStartPosition(mCurrentTargetOffsetTop + getPaddingTop(), mReturnToStartPositionListener);
        }

    };

    private float actiondown;

    private float actionup;

    private String mText = "?";

    /**
     * Simple constructor to use when creating a SwipeRefreshLayout from code.
     * 
     * @param context
     */
    public SwipeRefreshLayout(Context context) {
        this(context, null);
    }

    /**
     * Constructor that is called when inflating SwipeRefreshLayout from XML.
     * 
     * @param context
     * @param attrs
     */
    public SwipeRefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);

        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();

        mMediumAnimationDuration = getResources().getInteger(android.R.integer.config_longAnimTime);

        setWillNotDraw(false);

        final DisplayMetrics metrics = getResources().getDisplayMetrics();
        mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);
        // screenWidth = metrics.widthPixels;

        trigger_angle = Math.atan((double) metrics.widthPixels / metrics.heightPixels);

        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.swiperefresh);
        // setEnabled(a.getBoolean(0, true));
        setEnabled(true);

        ptr_drawable = a.getResourceId(R.styleable.swiperefresh_roateImage, R.drawable.default_ptr_rotate);
        ptr_flip_drawable = a.getResourceId(R.styleable.swiperefresh_flipImage, R.drawable.default_ptr_flip);
        finished_drawable = a.getResourceId(R.styleable.swiperefresh_finishedImage, R.drawable.xlistview_success);
        pull2refresh = a.getBoolean(R.styleable.swiperefresh_ptr, true);
        pull2load = a.getBoolean(R.styleable.swiperefresh_ptl, true);
        textSize = a.getDimension(R.styleable.swiperefresh_srlTextSize, DEFAULT_TIPS_TEXTSIZE * metrics.density);
        textColor = a.getColor(R.styleable.swiperefresh_srlTextColor, DEFAULT_TEXT_COLOR);
        type = a.getInt(R.styleable.swiperefresh_srlAnimationStyle, DEFAULT_TYPE);

        pullDownLabel = a.getString(R.styleable.swiperefresh_pullDownLabel);
        refreshingLabel = a.getString(R.styleable.swiperefresh_refreshingLabel);
        releaseDownLabel = a.getString(R.styleable.swiperefresh_releaseDownLabel);

        pullUpLabel = a.getString(R.styleable.swiperefresh_pullUpLabel);
        loadingLabel = a.getString(R.styleable.swiperefresh_loadingLabel);
        releaseUpLabel = a.getString(R.styleable.swiperefresh_releaseUpLabel);

        if (null == pullDownLabel || pullDownLabel.equals("")) {
            pullDownLabel = DEFAULT_PULL_DOWN_LABEL;
        }
        if (null == refreshingLabel || refreshingLabel.equals("")) {
            refreshingLabel = DEFAULT_REFRESHING_LABEL;
        }
        if (null == releaseDownLabel || releaseDownLabel.equals("")) {
            releaseDownLabel = DEFAULT_RELEASE_DOWN_LABEL;
        }

        if (null == pullUpLabel || pullUpLabel.equals("")) {
            pullUpLabel = DEFAULT_PULL_UP_LABEL;
        }
        if (null == loadingLabel || loadingLabel.equals("")) {
            loadingLabel = DEFAULT_LOADING_LABEL;
        }
        if (null == releaseUpLabel || releaseUpLabel.equals("")) {
            releaseUpLabel = DEFAULT_RELEASE_UP_LABEL;
        }

        mProgressBar = new SwipeProgressBar(this, textSize, textColor, ptr_drawable, ptr_flip_drawable,
                finished_drawable, type);
        a.recycle();
    }

    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();
        removeCallbacks(mCancel);
        removeCallbacks(mReturnToStartPosition);
    }

    @Override
    public void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        removeCallbacks(mReturnToStartPosition);
        removeCallbacks(mCancel);
        mProgressBar.destory();
    }

    private void animateOffsetToStartPosition(int from, AnimationListener listener) {
        mFrom = from;
        mAnimateToStartPosition.reset();
        mAnimateToStartPosition.setDuration(mMediumAnimationDuration);
        mAnimateToStartPosition.setAnimationListener(listener);
        mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator);
        mTarget.startAnimation(mAnimateToStartPosition);
    }

    /**
     * Set the listener to be notified when a refresh is triggered via the swipe
     * gesture.
     */
    public void setOnRefreshListener(OnRefreshListener listener) {
        mListener = listener;
    }

    public void setOnLoadListener(OnLoadListener mLoadListener) {
        this.mLoadListener = mLoadListener;
    }

    private void setProgressBarProperty(float diff) {

        if (direction == 1) {
            mProgressBar.setBounds(0, mTarget.getBottom() + mTarget.getPaddingBottom(), mProgreesBarWidth,
                    mTarget.getBottom() + mTarget.getPaddingBottom() + Math.abs((int) diff));
            mProgressBar.setProperty(defaultText, latestLoadTime, direction, state);

        } else {
            mProgressBar.setBounds(0, 0, mProgreesBarWidth, (int) diff);
            mProgressBar.setProperty(defaultText, latestRefreshTime, direction, state);

        }

        mProgressBar.setWidth(mProgreesBarWidth);
    }

    /**
     * Notify the widget that refresh state has changed. Do not call this when
     * refresh is triggered by a swipe gesture.
     * 
     * @param refreshing
     *            Whether or not the view should show refresh progress.
     */
    public void setRefreshing(boolean refreshing) {
        if (mRefreshing != refreshing) {
            tmpRefreshing = refreshing;
            if (refreshing) {
                mRefreshing = refreshing;
            }
            // mRefreshing = refreshing;
            ensureTarget();
            if (refreshing) {
                mProgressBar.start();
                mReturnToStartPosition.run();
                latestRefreshTime = formatter.format(new Date());
                mProgressBar.setProcessingTips(refreshingLabel);
            } else {
                // mReturnToStartPosition.run();
                mOriginalOffsetTop = mOriginalOffsetTopBackup;

                mProgressBar.stop(mText);
                postDelayed(mReturnToStartPosition, 500);
            }
            // mReturnToStartPosition.run();
        }
    }

    /**
     * 
     * 
     * @param text
     */

    public void setFaildText(String text) {
        mText = text;
    }

    /**
     * ?
     * 
     *
     */

    public String getFaildText() {
        return mText;
    }

    public void setLoading(boolean loading) {
        if (mLoading != loading) {
            tmpLoading = loading;
            if (loading) {
                mLoading = loading;
            }
            // mLoading = loading;
            ensureTarget();
            if (loading) {
                mReturnToStartPosition.run();
                mProgressBar.start();
                mProgressBar.setProcessingTips(loadingLabel);
            } else {
                // mReturnToStartPosition.run();
                mOriginalOffsetTop = mOriginalOffsetTopBackup;
                mProgressBar.stop(mText);
                postDelayed(mReturnToStartPosition, 500);
            }
            // mReturnToStartPosition.run();
        }
    }

    /**
     * @return Whether the SwipeRefreshWidget is actively showing refresh
     *         progress.
     */
    public boolean isRefreshing() {
        return mRefreshing;
    }

    private void ensureTarget() {
        // Don't bother getting the parent height if the parent hasn't been laid
        // out yet.
        if (mTarget == null) {
            if (getChildCount() > 1 && !isInEditMode()) {
                throw new IllegalStateException("SwipeRefreshLayout can host only one direct child");
            }
            mTarget = getChildAt(0);

            mOriginalOffsetTopBackup = mOriginalOffsetTop = mTarget.getTop() + getPaddingTop();
        }
        final DisplayMetrics metrics = getResources().getDisplayMetrics();
        if (headerHeight == -1) {
            //
            headerHeight = (int) (DisplayUtil.getFontHeight(textSize) * 2 + 5 * metrics.density * 4)
                    + DisplayUtil.dip2px(getContext(), 10);
        }

        if (isRefreshing()) {
            mOriginalOffsetTop = headerHeight + getPaddingTop();
        } else if (mLoading) {
            mOriginalOffsetTop = -headerHeight + getPaddingTop();
        } else {
            mOriginalOffsetTop = mOriginalOffsetTopBackup;
        }
        if (mDistanceToTriggerSync == -1) {
            if (getParent() != null && ((View) getParent()).getHeight() > 0) {

                mDistanceToTriggerSync = (int) Math.min(
                        ((View) getParent()).getHeight() * MAX_SWIPE_DISTANCE_FACTOR,
                        REFRESH_TRIGGER_DISTANCE * metrics.density);
            }
        }
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        mProgressBar.draw(canvas);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        final int width = getMeasuredWidth();
        final int height = getMeasuredHeight();
        // TODO header?
        // mProgressBar.setBounds(0, 0, width, mCurrentTargetOffsetTop);
        mProgreesBarWidth = width;
        if (getChildCount() == 0) {
            return;
        }
        final View child = getChildAt(0);
        final int childLeft = getPaddingLeft();
        final int childTop = mCurrentTargetOffsetTop + getPaddingTop();
        final int childWidth = width - getPaddingLeft() - getPaddingRight();
        final int childHeight = height - getPaddingTop() - getPaddingBottom();
        child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (getChildCount() > 1 && !isInEditMode()) {
            throw new IllegalStateException("SwipeRefreshLayout can host only one direct child");
        }
        if (getChildCount() > 0) {
            getChildAt(0).measure(
                    MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                            MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
                            MeasureSpec.EXACTLY));
        }
    }

    /**
     * @return Whether it is possible for the child view of this layout to
     *         scroll up. Override this if the child view is a custom view.
     */
    public boolean canChildScrollUp() {
        if (android.os.Build.VERSION.SDK_INT < 14) {
            if (mTarget instanceof AbsListView) {
                final AbsListView absListView = (AbsListView) mTarget;
                return absListView.getChildCount() > 0 && (absListView.getFirstVisiblePosition() > 0
                        || absListView.getChildAt(0).getTop() < absListView.getPaddingTop());
            } else {
                return mTarget.getScrollY() > 0;
            }
        } else {
            return ViewCompat.canScrollVertically(mTarget, -1);
        }
    }

    /**
     * @return Whether it is possible for the child view of this layout to
     *         scroll down. Override this if the child view is a custom view.
     */
    public boolean canChildScrollDown() {
        if (android.os.Build.VERSION.SDK_INT < 14) {
            if (mTarget instanceof AbsListView) {
                final AbsListView absListView = (AbsListView) mTarget;
                View lastChild = absListView.getChildAt(absListView.getChildCount() - 1);

                if (lastChild != null) {
                    if ((absListView.getLastVisiblePosition() == (absListView.getCount() - 1))
                            && lastChild.getBottom() >= absListView.getPaddingBottom()) {
                        return false;
                    }
                    return true;
                } else {
                    return false;
                }
            } else {
                return mTarget.getHeight() - mTarget.getScrollY() > 0;
            }
        } else {
            return ViewCompat.canScrollVertically(mTarget, 1);
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        try {
            ensureTarget();

            final int action = MotionEventCompat.getActionMasked(ev);

            if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
                mReturningToStart = false;
            }

            if (!isEnabled() || mReturningToStart) {
                // Fail fast if we're not in a state where a swipe is possible
                return false;
            }
            if (isRefreshing()) {
                return false;
            }

            if (mLoading) {
                return false;
            }

            switch (action) {
            case MotionEvent.ACTION_DOWN:
                mInitialMotionY = ev.getY();
                mInitialMotionX = ev.getX();
                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
                mIsBeingDragged = false;

                up = canChildScrollUp();
                down = canChildScrollDown();

                break;

            case MotionEvent.ACTION_MOVE:
                if (mActivePointerId == INVALID_POINTER) {
                    Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");
                    return false;
                }

                final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
                if (pointerIndex < 0) {
                    Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
                    return false;
                }
                final float x = MotionEventCompat.getX(ev, pointerIndex);
                final float y = MotionEventCompat.getY(ev, pointerIndex);
                final float yDiff = y - mInitialMotionY;

                final float adx = Math.abs(x - mInitialMotionX);
                final float ady = Math.abs(y - mInitialMotionY);

                if (up) { // ??
                    if (down) { // ??
                        return false;
                    } else {
                        if (pull2load) {
                            double angle = Math.atan(adx / ady);
                            if (-yDiff > mTouchSlop && (angle < trigger_angle)) { // 
                                direction = 1;
                                mIsBeingDragged = true;
                            }
                        }
                    }
                } else {
                    if (pull2refresh) {
                        double angle = Math.atan(adx / ady);
                        if (yDiff > mTouchSlop && (angle < trigger_angle)) {
                            direction = 0;
                            mIsBeingDragged = true;
                        }
                    }

                }

                break;

            case MotionEventCompat.ACTION_POINTER_UP:
                onSecondaryPointerUp(ev);
                break;

            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mIsBeingDragged = false;
                mActivePointerId = INVALID_POINTER;
                break;
            }

            return mIsBeingDragged;
        } catch (IllegalArgumentException e) {
            Log.e("error", e.getMessage());
            return false;
        }

    }

    @Override
    public void requestDisallowInterceptTouchEvent(boolean b) {
        // Nope.
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        try {
            final int action = MotionEventCompat.getActionMasked(ev);

            if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {

                mReturningToStart = false;
            }

            if (!isEnabled() || mReturningToStart) {
                // Fail fast if we're not in a state where a swipe is possible
                return false;
            }
            if (isRefreshing()) {
                return false;
            }

            if (mLoading) {
                return false;
            }

            switch (action) {
            case MotionEvent.ACTION_DOWN:
                mInitialMotionY = ev.getY();
                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
                mIsBeingDragged = false;

                up = canChildScrollUp();
                down = canChildScrollDown();
                break;

            case MotionEvent.ACTION_MOVE:

                final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
                if (pointerIndex < 0) {
                    Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
                    return false;
                }

                final float y = MotionEventCompat.getY(ev, pointerIndex);
                final float yDiff = y - mInitialMotionY;

                if (up) { // ??
                    if (down) { // ??
                        return false;
                    } else {
                        if (-yDiff > mTouchSlop) { // 
                            direction = 1;
                            mIsBeingDragged = true;
                        }
                    }
                } else {
                    if (yDiff > mTouchSlop) {
                        direction = 0;
                        mIsBeingDragged = true;
                    }
                }

                if (mIsBeingDragged) {
                    if (direction == 0) {
                        // User velocity passed min velocity; trigger a refresh
                        if (yDiff > mDistanceToTriggerSync) {
                            state = RELEASE_TO_REFRESH;
                            defaultText = releaseDownLabel;

                        } else {
                            state = PULL_TO_REFRESH;
                            defaultText = pullDownLabel;
                        }
                        if (latestRefreshTime.equals("")) {
                            latestRefreshTime = formatter.format(new Date());
                        }
                        setProgressBarProperty(mTarget.getTop());

                        updateContentOffsetTop((int) yDiff >> 1);
                    } else {
                        if (-yDiff > mDistanceToTriggerSync) {
                            state = RELEASE_TO_REFRESH;
                            defaultText = releaseUpLabel;
                        } else {
                            state = PULL_TO_REFRESH;
                            defaultText = pullUpLabel;
                        }
                        if (latestLoadTime.equals("")) {
                            latestLoadTime = formatter.format(new Date());
                        }
                        setProgressBarProperty(mTarget.getTop());
                        updateContentOffsetBottom((int) yDiff >> 1);
                    }
                }

                break;

            case MotionEventCompat.ACTION_POINTER_DOWN: {
                final int index = MotionEventCompat.getActionIndex(ev);
                mActivePointerId = MotionEventCompat.getPointerId(ev, index);
                break;
            }

            case MotionEventCompat.ACTION_POINTER_UP:
                onSecondaryPointerUp(ev);
                break;

            case MotionEvent.ACTION_UP:
                final int mPointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
                if (mPointerIndex < 0) {
                    Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
                    return false;
                }

                final float mY = MotionEventCompat.getY(ev, mPointerIndex);
                final float mYDiff = mY - mInitialMotionY;

                if (!mIsBeingDragged && mYDiff > mTouchSlop) {
                    mIsBeingDragged = true;
                }

                if (direction == 0) {
                    if (mIsBeingDragged) {
                        if (mYDiff > mDistanceToTriggerSync) {
                            // User movement passed distance; trigger a refresh
                            startRefresh();
                        } else {
                            mReturnToStartPosition.run();
                        }
                    }
                } else {
                    if (mIsBeingDragged) {
                        if (-mYDiff > mDistanceToTriggerSync) {
                            // User movement passed distance; trigger a refresh
                            startLoad();
                        } else {
                            mReturnToStartPosition.run();
                        }
                    }
                }

            case MotionEvent.ACTION_CANCEL:
                mIsBeingDragged = false;
                mActivePointerId = INVALID_POINTER;
                return false;
            }

            return true;
        } catch (IllegalArgumentException e) {
            Log.e("error", e.getMessage());
            return false;
        }

    }

    public void startRefresh() {
        removeCallbacks(mCancel);
        setRefreshing(true);
        mListener.onRefresh();
    }

    private void startLoad() {
        setLoading(true);
        mLoadListener.onLoad();
    }

    private void updateContentOffsetBottom(int targetBottom) {
        if (targetBottom > 0) {
            targetBottom = 0;
        }
        int offset = 0;
        if (lastDiff != 0) {
            offset = targetBottom - lastDiff;

        } else {
            offset = targetBottom;
        }

        lastDiff = targetBottom;

        setTargetOffsetTopAndBottom(offset);
    }

    private void updateContentOffsetTop(int targetTop) {
        final int currentTop = mTarget.getTop();
        if (targetTop < 0) {
            targetTop = 0;
        }

        setTargetOffsetTopAndBottom(targetTop - currentTop);
    }

    private void setTargetOffsetTopAndBottom(int offset) {
        mTarget.offsetTopAndBottom(offset);
        mCurrentTargetOffsetTop = mTarget.getTop();
    }

    private void onSecondaryPointerUp(MotionEvent ev) {
        final int pointerIndex = MotionEventCompat.getActionIndex(ev);
        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
        if (pointerId == mActivePointerId) {
            // This was our active pointer going up. Choose a new
            // active pointer and adjust accordingly.
            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
        }
    }

    /**
     * Classes that wish to be notified when the swipe gesture correctly
     * triggers a refresh should implement this interface.
     */
    public interface OnRefreshListener {
        public void onRefresh();
    }

    public interface OnLoadListener {
        public void onLoad();
    }

    /**
     * Simple AnimationListener to avoid having to implement unneeded methods in
     * AnimationListeners.
     */
    private class BaseAnimationListener implements AnimationListener {
        @Override
        public void onAnimationStart(Animation animation) {
        }

        @Override
        public void onAnimationEnd(Animation animation) {
        }

        @Override
        public void onAnimationRepeat(Animation animation) {
        }
    }

    public void setPull2load(boolean pull2load) {
        this.pull2load = pull2load;
    }

}