content: package com.hzl.pulltorefresh.refresh;
  
  import android.content.Context;
  import android.util.AttributeSet;
  import android.view.Gravity;
  import android.view.MotionEvent;
  import android.view.View;
  import android.view.ViewConfiguration;
  import android.view.ViewGroup;
  import android.widget.Scroller;
  import android.widget.TextView;
  
  import com.facebook.react.uimanager.events.NativeGestureUtil;
  import com.facebook.react.views.view.ReactViewGroup;
  import com.hzl.pulltorefresh.refresh.indicator.PtrIndicator;
  import com.hzl.pulltorefresh.refresh.util.PtrCLog;
  
  
  /**
   * 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 PtrUIHandler}, which allows you customize the UI easily.
   */
  public class PtrFrameLayout extends ReactViewGroup {
  
      // status enum
      public final static byte PTR_STATUS_INIT = 1;
      private byte mStatus = PTR_STATUS_INIT;
      public final static byte PTR_STATUS_PREPARE = 2;
      public final static byte PTR_STATUS_LOADING = 3;
      public final static byte PTR_STATUS_COMPLETE = 4;
      private static final boolean DEBUG_LAYOUT = true;
      public static boolean DEBUG = false;
      private static int ID = 1;
      protected final String LOG_TAG = "ptr-frame-" + ++ID;
      // auto refresh status
      private final static byte FLAG_AUTO_REFRESH_AT_ONCE = 0x01;
      private final static byte FLAG_AUTO_REFRESH_BUT_LATER = 0x01 << 1;
      private final static byte FLAG_ENABLE_NEXT_PTR_AT_ONCE = 0x01 << 2;
      private final static byte FLAG_PIN_CONTENT = 0x01 << 3;
      private final static byte MASK_AUTO_REFRESH = 0x03;
      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 boolean mDisableWhenHorizontalMove = false;
      private int mFlag = 0x00;
  
      // disable when detect moving horizontally
      private boolean mPreventForHorizontal = false;
  
      private MotionEvent mLastMoveEvent;
  
      private PtrUIHandlerHook mRefreshCompleteHook;
  
      private boolean isFinishInflate = false;
  
      private int mLoadingMinTime = 500;
      private long mLoadingStartTime = 0;
      private PtrIndicator mPtrIndicator;
      private boolean mHasSendCancelEvent = false;
      private Runnable mPerformRefreshCompleteDelay = new Runnable() {
          @Override
          public void run() {
              performRefreshComplete();
          }
      };
  
      public PtrFrameLayout(Context context) {
          super(context);
          mPtrIndicator = new PtrIndicator();
          mScrollChecker = new ScrollChecker();
          final ViewConfiguration conf = ViewConfiguration.get(getContext());
          mPagingTouchSlop = conf.getScaledTouchSlop();
      }
  
      @Override
      protected void onAttachedToWindow() {
          super.onAttachedToWindow();
          if (!isFinishInflate) {
              onFinishInflate();
              isFinishInflate = true;
          }
      }
  
      @Override
      protected void onFinishInflate() {
          final int childCount = getChildCount();
          if (childCount > 2) {
              throw new IllegalStateException("PtrFrameLayout can only contains 2 children");
          } 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;
                          }
                      }
                  }
                  addPtrUIHandler((PtrUIHandler) mHeaderView);
              }
          } 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 onDetachedFromWindow() {
          super.onDetachedFromWindow();
          if (mScrollChecker != null) {
              mScrollChecker.destroy();
          }
  
          if (mPerformRefreshCompleteDelay != null) {
              removeCallbacks(mPerformRefreshCompleteDelay);
          }
      }
  
      @Override
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  
          if (isDebug()) {
              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 (isDebug()) {
                  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 offset = mPtrIndicator.getCurrentPosY();
          int paddingLeft = getPaddingLeft();
          int paddingTop = getPaddingTop();
  
          if (mHeaderView != null) {
              MarginLayoutParams lp = (MarginLayoutParams) mHeaderView.getLayoutParams();
              final int left = paddingLeft + lp.leftMargin;
              // enhance readability(header is layout above screen when first init)
              final int top = -(mHeaderHeight - paddingTop - lp.topMargin - offset);
              final int right = left + mHeaderView.getMeasuredWidth();
              final int bottom = top + mHeaderView.getMeasuredHeight();
              mHeaderView.layout(left, top, right, bottom);
              if (isDebug()) {
                  PtrCLog.d(LOG_TAG, "onLayout header: %s %s %s %s", left, top, right, bottom);
              }
          }
          if (mContent != null) {
              if (isPinContent()) {
                  offset = 0;
              }
              MarginLayoutParams lp = (MarginLayoutParams) mContent.getLayoutParams();
              final int left = paddingLeft + lp.leftMargin;
              final int top = paddingTop + lp.topMargin + offset;
              final int right = left + mContent.getMeasuredWidth();
              final int bottom = top + mContent.getMeasuredHeight();
              if (isDebug()) {
                  PtrCLog.d(LOG_TAG, "onLayout content: %s %s %s %s", left, top, right, bottom);
              }
              mContent.layout(left, top, right, bottom);
          }
      }
  
      @SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions"})
      private boolean isDebug() {
          return DEBUG && DEBUG_LAYOUT;
      }
  
      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 (Math.abs(offsetX) < Math.abs(offsetY)) {
                      NativeGestureUtil.notifyNativeGestureStarted(this, e);
                  }
  
                  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 ((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.
       */
      final public 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(mPerformRefreshCompleteDelay, 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;
      }
  
      public boolean isEnabledNextPtrAtOnce() {
          return (mFlag & FLAG_ENABLE_NEXT_PTR_AT_ONCE) > 0;
      }
  
      /**
       * 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 isPinContent() {
          return (mFlag & FLAG_PIN_CONTENT) > 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;
          }
      }
  
      /**
       * 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 int getOffsetToKeepHeaderWhileLoading() {
          return mPtrIndicator.getOffsetToKeepHeaderWhileLoading();
      }
  
      @SuppressWarnings({"unused"})
      public void setOffsetToKeepHeaderWhileLoading(int offset) {
          mPtrIndicator.setOffsetToKeepHeaderWhileLoading(offset);
      }
  
      @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 != null && 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);
          }
  
          private void destroy() {
              reset();
              if (!mScroller.isFinished()) {
                  mScroller.forceFinished(true);
              }
          }
  
          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;
          }
      }
  }
  