コード例 #1
0
ファイル: PtrFrameLayout.java プロジェクト: yqfs/AGOCAL
 private void performRefresh() {
   mLoadingStartTime = System.currentTimeMillis();
   if (mPtrUIHandlerHolder.hasHandler()) {
     mPtrUIHandlerHolder.onUIRefreshBegin(this);
     if (DEBUG) {
       PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIRefreshBegin");
     }
   }
   if (mPtrHandler != null) {
     mPtrHandler.onRefreshBegin(this);
   }
 }
コード例 #2
0
ファイル: PtrFrameLayout.java プロジェクト: yqfs/AGOCAL
 /** If at the top and not in loading, reset */
 private boolean tryToNotifyReset() {
   if ((mStatus == PTR_STATUS_COMPLETE || mStatus == PTR_STATUS_PREPARE)
       && mPtrIndicator.isInStartPosition()) {
     if (mPtrUIHandlerHolder.hasHandler()) {
       mPtrUIHandlerHolder.onUIReset(this);
       if (DEBUG) {
         PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIReset");
       }
     }
     mStatus = PTR_STATUS_INIT;
     clearFlag();
     return true;
   }
   return false;
 }
コード例 #3
0
ファイル: PtrFrameLayout.java プロジェクト: yqfs/AGOCAL
  public void autoRefresh(boolean atOnce, int duration) {

    if (mStatus != PTR_STATUS_INIT) {
      return;
    }

    mFlag |= atOnce ? FLAG_AUTO_REFRESH_AT_ONCE : FLAG_AUTO_REFRESH_BUT_LATER;

    mStatus = PTR_STATUS_PREPARE;
    if (mPtrUIHandlerHolder.hasHandler()) {
      mPtrUIHandlerHolder.onUIRefreshPrepare(this);
      if (DEBUG) {
        PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIRefreshPrepare, mFlag %s", mFlag);
      }
    }
    mScrollChecker.tryToScrollTo(mPtrIndicator.getOffsetToRefresh(), duration);
    if (atOnce) {
      mStatus = PTR_STATUS_LOADING;
      performRefresh();
    }
  }
コード例 #4
0
ファイル: PtrFrameLayout.java プロジェクト: yqfs/AGOCAL
  /**
   * Do real refresh work. If there is a hook, execute the hook first.
   *
   * @param ignoreHook
   */
  private void notifyUIRefreshComplete(boolean ignoreHook) {
    /**
     * After hook operation is done, {@link #notifyUIRefreshComplete} will be call in resume action
     * to ignore hook.
     */
    if (mPtrIndicator.hasLeftStartPosition() && !ignoreHook && mRefreshCompleteHook != null) {
      if (DEBUG) {
        PtrCLog.d(LOG_TAG, "notifyUIRefreshComplete mRefreshCompleteHook run.");
      }

      mRefreshCompleteHook.takeOver();
      return;
    }
    if (mPtrUIHandlerHolder.hasHandler()) {
      if (DEBUG) {
        PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIRefreshComplete");
      }
      mPtrUIHandlerHolder.onUIRefreshComplete(this);
    }
    mPtrIndicator.onUIRefreshComplete();
    tryScrollBackToTopAfterComplete();
    tryToNotifyReset();
  }
コード例 #5
0
ファイル: PtrFrameLayout.java プロジェクト: yqfs/AGOCAL
 @SuppressWarnings({"unused"})
 public void removePtrUIHandler(PtrUIHandler ptrUIHandler) {
   mPtrUIHandlerHolder = PtrUIHandlerHolder.removeHandler(mPtrUIHandlerHolder, ptrUIHandler);
 }
コード例 #6
0
ファイル: PtrFrameLayout.java プロジェクト: yqfs/AGOCAL
 public void addPtrUIHandler(PtrUIHandler ptrUIHandler) {
   PtrUIHandlerHolder.addHandler(mPtrUIHandlerHolder, ptrUIHandler);
 }
