content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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 org.apache.weex.ui.view.refresh.core;
  
  import android.animation.Animator;
  import android.animation.ValueAnimator;
  import android.annotation.SuppressLint;
  import android.annotation.TargetApi;
  import android.content.Context;
  import android.graphics.Color;
  import android.os.Build;
  import android.support.annotation.Nullable;
  import android.support.v4.view.NestedScrollingChild;
  import android.support.v4.view.NestedScrollingChildHelper;
  import android.support.v4.view.NestedScrollingParent;
  import android.support.v4.view.NestedScrollingParentHelper;
  import android.support.v4.view.ViewCompat;
  import android.support.v4.view.ViewParentCompat;
  import android.util.AttributeSet;
  import android.util.DisplayMetrics;
  import android.util.TypedValue;
  import android.view.Gravity;
  import android.view.MotionEvent;
  import android.view.View;
  import android.view.ViewGroup;
  import android.view.ViewParent;
  import android.widget.AbsListView;
  import android.widget.FrameLayout;
  
  import java.util.LinkedList;
  import java.util.List;
  
  public class WXSwipeLayout extends FrameLayout implements NestedScrollingParent, NestedScrollingChild {
  
    private NestedScrollingParentHelper mNestedScrollingParentHelper;
    private NestedScrollingChildHelper mNestedScrollingChildHelper;
    private final int[] mParentScrollConsumed = new int[2];
    private final int[] mParentOffsetInWindow = new int[2];
    private boolean mNestedScrollInProgress;
    private WXOnRefreshListener onRefreshListener;
    private WXOnLoadingListener onLoadingListener;
  
    private ViewParent mNestedScrollAcceptedParent;
  
    private final List<OnRefreshOffsetChangedListener> mRefreshOffsetChangedListeners = new LinkedList<>();
  
    public interface OnRefreshOffsetChangedListener {
      void onOffsetChanged(int verticalOffset);
    }
  
    /**
     * On refresh Callback, call on start refresh
     */
    public interface WXOnRefreshListener {
  
      void onRefresh();
  
      void onPullingDown(float dy, int pullOutDistance, float viewHeight);
    }
  
    /**
     * On loadmore Callback, call on start loadmore
     */
    public interface WXOnLoadingListener {
  
      void onLoading();
      void onPullingUp(float dy, int pullOutDistance, float viewHeight);
    }
  
    static class WXRefreshAnimatorListener implements Animator.AnimatorListener {
  
      @Override
      public void onAnimationStart(Animator animation) {
      }
  
      @Override
      public void onAnimationEnd(Animator animation) {
      }
  
      @Override
      public void onAnimationCancel(Animator animation) {
      }
  
      @Override
      public void onAnimationRepeat(Animator animation) {
      }
    }
  
    private WXRefreshView headerView;
    private WXRefreshView footerView;
    private View mTargetView;
  
    private static final int INVALID = -1;
    private static final int PULL_REFRESH = 0;
    private static final int LOAD_MORE = 1;
  
    // Enable PullRefresh and Loadmore
    private boolean mPullRefreshEnable = false;
    private boolean mPullLoadEnable = false;
  
    // Is Refreshing
    volatile private boolean mRefreshing = false;
  
    // RefreshView Height
    private volatile float refreshViewHeight = 0;
    private volatile float loadingViewHeight = 0;
  
    // RefreshView Over Flow Height
    private volatile float refreshViewFlowHeight = 0;
    private volatile float loadingViewFlowHeight = 0;
  
    private static final float overFlow = 1.0f;
  
    private static final float DAMPING = 0.4f;
  
    // Drag Action
    private int mCurrentAction = -1;
    private boolean isConfirm = false;
  
    // RefreshView Attrs
    private int mRefreshViewBgColor;
    private int mProgressBgColor;
    private int mProgressColor;
  
    public WXSwipeLayout(Context context) {
      super(context);
      initAttrs(context, null);
    }
  
    public WXSwipeLayout(Context context, AttributeSet attrs) {
      super(context, attrs);
      initAttrs(context, attrs);
    }
  
    public WXSwipeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
      super(context, attrs, defStyleAttr);
      initAttrs(context, attrs);
    }
  
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public WXSwipeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
      super(context, attrs, defStyleAttr, defStyleRes);
      initAttrs(context, attrs);
    }
  
    private void initAttrs(Context context, AttributeSet attrs) {
  
      if (getChildCount() > 1) {
        throw new RuntimeException("WXSwipeLayout should not have more than one child");
      }
  
      mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);
      mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
      setNestedScrollingEnabled(false);
  
      if (isInEditMode() && attrs == null) {
        return;
      }
  
      mRefreshViewBgColor = Color.TRANSPARENT;
      mProgressBgColor = Color.TRANSPARENT;
      mProgressColor = Color.RED;
    }
  
    @Override
    protected void onAttachedToWindow() {
      super.onAttachedToWindow();
      if(mTargetView == null && getChildCount() > 0){
        mTargetView = getChildAt(0);
      }
      if(mTargetView != null){
        if(headerView == null || footerView == null){
          setRefreshView();
        }
      }
    }
  
  
    public void addTargetView(View mInnerView){
      this.addView(mInnerView, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
      setRefreshView();
    }
    /**
     * Init refresh view or loading view
     */
    private void setRefreshView() {
      // SetUp HeaderView
      FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, 0);
      headerView = new WXRefreshView(getContext());
      headerView.setStartEndTrim(0, 0.75f);
      headerView.setBackgroundColor(mRefreshViewBgColor);
      headerView.setProgressBgColor(mProgressBgColor);
      headerView.setProgressColor(mProgressColor);
      headerView.setContentGravity(Gravity.BOTTOM);
      addView(headerView, lp);
  
      // SetUp FooterView
      lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, 0);
      lp.gravity = Gravity.BOTTOM;
      footerView = new WXRefreshView(getContext());
      footerView.setStartEndTrim(0.5f, 1.25f);
      footerView.setBackgroundColor(mRefreshViewBgColor);
      footerView.setProgressBgColor(mProgressBgColor);
      footerView.setProgressColor(mProgressColor);
      footerView.setContentGravity(Gravity.TOP);
      addView(footerView, lp);
    }
  
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
      if ((!mPullRefreshEnable && !mPullLoadEnable)) {
        return false;
      }
      if (!isEnabled() || canChildScrollUp()
              || mRefreshing || mNestedScrollInProgress) {
        // Fail fast if we're not in a state where a swipe is possible
        return false;
      }
  
      return super.onInterceptTouchEvent(ev);
    }
  
    // NestedScrollingChild
  
    @Override
    public void setNestedScrollingEnabled(boolean enabled) {
      mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled);
    }
  
    @Override
    public boolean isNestedScrollingEnabled() {
      return mNestedScrollingChildHelper.isNestedScrollingEnabled();
    }
  
    @Override
    public boolean startNestedScroll(int axes) {
      boolean result = mNestedScrollingChildHelper.startNestedScroll(axes);
      if(result){
        if(mNestedScrollAcceptedParent == null){
          ViewParent parent  = this.getParent();
          View child = this;
          while (parent != null) {
            if (ViewParentCompat.onStartNestedScroll(parent, child, this, axes)){
              mNestedScrollAcceptedParent = parent;
              break;
            }
            if(parent instanceof  View){
              child = (View) parent;
            }
            parent = parent.getParent();
          }
        }
      }
      return result;
    }
  
    @Override
    public void stopNestedScroll() {
      mNestedScrollingChildHelper.stopNestedScroll();
      if(mNestedScrollAcceptedParent != null){
        mNestedScrollAcceptedParent = null;
      }
    }
  
    @Override
    public boolean hasNestedScrollingParent() {
      return mNestedScrollingChildHelper.hasNestedScrollingParent();
    }
  
    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
                                        int dyUnconsumed, int[] offsetInWindow) {
      return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed,
              dxUnconsumed, dyUnconsumed, offsetInWindow);
    }
  
    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
      return mNestedScrollingChildHelper.dispatchNestedPreScroll(
              dx, dy, consumed, offsetInWindow);
    }
  
  
  
    @Override
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
      return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
    }
  
    @Override
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
      return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
    }
  
    /*********************************** NestedScrollParent *************************************/
  
  
    @Override
    public boolean onNestedPreFling(View target, float velocityX,
                                    float velocityY) {
      if(isNestedScrollingEnabled()) {
        return dispatchNestedPreFling(velocityX, velocityY);
      }
      return  false;
    }
  
    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY,
                                 boolean consumed) {
      if(isNestedScrollingEnabled()) {
        return dispatchNestedFling(velocityX, velocityY, consumed);
      }
      return  false;
    }
  
    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
      boolean result =  isEnabled()  && !mRefreshing
              && (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
  
      return  result;
    }
  
    @Override
    public void onNestedScrollAccepted(View child, View target, int axes) {
      mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes);
      if(isNestedScrollingEnabled()){
        startNestedScroll(axes & ViewCompat.SCROLL_AXIS_VERTICAL);
        mNestedScrollInProgress = true;
      }
    }
  
  
    /**
     * With child view to processing move events
     * @param target the child view
     * @param dx move x
     * @param dy move y
     * @param consumed parent consumed move distance
     */
    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
      // Now let our nested parent consume the leftovers
      final int[] parentConsumed = mParentScrollConsumed;
      if(isNestedScrollingEnabled()){
        if (dispatchNestedPreScroll(dx - consumed[0], dy - consumed[1], parentConsumed, null)) {
          consumed[0] += parentConsumed[0];
          consumed[1] += parentConsumed[1];
          return;
        }
      }
      if ((!mPullRefreshEnable && !mPullLoadEnable)) {
        return;
      }
  
      /**
       * when in nest-scroll, list canChildScrollUp() false,
       * maybe parent scroll can scroll up
       * */
      if(!canChildScrollUp() && isNestedScrollingEnabled()){
        if(mNestedScrollAcceptedParent != null && mNestedScrollAcceptedParent != mTargetView){
          ViewGroup group = (ViewGroup) mNestedScrollAcceptedParent;
          if(group.getChildCount() > 0){
            int count = group.getChildCount();
            for(int i=0; i<count; i++){
              View view  = group.getChildAt(i);
              if(view.getVisibility() != View.GONE && view.getMeasuredHeight() > 0){
                if(view.getTop() < 0){
                  return;
                }else{
                  break;
                }
              }
            }
          }
        }
      }
  
  
  
      int spinnerDy = (int) calculateDistanceY(target, dy);
  
      mRefreshing = false;
  
      if (!isConfirm) {
        if (spinnerDy < 0 && !canChildScrollUp()) {
          mCurrentAction = PULL_REFRESH;
          isConfirm = true;
        } else if (spinnerDy > 0 && !canChildScrollDown() && (!mRefreshing)) {
          mCurrentAction = LOAD_MORE;
          isConfirm = true;
        }
      }
  
      if (moveSpinner(-spinnerDy)) {
        if (!canChildScrollUp() && mPullRefreshEnable
                && mTargetView.getTranslationY() > 0
                && dy > 0) {
          consumed[1] += dy;
        }else if (!canChildScrollDown() && mPullLoadEnable
                && mTargetView.getTranslationY() < 0
                && dy < 0){
          consumed[1] += dy;
        }else{
          consumed[1] += spinnerDy;
        }
      }
    }
  
    @Override
    public int getNestedScrollAxes() {
      return mNestedScrollingParentHelper.getNestedScrollAxes();
    }
  
  
    /**
     * Callback on TouchEvent.ACTION_CANCLE or TouchEvent.ACTION_UP
     * handler : refresh or loading
     * @param child : child view of SwipeLayout,RecyclerView or Scroller
     */
    @Override
    public void onStopNestedScroll(View child) {
      mNestedScrollingParentHelper.onStopNestedScroll(child);
      handlerAction();
      if(isNestedScrollingEnabled()) {
        mNestedScrollInProgress = true;
        stopNestedScroll();
      }
    }
  
  
    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
      if(isNestedScrollingEnabled()) {
        dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, mParentOffsetInWindow);
      }
    }
  
  
  
  
  
    private double calculateDistanceY(View target, int dy) {
      int viewHeight = target.getMeasuredHeight();
      double ratio = (viewHeight - Math.abs(target.getY())) / 1.0d / viewHeight * DAMPING;
      if (ratio <= 0.01d) {
        //Filter tiny scrolling action
        ratio = 0.01d;
      }
      return ratio * dy;
    }
  
    /**
     * Adjust the refresh or loading view according to the size of the gesture
     *
     * @param distanceY move distance of Y
     */
    private boolean moveSpinner(float distanceY) {
      if (mRefreshing) {
        return false;
      }
  
      if (!canChildScrollUp() && mPullRefreshEnable && mCurrentAction == PULL_REFRESH) {
        // Pull Refresh
        LayoutParams lp = (LayoutParams) headerView.getLayoutParams();
        lp.height += distanceY;
        if (lp.height < 0) {
          lp.height = 0;
        }
  
        if (lp.height == 0) {
          isConfirm = false;
          mCurrentAction = INVALID;
        }
        headerView.setLayoutParams(lp);
        onRefreshListener.onPullingDown(distanceY, lp.height, refreshViewFlowHeight);
        notifyOnRefreshOffsetChangedListener(lp.height);
        headerView.setProgressRotation(lp.height / refreshViewFlowHeight);
        moveTargetView(lp.height);
        return true;
      } else if (!canChildScrollDown() && mPullLoadEnable && mCurrentAction == LOAD_MORE) {
        // Load more
        LayoutParams lp = (LayoutParams) footerView.getLayoutParams();
        lp.height -= distanceY;
        if (lp.height < 0) {
          lp.height = 0;
        }
  
        if (lp.height == 0) {
          isConfirm = false;
          mCurrentAction = INVALID;
        }
        footerView.setLayoutParams(lp);
        onLoadingListener.onPullingUp(distanceY, lp.height, loadingViewFlowHeight);
        footerView.setProgressRotation(lp.height / loadingViewFlowHeight);
        moveTargetView(-lp.height);
        return true;
      }
      return false;
    }
  
    /**
     * Adjust contentView(Scroller or List) at refresh or loading time
     * @param h Height of refresh view or loading view
     */
    private void moveTargetView(float h) {
      mTargetView.setTranslationY(h);
    }
  
    /**
     * Decide on the action refresh or loadmore
     */
    private void handlerAction() {
  
      if (isRefreshing()) {
        return;
      }
      isConfirm = false;
  
      LayoutParams lp;
      if (mPullRefreshEnable && mCurrentAction == PULL_REFRESH) {
        lp = (LayoutParams) headerView.getLayoutParams();
        if (lp.height >= refreshViewHeight) {
          startRefresh(lp.height);
        } else if (lp.height > 0) {
          resetHeaderView(lp.height);
        } else {
          resetRefreshState();
        }
      }
  
      if (mPullLoadEnable && mCurrentAction == LOAD_MORE) {
        lp = (LayoutParams) footerView.getLayoutParams();
        if (lp.height >= loadingViewHeight) {
          startLoadmore(lp.height);
        } else if (lp.height > 0) {
          resetFootView(lp.height);
        } else {
          resetLoadmoreState();
        }
      }
    }
  
    /**
     * Start Refresh
     * @param headerViewHeight
     */
    private void startRefresh(int headerViewHeight) {
      mRefreshing = true;
      ValueAnimator animator = ValueAnimator.ofFloat(headerViewHeight, refreshViewHeight);
      animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
          LayoutParams lp = (LayoutParams) headerView.getLayoutParams();
          lp.height = (int) ((Float) animation.getAnimatedValue()).floatValue();
          notifyOnRefreshOffsetChangedListener(lp.height);
          headerView.setLayoutParams(lp);
          moveTargetView(lp.height);
        }
      });
      animator.addListener(new WXRefreshAnimatorListener() {
        @Override
        public void onAnimationEnd(Animator animation) {
          headerView.startAnimation();
          //TODO updateLoadText
          if (onRefreshListener != null) {
            onRefreshListener.onRefresh();
          }
        }
      });
      animator.setDuration(300);
      animator.start();
    }
  
    /**
     * Reset refresh state
     * @param headerViewHeight
     */
    private void resetHeaderView(int headerViewHeight) {
      headerView.stopAnimation();
      headerView.setStartEndTrim(0, 0.75f);
      ValueAnimator animator = ValueAnimator.ofFloat(headerViewHeight, 0);
      animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
          LayoutParams lp = (LayoutParams) headerView.getLayoutParams();
          lp.height = (int) ((Float) animation.getAnimatedValue()).floatValue();
          notifyOnRefreshOffsetChangedListener(lp.height);
          headerView.setLayoutParams(lp);
          moveTargetView(lp.height);
        }
      });
      animator.addListener(new WXRefreshAnimatorListener() {
        @Override
        public void onAnimationEnd(Animator animation) {
          resetRefreshState();
  
        }
      });
      animator.setDuration(300);
      animator.start();
    }
  
    private void resetRefreshState() {
      mRefreshing = false;
      isConfirm = false;
      mCurrentAction = -1;
      //TODO updateLoadText
    }
  
    /**
     * Start loadmore
     * @param headerViewHeight
     */
    private void startLoadmore(int headerViewHeight) {
      mRefreshing = true;
      ValueAnimator animator = ValueAnimator.ofFloat(headerViewHeight, loadingViewHeight);
      animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
          LayoutParams lp = (LayoutParams) footerView.getLayoutParams();
          lp.height = (int) ((Float) animation.getAnimatedValue()).floatValue();
          footerView.setLayoutParams(lp);
          moveTargetView(-lp.height);
        }
      });
      animator.addListener(new WXRefreshAnimatorListener() {
        @Override
        public void onAnimationEnd(Animator animation) {
          footerView.startAnimation();
          //TODO updateLoadText
          if (onLoadingListener != null) {
            onLoadingListener.onLoading();
          }
        }
      });
      animator.setDuration(300);
      animator.start();
    }
  
    /**
     * Reset loadmore state
     * @param headerViewHeight
     */
    private void resetFootView(int headerViewHeight) {
      footerView.stopAnimation();
      footerView.setStartEndTrim(0.5f, 1.25f);
      ValueAnimator animator = ValueAnimator.ofFloat(headerViewHeight, 0);
      animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
          LayoutParams lp = (LayoutParams) footerView.getLayoutParams();
          lp.height = (int) ((Float) animation.getAnimatedValue()).floatValue();
          footerView.setLayoutParams(lp);
          moveTargetView(-lp.height);
        }
      });
      animator.addListener(new WXRefreshAnimatorListener() {
        @Override
        public void onAnimationEnd(Animator animation) {
          resetLoadmoreState();
  
        }
      });
      animator.setDuration(300);
      animator.start();
    }
  
    private void resetLoadmoreState() {
      mRefreshing = false;
      isConfirm = false;
      mCurrentAction = -1;
      //TODO updateLoadText
    }
  
    /**
     * Whether child view can scroll up
     * @return
     */
    @SuppressLint("ObsoleteSdkInt")
    public boolean canChildScrollUp() {
      if (mTargetView == null) {
        return false;
      }
      if (Build.VERSION.SDK_INT < 14) {
        //The minimum version of Android platform that Weex supports is API 14,
        //so this block should never enter.
        if (mTargetView instanceof AbsListView) {
          final AbsListView absListView = (AbsListView) mTargetView;
          return absListView.getChildCount() > 0
                  && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
                  .getTop() < absListView.getPaddingTop());
        } else {
          return ViewCompat.canScrollVertically(mTargetView, -1) || mTargetView.getScrollY() > 0;
        }
      } else {
        return ViewCompat.canScrollVertically(mTargetView, -1);
      }
    }
  
    /**
     * Whether child view can scroll down
     * @return
     */
    @SuppressLint("ObsoleteSdkInt")
    public boolean canChildScrollDown() {
      if (mTargetView == null) {
        return false;
      }
      if (Build.VERSION.SDK_INT < 14) {
        //The minimum version of Android platform that Weex supports is API 14,
        //so this block should never enter.
        if (mTargetView instanceof AbsListView) {
          final AbsListView absListView = (AbsListView) mTargetView;
          if (absListView.getChildCount() > 0) {
            int lastChildBottom = absListView.getChildAt(absListView.getChildCount() - 1)
                    .getBottom();
            return absListView.getLastVisiblePosition() == absListView.getAdapter().getCount() - 1
                    && lastChildBottom <= absListView.getMeasuredHeight();
          } else {
            return false;
          }
  
        } else {
          return ViewCompat.canScrollVertically(mTargetView, 1) || mTargetView.getScrollY() > 0;
        }
      } else {
        return ViewCompat.canScrollVertically(mTargetView, 1);
      }
    }
  
    public float dipToPx(Context context, float value) {
      DisplayMetrics metrics = context.getResources().getDisplayMetrics();
      return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, metrics);
    }
  
    public void setOnLoadingListener(WXOnLoadingListener onLoadingListener) {
      this.onLoadingListener = onLoadingListener;
    }
  
    public void setOnRefreshListener(WXOnRefreshListener onRefreshListener) {
      this.onRefreshListener = onRefreshListener;
    }
  
    @SuppressWarnings("unused")
    public void addOnRefreshOffsetChangedListener(@Nullable OnRefreshOffsetChangedListener listener) {
      if(listener != null && !mRefreshOffsetChangedListeners.contains(listener)) {
        mRefreshOffsetChangedListeners.add(listener);
      }
    }
  
    @SuppressWarnings("unused")
    public boolean removeOnRefreshOffsetChangedListener(@Nullable OnRefreshOffsetChangedListener listener) {
      if(listener != null) {
        return mRefreshOffsetChangedListeners.remove(listener);
      }
      return false;
    }
  
    private void notifyOnRefreshOffsetChangedListener(int verticalOffset) {
      int size = mRefreshOffsetChangedListeners.size();
      OnRefreshOffsetChangedListener listener;
      for (int i=0; i<size; i++) {
        if(i >= mRefreshOffsetChangedListeners.size()){
          break;
        }
        listener = mRefreshOffsetChangedListeners.get(i);
  
        if (listener != null) {
          listener.onOffsetChanged(verticalOffset);
        }
      }
    }
  
    /**
     * Callback on refresh finish
     */
    public void finishPullRefresh() {
      if (mCurrentAction == PULL_REFRESH) {
        resetHeaderView(headerView == null ? 0 : headerView.getMeasuredHeight());
      }
    }
  
    /**
     * Callback on loadmore finish
     */
    public void finishPullLoad() {
      if (mCurrentAction == LOAD_MORE) {
        resetFootView(footerView == null ? 0 : footerView.getMeasuredHeight());
      }
    }
  
    public WXRefreshView getHeaderView() {
      return headerView;
    }
  
    public WXRefreshView getFooterView() {
      return footerView;
    }
  
    public boolean isPullLoadEnable() {
      return mPullLoadEnable;
    }
  
    public void setPullLoadEnable(boolean mPullLoadEnable) {
      this.mPullLoadEnable = mPullLoadEnable;
    }
  
    public boolean isPullRefreshEnable() {
      return mPullRefreshEnable;
    }
  
    public void setPullRefreshEnable(boolean mPullRefreshEnable) {
      this.mPullRefreshEnable = mPullRefreshEnable;
    }
  
    public boolean isRefreshing() {
      return mRefreshing;
    }
  
    public void setRefreshHeight(int height) {
      refreshViewHeight = height;
      refreshViewFlowHeight = refreshViewHeight * overFlow;
    }
  
    public void setLoadingHeight(int height) {
      loadingViewHeight = height;
      loadingViewFlowHeight = loadingViewHeight * overFlow;
    }
  
    public void setRefreshBgColor(int color) {
      headerView.setBackgroundColor(color);
    }
  
    public void setLoadingBgColor(int color) {
      footerView.setBackgroundColor(color);
    }
  }
  