final void setState(State state, final boolean... params) {
    mState = state;
    if (DEBUG) {
      Log.d(LOG_TAG, "State: " + mState.name());
    }

    switch (mState) {
      case RESET:
        onReset();
        break;
      case PULL_TO_REFRESH:
        onPullToRefresh();
        break;
      case RELEASE_TO_REFRESH:
        onReleaseToRefresh();
        break;
      case REFRESHING:
      case MANUAL_REFRESHING:
        onRefreshing(params[0]);
        break;
      case OVERSCROLLING:
        // NO-OP
        break;
    }

    // Call OnPullEventListener
    if (null != mOnPullEventListener) {
      mOnPullEventListener.onPullEvent(this, mState, mCurrentMode);
    }
  }
  @Override
  protected void onReleaseToRefresh() {
    super.onReleaseToRefresh();

    if (getShowIndicatorInternal()) {
      switch (getCurrentMode()) {
        case PULL_UP_TO_REFRESH:
          mIndicatorIvBottom.releaseToRefresh();
          break;
        case PULL_DOWN_TO_REFRESH:
          mIndicatorIvTop.releaseToRefresh();
          break;
      }
    }
  }
  @Override
  protected void onReleaseToRefresh() {
    super.onReleaseToRefresh();

    //		if (getShowIndicatorInternal()) {
    //			switch (getCurrentMode()) {
    //				case PULL_FROM_END:
    //					mIndicatorIvBottom.releaseToRefresh();
    //					break;
    //				case PULL_FROM_START:
    //					mIndicatorIvTop.releaseToRefresh();
    //					break;
    //				default:
    //					// NO-OP
    //					break;
    //			}
    //		}
  }
  @Override
  protected void onReleaseToRefresh() {
    super.onReleaseToRefresh();

    if (getShowIndicatorInternal()) {
      switch (getCurrentMode()) {
        case PULL_FROM_END:
          mIndicatorIvBottom.releaseToRefresh();
          break;
        case PULL_FROM_START:
          mIndicatorIvTop.releaseToRefresh();
          break;
        default:
          // NO-OP
          break;
      }
    }
  }
  /**
   * Actions a Pull Event
   *
   * @return true if the Event has been handled, false if there has been no change
   */
  private boolean pullEvent() {

    final int newHeight;
    final int oldHeight = getScrollY();

    switch (mCurrentMode) {
      case PULL_UP_TO_REFRESH:
        newHeight = Math.round(Math.max(mInitialMotionY - mLastMotionY, 0) / FRICTION);
        break;
      case PULL_DOWN_TO_REFRESH:
      default:
        newHeight = Math.round(Math.min(mInitialMotionY - mLastMotionY, 0) / FRICTION);
        break;
    }

    setHeaderScroll(newHeight);

    if (newHeight != 0) {

      float scale = Math.abs(newHeight) / (float) mHeaderHeight;
      switch (mCurrentMode) {
        case PULL_UP_TO_REFRESH:
          mFooterLayout.onPullY(scale);
          break;
        case PULL_DOWN_TO_REFRESH:
          mHeaderLayout.onPullY(scale);
          break;
      }

      if (mState == PULL_TO_REFRESH && mHeaderHeight < Math.abs(newHeight)) {
        mState = RELEASE_TO_REFRESH;
        onReleaseToRefresh();
        return true;

      } else if (mState == RELEASE_TO_REFRESH && mHeaderHeight >= Math.abs(newHeight)) {
        mState = PULL_TO_REFRESH;
        onPullToRefresh();
        return true;
      }
    }

    return oldHeight != newHeight;
  }