コード例 #7
0
ファイル: PtrFrameLayout.java プロジェクト: yqfs/AGOCAL
  private void updatePos(int change) {
    if (change == 0) {
      return;
    }

    boolean isUnderTouch = mPtrIndicator.isUnderTouch();

    // once moved, cancel event will be sent to child
    if (isUnderTouch && !mHasSendCancelEvent && mPtrIndicator.hasMovedAfterPressedDown()) {
      mHasSendCancelEvent = true;
      sendCancelEvent();
    }

    // leave initiated position or just refresh complete
    if ((mPtrIndicator.hasJustLeftStartPosition() && mStatus == PTR_STATUS_INIT)
        || (mPtrIndicator.goDownCrossFinishPosition()
            && mStatus == PTR_STATUS_COMPLETE
            && isEnabledNextPtrAtOnce())) {

      mStatus = PTR_STATUS_PREPARE;
      mPtrUIHandlerHolder.onUIRefreshPrepare(this);
      if (DEBUG) {
        PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIRefreshPrepare, mFlag %s", mFlag);
      }
    }

    // back to initiated position
    if (mPtrIndicator.hasJustBackToStartPosition()) {
      tryToNotifyReset();

      // recover event to children
      if (isUnderTouch) {
        sendDownEvent();
      }
    }

    // Pull to Refresh
    if (mStatus == PTR_STATUS_PREPARE) {
      // reach fresh height while moving from top to bottom
      if (isUnderTouch
          && !isAutoRefresh()
          && mPullToRefresh
          && mPtrIndicator.crossRefreshLineFromTopToBottom()) {
        tryToPerformRefresh();
      }
      // reach header height while auto refresh
      if (performAutoRefreshButLater()
          && mPtrIndicator.hasJustReachedHeaderHeightFromTopToBottom()) {
        tryToPerformRefresh();
      }
    }

    if (DEBUG) {
      PtrCLog.v(
          LOG_TAG,
          "updatePos: change: %s, current: %s last: %s, top: %s, headerHeight: %s",
          change,
          mPtrIndicator.getCurrentPosY(),
          mPtrIndicator.getLastPosY(),
          mContent.getTop(),
          mHeaderHeight);
    }

    mHeaderView.offsetTopAndBottom(change);
    if (!isPinContent()) {
      mContent.offsetTopAndBottom(change);
    }
    invalidate();

    if (mPtrUIHandlerHolder.hasHandler()) {
      mPtrUIHandlerHolder.onUIPositionChange(this, isUnderTouch, mStatus, mPtrIndicator);
    }
    onPositionChange(isUnderTouch, mStatus, mPtrIndicator);
  }
コード例 #8
0
ファイル: PtrFrameLayout.java プロジェクト: yqfs/AGOCAL
/**
 * This layout view for "Pull to Refresh(Ptr)" support all of the view, you can contain everything
 * you want. support: pull to refresh / release to refresh / auto refresh / keep header view while
 * refreshing / hide header view while refreshing It defines {@link
 * in.srain.cube.views.ptr.PtrUIHandler}, which allows you customize the UI easily.
 */
public class PtrFrameLayout extends ViewGroup {

  // status enum
  public static final byte PTR_STATUS_INIT = 1;
  public static final byte PTR_STATUS_PREPARE = 2;
  public static final byte PTR_STATUS_LOADING = 3;
  public static final byte PTR_STATUS_COMPLETE = 4;

  private static final boolean DEBUG_LAYOUT = true;
  public static boolean DEBUG = false;
  private static int ID = 1;

  // auto refresh status
  private static byte FLAG_AUTO_REFRESH_AT_ONCE = 0x01;
  private static byte FLAG_AUTO_REFRESH_BUT_LATER = 0x01 << 1;
  private static byte FLAG_ENABLE_NEXT_PTR_AT_ONCE = 0x01 << 2;
  private static byte FLAG_PIN_CONTENT = 0x01 << 3;

  private static byte MASK_AUTO_REFRESH = 0x03;

  protected final String LOG_TAG = "ptr-frame-" + ++ID;
  protected View mContent;
  // optional config for define header and content in xml file
  private int mHeaderId = 0;
  private int mContainerId = 0;
  // config
  private int mDurationToClose = 200;
  private int mDurationToCloseHeader = 1000;
  private boolean mKeepHeaderWhenRefresh = true;
  private boolean mPullToRefresh = false;
  private View mHeaderView;
  private PtrUIHandlerHolder mPtrUIHandlerHolder = PtrUIHandlerHolder.create();
  private PtrHandler mPtrHandler;
  // working parameters
  private ScrollChecker mScrollChecker;
  private int mPagingTouchSlop;
  private int mHeaderHeight;

  private byte mStatus = PTR_STATUS_INIT;
  private boolean mDisableWhenHorizontalMove = false;
  private int mFlag = 0x00;

  // disable when detect moving horizontally
  private boolean mPreventForHorizontal = false;

  private MotionEvent mLastMoveEvent;

  private PtrUIHandlerHook mRefreshCompleteHook;

  private int mLoadingMinTime = 500;
  private long mLoadingStartTime = 0;
  private PtrIndicator mPtrIndicator;
  private boolean mHasSendCancelEvent = false;

  public PtrFrameLayout(Context context) {
    this(context, null);
  }

  public PtrFrameLayout(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }

  public PtrFrameLayout(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);

    mPtrIndicator = new PtrIndicator();

    TypedArray arr = context.obtainStyledAttributes(attrs, R.styleable.PtrFrameLayout, 0, 0);
    if (arr != null) {

      mHeaderId = arr.getResourceId(R.styleable.PtrFrameLayout_ptr_header, mHeaderId);
      mContainerId = arr.getResourceId(R.styleable.PtrFrameLayout_ptr_content, mContainerId);

      mPtrIndicator.setResistance(
          arr.getFloat(R.styleable.PtrFrameLayout_ptr_resistance, mPtrIndicator.getResistance()));

      mDurationToClose =
          arr.getInt(R.styleable.PtrFrameLayout_ptr_duration_to_close, mDurationToClose);
      mDurationToCloseHeader =
          arr.getInt(
              R.styleable.PtrFrameLayout_ptr_duration_to_close_header, mDurationToCloseHeader);

      float ratio = mPtrIndicator.getRatioOfHeaderToHeightRefresh();
      ratio = arr.getFloat(R.styleable.PtrFrameLayout_ptr_ratio_of_header_height_to_refresh, ratio);
      mPtrIndicator.setRatioOfHeaderHeightToRefresh(ratio);

      mKeepHeaderWhenRefresh =
          arr.getBoolean(
              R.styleable.PtrFrameLayout_ptr_keep_header_when_refresh, mKeepHeaderWhenRefresh);

      mPullToRefresh = arr.getBoolean(R.styleable.PtrFrameLayout_ptr_pull_to_fresh, mPullToRefresh);
      arr.recycle();
    }

    mScrollChecker = new ScrollChecker();

    final ViewConfiguration conf = ViewConfiguration.get(getContext());
    mPagingTouchSlop = conf.getScaledTouchSlop() * 2;
  }

  @Override
  protected void onFinishInflate() {
    final int childCount = getChildCount();
    if (childCount > 2) {
      throw new IllegalStateException("PtrFrameLayout only can host 2 elements");
    } else if (childCount == 2) {
      if (mHeaderId != 0 && mHeaderView == null) {
        mHeaderView = findViewById(mHeaderId);
      }
      if (mContainerId != 0 && mContent == null) {
        mContent = findViewById(mContainerId);
      }

      // not specify header or content
      if (mContent == null || mHeaderView == null) {

        View child1 = getChildAt(0);
        View child2 = getChildAt(1);
        if (child1 instanceof PtrUIHandler) {
          mHeaderView = child1;
          mContent = child2;
        } else if (child2 instanceof PtrUIHandler) {
          mHeaderView = child2;
          mContent = child1;
        } else {
          // both are not specified
          if (mContent == null && mHeaderView == null) {
            mHeaderView = child1;
            mContent = child2;
          }
          // only one is specified
          else {
            if (mHeaderView == null) {
              mHeaderView = mContent == child1 ? child2 : child1;
            } else {
              mContent = mHeaderView == child1 ? child2 : child1;
            }
          }
        }
      }
    } else if (childCount == 1) {
      mContent = getChildAt(0);
    } else {
      TextView errorView = new TextView(getContext());
      errorView.setClickable(true);
      errorView.setTextColor(0xffff6600);
      errorView.setGravity(Gravity.CENTER);
      errorView.setTextSize(20);
      errorView.setText(
          "The content view in PtrFrameLayout is empty. Do you forget to specify its id in xml layout file?");
      mContent = errorView;
      addView(mContent);
    }
    if (mHeaderView != null) {
      mHeaderView.bringToFront();
    }
    super.onFinishInflate();
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    if (DEBUG && DEBUG_LAYOUT) {
      PtrCLog.d(
          LOG_TAG,
          "onMeasure frame: width: %s, height: %s, padding: %s %s %s %s",
          getMeasuredHeight(),
          getMeasuredWidth(),
          getPaddingLeft(),
          getPaddingRight(),
          getPaddingTop(),
          getPaddingBottom());
    }

    if (mHeaderView != null) {
      measureChildWithMargins(mHeaderView, widthMeasureSpec, 0, heightMeasureSpec, 0);
      MarginLayoutParams lp = (MarginLayoutParams) mHeaderView.getLayoutParams();
      mHeaderHeight = mHeaderView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
      mPtrIndicator.setHeaderHeight(mHeaderHeight);
    }

    if (mContent != null) {
      measureContentView(mContent, widthMeasureSpec, heightMeasureSpec);
      if (DEBUG && DEBUG_LAYOUT) {
        ViewGroup.MarginLayoutParams lp = (MarginLayoutParams) mContent.getLayoutParams();
        PtrCLog.d(
            LOG_TAG,
            "onMeasure content, width: %s, height: %s, margin: %s %s %s %s",
            getMeasuredWidth(),
            getMeasuredHeight(),
            lp.leftMargin,
            lp.topMargin,
            lp.rightMargin,
            lp.bottomMargin);
        PtrCLog.d(
            LOG_TAG,
            "onMeasure, currentPos: %s, lastPos: %s, top: %s",
            mPtrIndicator.getCurrentPosY(),
            mPtrIndicator.getLastPosY(),
            mContent.getTop());
      }
    }
  }

  private void measureContentView(
      View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

    final int childWidthMeasureSpec =
        getChildMeasureSpec(
            parentWidthMeasureSpec,
            getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin,
            lp.width);
    final int childHeightMeasureSpec =
        getChildMeasureSpec(
            parentHeightMeasureSpec,
            getPaddingTop() + getPaddingBottom() + lp.topMargin,
            lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  }

  @Override
  protected void onLayout(boolean flag, int i, int j, int k, int l) {
    layoutChildren();
  }

  private void layoutChildren() {
    int offsetX = mPtrIndicator.getCurrentPosY();
    int paddingLeft = getPaddingLeft();
    int paddingTop = getPaddingTop();

    if (mHeaderView != null) {
      MarginLayoutParams lp = (MarginLayoutParams) mHeaderView.getLayoutParams();
      final int left = paddingLeft + lp.leftMargin;
      final int top = paddingTop + lp.topMargin + offsetX - mHeaderHeight;
      final int right = left + mHeaderView.getMeasuredWidth();
      final int bottom = top + mHeaderView.getMeasuredHeight();
      mHeaderView.layout(left, top, right, bottom);
      if (DEBUG && DEBUG_LAYOUT) {
        PtrCLog.d(LOG_TAG, "onLayout header: %s %s %s %s", left, top, right, bottom);
      }
    }
    if (mContent != null) {
      if (isPinContent()) {
        offsetX = 0;
      }
      MarginLayoutParams lp = (MarginLayoutParams) mContent.getLayoutParams();
      final int left = paddingLeft + lp.leftMargin;
      final int top = paddingTop + lp.topMargin + offsetX;
      final int right = left + mContent.getMeasuredWidth();
      final int bottom = top + mContent.getMeasuredHeight();
      if (DEBUG && DEBUG_LAYOUT) {
        PtrCLog.d(LOG_TAG, "onLayout content: %s %s %s %s", left, top, right, bottom);
      }
      mContent.layout(left, top, right, bottom);
    }
  }

  public boolean dispatchTouchEventSupper(MotionEvent e) {
    return super.dispatchTouchEvent(e);
  }

  @Override
  public boolean dispatchTouchEvent(MotionEvent e) {
    if (!isEnabled() || mContent == null || mHeaderView == null) {
      return dispatchTouchEventSupper(e);
    }
    int action = e.getAction();
    switch (action) {
      case MotionEvent.ACTION_UP:
      case MotionEvent.ACTION_CANCEL:
        mPtrIndicator.onRelease();
        if (mPtrIndicator.hasLeftStartPosition()) {
          if (DEBUG) {
            PtrCLog.d(LOG_TAG, "call onRelease when user release");
          }
          onRelease(false);
          if (mPtrIndicator.hasMovedAfterPressedDown()) {
            sendCancelEvent();
            return true;
          }
          return dispatchTouchEventSupper(e);
        } else {
          return dispatchTouchEventSupper(e);
        }

      case MotionEvent.ACTION_DOWN:
        mHasSendCancelEvent = false;
        mPtrIndicator.onPressDown(e.getX(), e.getY());

        mScrollChecker.abortIfWorking();

        mPreventForHorizontal = false;
        // The cancel event will be sent once the position is moved.
        // So let the event pass to children.
        // fix #93, #102
        dispatchTouchEventSupper(e);
        return true;

      case MotionEvent.ACTION_MOVE:
        mLastMoveEvent = e;
        mPtrIndicator.onMove(e.getX(), e.getY());
        float offsetX = mPtrIndicator.getOffsetX();
        float offsetY = mPtrIndicator.getOffsetY();

        if (mDisableWhenHorizontalMove
            && !mPreventForHorizontal
            && (Math.abs(offsetX) > mPagingTouchSlop && Math.abs(offsetX) > Math.abs(offsetY))) {
          if (mPtrIndicator.isInStartPosition()) {
            mPreventForHorizontal = true;
          }
        }
        if (mPreventForHorizontal) {
          return dispatchTouchEventSupper(e);
        }

        boolean moveDown = offsetY > 0;
        boolean moveUp = !moveDown;
        boolean canMoveUp = mPtrIndicator.hasLeftStartPosition();

        if (DEBUG) {
          boolean canMoveDown =
              mPtrHandler != null && mPtrHandler.checkCanDoRefresh(this, mContent, mHeaderView);
          PtrCLog.v(
              LOG_TAG,
              "ACTION_MOVE: offsetY:%s, currentPos: %s, moveUp: %s, canMoveUp: %s, moveDown: %s: canMoveDown: %s",
              offsetY,
              mPtrIndicator.getCurrentPosY(),
              moveUp,
              canMoveUp,
              moveDown,
              canMoveDown);
        }

        // disable move when header not reach top
        if (moveDown
            && mPtrHandler != null
            && !mPtrHandler.checkCanDoRefresh(this, mContent, mHeaderView)) {
          return dispatchTouchEventSupper(e);
        }

        if ((moveUp && canMoveUp) || moveDown) {
          movePos(offsetY);
          return true;
        }
    }
    return dispatchTouchEventSupper(e);
  }

  /**
   * if deltaY > 0, move the content down
   *
   * @param deltaY
   */
  private void movePos(float deltaY) {
    // has reached the top
    if ((deltaY < 0 && mPtrIndicator.isInStartPosition())) {
      if (DEBUG) {
        PtrCLog.e(LOG_TAG, String.format("has reached the top"));
      }
      return;
    }

    int to = mPtrIndicator.getCurrentPosY() + (int) deltaY;

    // over top
    if (mPtrIndicator.willOverTop(to)) {
      if (DEBUG) {
        PtrCLog.e(LOG_TAG, String.format("over top"));
      }
      to = PtrIndicator.POS_START;
    }

    mPtrIndicator.setCurrentPos(to);
    int change = to - mPtrIndicator.getLastPosY();
    updatePos(change);
  }

  private void updatePos(int change) {
    if (change == 0) {
      return;
    }

    boolean isUnderTouch = mPtrIndicator.isUnderTouch();

    // once moved, cancel event will be sent to child
    if (isUnderTouch && !mHasSendCancelEvent && mPtrIndicator.hasMovedAfterPressedDown()) {
      mHasSendCancelEvent = true;
      sendCancelEvent();
    }

    // leave initiated position or just refresh complete
    if ((mPtrIndicator.hasJustLeftStartPosition() && mStatus == PTR_STATUS_INIT)
        || (mPtrIndicator.goDownCrossFinishPosition()
            && mStatus == PTR_STATUS_COMPLETE
            && isEnabledNextPtrAtOnce())) {

      mStatus = PTR_STATUS_PREPARE;
      mPtrUIHandlerHolder.onUIRefreshPrepare(this);
      if (DEBUG) {
        PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIRefreshPrepare, mFlag %s", mFlag);
      }
    }

    // back to initiated position
    if (mPtrIndicator.hasJustBackToStartPosition()) {
      tryToNotifyReset();

      // recover event to children
      if (isUnderTouch) {
        sendDownEvent();
      }
    }

    // Pull to Refresh
    if (mStatus == PTR_STATUS_PREPARE) {
      // reach fresh height while moving from top to bottom
      if (isUnderTouch
          && !isAutoRefresh()
          && mPullToRefresh
          && mPtrIndicator.crossRefreshLineFromTopToBottom()) {
        tryToPerformRefresh();
      }
      // reach header height while auto refresh
      if (performAutoRefreshButLater()
          && mPtrIndicator.hasJustReachedHeaderHeightFromTopToBottom()) {
        tryToPerformRefresh();
      }
    }

    if (DEBUG) {
      PtrCLog.v(
          LOG_TAG,
          "updatePos: change: %s, current: %s last: %s, top: %s, headerHeight: %s",
          change,
          mPtrIndicator.getCurrentPosY(),
          mPtrIndicator.getLastPosY(),
          mContent.getTop(),
          mHeaderHeight);
    }

    mHeaderView.offsetTopAndBottom(change);
    if (!isPinContent()) {
      mContent.offsetTopAndBottom(change);
    }
    invalidate();

    if (mPtrUIHandlerHolder.hasHandler()) {
      mPtrUIHandlerHolder.onUIPositionChange(this, isUnderTouch, mStatus, mPtrIndicator);
    }
    onPositionChange(isUnderTouch, mStatus, mPtrIndicator);
  }

  protected void onPositionChange(boolean isInTouching, byte status, PtrIndicator mPtrIndicator) {}

  @SuppressWarnings("unused")
  public int getHeaderHeight() {
    return mHeaderHeight;
  }

  private void onRelease(boolean stayForLoading) {

    tryToPerformRefresh();

    if (mStatus == PTR_STATUS_LOADING) {
      // keep header for fresh
      if (mKeepHeaderWhenRefresh) {
        // scroll header back
        if (mPtrIndicator.isOverOffsetToKeepHeaderWhileLoading() && !stayForLoading) {
          mScrollChecker.tryToScrollTo(
              mPtrIndicator.getOffsetToKeepHeaderWhileLoading(), mDurationToClose);
        } else {
          // do nothing
        }
      } else {
        tryScrollBackToTopWhileLoading();
      }
    } else {
      if (mStatus == PTR_STATUS_COMPLETE) {
        notifyUIRefreshComplete(false);
      } else {
        tryScrollBackToTopAbortRefresh();
      }
    }
  }

  /**
   * please DO REMEMBER resume the hook
   *
   * @param hook
   */
  public void setRefreshCompleteHook(PtrUIHandlerHook hook) {
    mRefreshCompleteHook = hook;
    hook.setResumeAction(
        new Runnable() {
          @Override
          public void run() {
            if (DEBUG) {
              PtrCLog.d(LOG_TAG, "mRefreshCompleteHook resume.");
            }
            notifyUIRefreshComplete(true);
          }
        });
  }

  /** Scroll back to to if is not under touch */
  private void tryScrollBackToTop() {
    if (!mPtrIndicator.isUnderTouch()) {
      mScrollChecker.tryToScrollTo(PtrIndicator.POS_START, mDurationToCloseHeader);
    }
  }

  /** just make easier to understand */
  private void tryScrollBackToTopWhileLoading() {
    tryScrollBackToTop();
  }

  /** just make easier to understand */
  private void tryScrollBackToTopAfterComplete() {
    tryScrollBackToTop();
  }

  /** just make easier to understand */
  private void tryScrollBackToTopAbortRefresh() {
    tryScrollBackToTop();
  }

  private boolean tryToPerformRefresh() {
    if (mStatus != PTR_STATUS_PREPARE) {
      return false;
    }
    // 网络不可用时,不能下拉刷新
    if (!NetWorkUtil.isNetworkAvailable(getContext())) {
      return false;
    }

    //
    if ((mPtrIndicator.isOverOffsetToKeepHeaderWhileLoading() && isAutoRefresh())
        || mPtrIndicator.isOverOffsetToRefresh()) {
      mStatus = PTR_STATUS_LOADING;
      performRefresh();
    }
    return false;
  }

  private void performRefresh() {
    mLoadingStartTime = System.currentTimeMillis();
    if (mPtrUIHandlerHolder.hasHandler()) {
      mPtrUIHandlerHolder.onUIRefreshBegin(this);
      if (DEBUG) {
        PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIRefreshBegin");
      }
    }
    if (mPtrHandler != null) {
      mPtrHandler.onRefreshBegin(this);
    }
  }

  /** If at the top and not in loading, reset */
  private boolean tryToNotifyReset() {
    if ((mStatus == PTR_STATUS_COMPLETE || mStatus == PTR_STATUS_PREPARE)
        && mPtrIndicator.isInStartPosition()) {
      if (mPtrUIHandlerHolder.hasHandler()) {
        mPtrUIHandlerHolder.onUIReset(this);
        if (DEBUG) {
          PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIReset");
        }
      }
      mStatus = PTR_STATUS_INIT;
      clearFlag();
      return true;
    }
    return false;
  }

  protected void onPtrScrollAbort() {
    if (mPtrIndicator.hasLeftStartPosition() && isAutoRefresh()) {
      if (DEBUG) {
        PtrCLog.d(LOG_TAG, "call onRelease after scroll abort");
      }
      onRelease(true);
    }
  }

  protected void onPtrScrollFinish() {
    if (mPtrIndicator.hasLeftStartPosition() && isAutoRefresh()) {
      if (DEBUG) {
        PtrCLog.d(LOG_TAG, "call onRelease after scroll finish");
      }
      onRelease(true);
    }
  }

  /**
   * Detect whether is refreshing.
   *
   * @return
   */
  public boolean isRefreshing() {
    return mStatus == PTR_STATUS_LOADING;
  }

  /**
   * Call this when data is loaded. The UI will perform complete at once or after a delay, depends
   * on the time elapsed is greater then {@link #mLoadingMinTime} or not.
   */
  public final void refreshComplete() {
    if (DEBUG) {
      PtrCLog.i(LOG_TAG, "refreshComplete");
    }

    if (mRefreshCompleteHook != null) {
      mRefreshCompleteHook.reset();
    }

    int delay = (int) (mLoadingMinTime - (System.currentTimeMillis() - mLoadingStartTime));
    if (delay <= 0) {
      if (DEBUG) {
        PtrCLog.d(LOG_TAG, "performRefreshComplete at once");
      }
      performRefreshComplete();
    } else {
      postDelayed(
          new Runnable() {
            @Override
            public void run() {
              performRefreshComplete();
            }
          },
          delay);
      if (DEBUG) {
        PtrCLog.d(LOG_TAG, "performRefreshComplete after delay: %s", delay);
      }
    }
  }

  /** Do refresh complete work when time elapsed is greater than {@link #mLoadingMinTime} */
  private void performRefreshComplete() {
    mStatus = PTR_STATUS_COMPLETE;

    // if is auto refresh do nothing, wait scroller stop
    if (mScrollChecker.mIsRunning && isAutoRefresh()) {
      // do nothing
      if (DEBUG) {
        PtrCLog.d(
            LOG_TAG,
            "performRefreshComplete do nothing, scrolling: %s, auto refresh: %s",
            mScrollChecker.mIsRunning,
            mFlag);
      }
      return;
    }

    notifyUIRefreshComplete(false);
  }

  /**
   * Do real refresh work. If there is a hook, execute the hook first.
   *
   * @param ignoreHook
   */
  private void notifyUIRefreshComplete(boolean ignoreHook) {
    /**
     * After hook operation is done, {@link #notifyUIRefreshComplete} will be call in resume action
     * to ignore hook.
     */
    if (mPtrIndicator.hasLeftStartPosition() && !ignoreHook && mRefreshCompleteHook != null) {
      if (DEBUG) {
        PtrCLog.d(LOG_TAG, "notifyUIRefreshComplete mRefreshCompleteHook run.");
      }

      mRefreshCompleteHook.takeOver();
      return;
    }
    if (mPtrUIHandlerHolder.hasHandler()) {
      if (DEBUG) {
        PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIRefreshComplete");
      }
      mPtrUIHandlerHolder.onUIRefreshComplete(this);
    }
    mPtrIndicator.onUIRefreshComplete();
    tryScrollBackToTopAfterComplete();
    tryToNotifyReset();
  }

  public void autoRefresh() {
    autoRefresh(true, mDurationToCloseHeader);
  }

  public void autoRefresh(boolean atOnce) {
    autoRefresh(atOnce, mDurationToCloseHeader);
  }

  private void clearFlag() {
    // remove auto fresh flag
    mFlag = mFlag & ~MASK_AUTO_REFRESH;
  }

  public void autoRefresh(boolean atOnce, int duration) {

    if (mStatus != PTR_STATUS_INIT) {
      return;
    }

    mFlag |= atOnce ? FLAG_AUTO_REFRESH_AT_ONCE : FLAG_AUTO_REFRESH_BUT_LATER;

    mStatus = PTR_STATUS_PREPARE;
    if (mPtrUIHandlerHolder.hasHandler()) {
      mPtrUIHandlerHolder.onUIRefreshPrepare(this);
      if (DEBUG) {
        PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIRefreshPrepare, mFlag %s", mFlag);
      }
    }
    mScrollChecker.tryToScrollTo(mPtrIndicator.getOffsetToRefresh(), duration);
    if (atOnce) {
      mStatus = PTR_STATUS_LOADING;
      performRefresh();
    }
  }

  public boolean isAutoRefresh() {
    return (mFlag & MASK_AUTO_REFRESH) > 0;
  }

  private boolean performAutoRefreshButLater() {
    return (mFlag & MASK_AUTO_REFRESH) == FLAG_AUTO_REFRESH_BUT_LATER;
  }

  /**
   * If @param enable has been set to true. The user can perform next PTR at once.
   *
   * @param enable
   */
  public void setEnabledNextPtrAtOnce(boolean enable) {
    if (enable) {
      mFlag = mFlag | FLAG_ENABLE_NEXT_PTR_AT_ONCE;
    } else {
      mFlag = mFlag & ~FLAG_ENABLE_NEXT_PTR_AT_ONCE;
    }
  }

  public boolean isEnabledNextPtrAtOnce() {
    return (mFlag & FLAG_ENABLE_NEXT_PTR_AT_ONCE) > 0;
  }

  /**
   * The content view will now move when {@param pinContent} set to true.
   *
   * @param pinContent
   */
  public void setPinContent(boolean pinContent) {
    if (pinContent) {
      mFlag = mFlag | FLAG_PIN_CONTENT;
    } else {
      mFlag = mFlag & ~FLAG_PIN_CONTENT;
    }
  }

  public boolean isPinContent() {
    return (mFlag & FLAG_PIN_CONTENT) > 0;
  }

  /**
   * It's useful when working with viewpager.
   *
   * @param disable
   */
  public void disableWhenHorizontalMove(boolean disable) {
    mDisableWhenHorizontalMove = disable;
  }

  /**
   * loading will last at least for so long
   *
   * @param time
   */
  public void setLoadingMinTime(int time) {
    mLoadingMinTime = time;
  }

  /**
   * Not necessary any longer. Once moved, cancel event will be sent to child.
   *
   * @param yes
   */
  @Deprecated
  public void setInterceptEventWhileWorking(boolean yes) {}

  @SuppressWarnings({"unused"})
  public View getContentView() {
    return mContent;
  }

  public void setPtrHandler(PtrHandler ptrHandler) {
    mPtrHandler = ptrHandler;
  }

  public void addPtrUIHandler(PtrUIHandler ptrUIHandler) {
    PtrUIHandlerHolder.addHandler(mPtrUIHandlerHolder, ptrUIHandler);
  }

  @SuppressWarnings({"unused"})
  public void removePtrUIHandler(PtrUIHandler ptrUIHandler) {
    mPtrUIHandlerHolder = PtrUIHandlerHolder.removeHandler(mPtrUIHandlerHolder, ptrUIHandler);
  }

  public void setPtrIndicator(PtrIndicator slider) {
    if (mPtrIndicator != null && mPtrIndicator != slider) {
      slider.convertFrom(mPtrIndicator);
    }
    mPtrIndicator = slider;
  }

  @SuppressWarnings({"unused"})
  public float getResistance() {
    return mPtrIndicator.getResistance();
  }

  public void setResistance(float resistance) {
    mPtrIndicator.setResistance(resistance);
  }

  @SuppressWarnings({"unused"})
  public float getDurationToClose() {
    return mDurationToClose;
  }

  /**
   * The duration to return back to the refresh position
   *
   * @param duration
   */
  public void setDurationToClose(int duration) {
    mDurationToClose = duration;
  }

  @SuppressWarnings({"unused"})
  public long getDurationToCloseHeader() {
    return mDurationToCloseHeader;
  }

  /**
   * The duration to close time
   *
   * @param duration
   */
  public void setDurationToCloseHeader(int duration) {
    mDurationToCloseHeader = duration;
  }

  public void setRatioOfHeaderHeightToRefresh(float ratio) {
    mPtrIndicator.setRatioOfHeaderHeightToRefresh(ratio);
  }

  public int getOffsetToRefresh() {
    return mPtrIndicator.getOffsetToRefresh();
  }

  @SuppressWarnings({"unused"})
  public void setOffsetToRefresh(int offset) {
    mPtrIndicator.setOffsetToRefresh(offset);
  }

  @SuppressWarnings({"unused"})
  public float getRatioOfHeaderToHeightRefresh() {
    return mPtrIndicator.getRatioOfHeaderToHeightRefresh();
  }

  @SuppressWarnings({"unused"})
  public void setOffsetToKeepHeaderWhileLoading(int offset) {
    mPtrIndicator.setOffsetToKeepHeaderWhileLoading(offset);
  }

  @SuppressWarnings({"unused"})
  public int getOffsetToKeepHeaderWhileLoading() {
    return mPtrIndicator.getOffsetToKeepHeaderWhileLoading();
  }

  @SuppressWarnings({"unused"})
  public boolean isKeepHeaderWhenRefresh() {
    return mKeepHeaderWhenRefresh;
  }

  public void setKeepHeaderWhenRefresh(boolean keepOrNot) {
    mKeepHeaderWhenRefresh = keepOrNot;
  }

  public boolean isPullToRefresh() {
    return mPullToRefresh;
  }

  public void setPullToRefresh(boolean pullToRefresh) {
    mPullToRefresh = pullToRefresh;
  }

  @SuppressWarnings({"unused"})
  public View getHeaderView() {
    return mHeaderView;
  }

  public void setHeaderView(View header) {
    if (mHeaderView != null && header != null && mHeaderView != header) {
      removeView(mHeaderView);
    }
    ViewGroup.LayoutParams lp = header.getLayoutParams();
    if (lp == null) {
      lp = new LayoutParams(-1, -2);
      header.setLayoutParams(lp);
    }
    mHeaderView = header;
    addView(header);
  }

  @Override
  protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    return p instanceof LayoutParams;
  }

  @Override
  protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
    return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
  }

  @Override
  protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
    return new LayoutParams(p);
  }

  @Override
  public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
    return new LayoutParams(getContext(), attrs);
  }

  private void sendCancelEvent() {
    if (DEBUG) {
      PtrCLog.d(LOG_TAG, "send cancel event");
    }
    // The ScrollChecker will update position and lead to send cancel event when mLastMoveEvent is
    // null.
    // fix #104, #80, #92
    if (mLastMoveEvent == null) {
      return;
    }
    MotionEvent last = mLastMoveEvent;
    MotionEvent e =
        MotionEvent.obtain(
            last.getDownTime(),
            last.getEventTime() + ViewConfiguration.getLongPressTimeout(),
            MotionEvent.ACTION_CANCEL,
            last.getX(),
            last.getY(),
            last.getMetaState());
    dispatchTouchEventSupper(e);
  }

  private void sendDownEvent() {
    if (DEBUG) {
      PtrCLog.d(LOG_TAG, "send down event");
    }
    final MotionEvent last = mLastMoveEvent;
    MotionEvent e =
        MotionEvent.obtain(
            last.getDownTime(),
            last.getEventTime(),
            MotionEvent.ACTION_DOWN,
            last.getX(),
            last.getY(),
            last.getMetaState());
    dispatchTouchEventSupper(e);
  }

  public static class LayoutParams extends MarginLayoutParams {

    public LayoutParams(Context c, AttributeSet attrs) {
      super(c, attrs);
    }

    public LayoutParams(int width, int height) {
      super(width, height);
    }

    @SuppressWarnings({"unused"})
    public LayoutParams(MarginLayoutParams source) {
      super(source);
    }

    public LayoutParams(ViewGroup.LayoutParams source) {
      super(source);
    }
  }

  class ScrollChecker implements Runnable {

    private int mLastFlingY;
    private Scroller mScroller;
    private boolean mIsRunning = false;
    private int mStart;
    private int mTo;

    public ScrollChecker() {
      mScroller = new Scroller(getContext());
    }

    public void run() {
      boolean finish = !mScroller.computeScrollOffset() || mScroller.isFinished();
      int curY = mScroller.getCurrY();
      int deltaY = curY - mLastFlingY;
      if (DEBUG) {
        if (deltaY != 0) {
          PtrCLog.v(
              LOG_TAG,
              "scroll: %s, start: %s, to: %s, currentPos: %s, current :%s, last: %s, delta: %s",
              finish,
              mStart,
              mTo,
              mPtrIndicator.getCurrentPosY(),
              curY,
              mLastFlingY,
              deltaY);
        }
      }
      if (!finish) {
        mLastFlingY = curY;
        movePos(deltaY);
        post(this);
      } else {
        finish();
      }
    }

    private void finish() {
      if (DEBUG) {
        PtrCLog.v(LOG_TAG, "finish, currentPos:%s", mPtrIndicator.getCurrentPosY());
      }
      reset();
      onPtrScrollFinish();
    }

    private void reset() {
      mIsRunning = false;
      mLastFlingY = 0;
      removeCallbacks(this);
    }

    public void abortIfWorking() {
      if (mIsRunning) {
        if (!mScroller.isFinished()) {
          mScroller.forceFinished(true);
        }
        onPtrScrollAbort();
        reset();
      }
    }

    public void tryToScrollTo(int to, int duration) {
      if (mPtrIndicator.isAlreadyHere(to)) {
        return;
      }
      mStart = mPtrIndicator.getCurrentPosY();
      mTo = to;
      int distance = to - mStart;
      if (DEBUG) {
        PtrCLog.d(LOG_TAG, "tryToScrollTo: start: %s, distance:%s, to:%s", mStart, distance, to);
      }
      removeCallbacks(this);

      mLastFlingY = 0;

      // fix #47: Scroller should be reused,
      // https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh/issues/47
      if (!mScroller.isFinished()) {
        mScroller.forceFinished(true);
      }
      mScroller.startScroll(0, 0, 0, distance, duration);
      post(this);
      mIsRunning = true;
    }
  }
}