private void finishSpinner(float overscrollTop) {
    if (overscrollTop > mTotalDragDistance) {
      setRefreshing(true, true /* notify */);
    } else {
      // cancel refresh
      mRefreshing = false;
      mProgress.setStartEndTrim(0f, 0f);
      Animation.AnimationListener listener = null;
      if (!mScale) {
        listener =
            new Animation.AnimationListener() {

              @Override
              public void onAnimationStart(Animation animation) {}

              @Override
              public void onAnimationEnd(Animation animation) {
                if (!mScale) {
                  startScaleDownAnimation(null);
                }
              }

              @Override
              public void onAnimationRepeat(Animation animation) {}
            };
      }
      animateOffsetToStartPosition(mCurrentTargetOffsetTop, listener);
      mProgress.showArrow(false);
    }
  }
 @Override
 public void onAnimationEnd(Animation animation) {
   if (mRefreshing) {
     // Make sure the progress view is fully visible
     mProgress.setAlpha(MAX_ALPHA);
     mProgress.start();
     if (mNotify) {
       if (mListener != null) {
         mListener.onRefresh();
       }
     }
   } else {
     mProgress.stop();
     mCircleView.setVisibility(View.GONE);
     setColorViewAlpha(MAX_ALPHA);
     // Return the circle to its start position
     if (mScale) {
       setAnimationProgress(0 /* animation complete and view is hidden */);
     } else {
       setTargetOffsetTopAndBottom(
           mOriginalOffsetTop - mCurrentTargetOffsetTop, true /* requires update */);
     }
   }
   mCurrentTargetOffsetTop = mCircleView.getTop();
 }
 private void createProgressView() {
   mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT, CIRCLE_DIAMETER / 2);
   mProgress = new MaterialProgressDrawable(getContext(), this);
   mProgress.setBackgroundColor(CIRCLE_BG_LIGHT);
   mCircleView.setImageDrawable(mProgress);
   mCircleView.setVisibility(View.GONE);
   addView(mCircleView);
 }
  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    ensureTarget();

    final int action = MotionEventCompat.getActionMasked(ev);

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

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

    switch (action) {
      case MotionEvent.ACTION_DOWN:
        setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true);
        mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
        mIsBeingDragged = false;
        final float initialDownY = getMotionEventY(ev, mActivePointerId);
        if (initialDownY == -1) {
          return false;
        }
        mInitialDownY = initialDownY;
        break;

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

        final float y = getMotionEventY(ev, mActivePointerId);
        if (y == -1) {
          return false;
        }
        final float yDiff = y - mInitialDownY;
        if (yDiff > mTouchSlop && !mIsBeingDragged) {
          mInitialMotionY = mInitialDownY + mTouchSlop;
          mIsBeingDragged = true;
          mProgress.setAlpha(STARTING_PROGRESS_ALPHA);
        }
        break;

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

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

    return mIsBeingDragged;
  }
 @Override
 public void applyTransformation(float interpolatedTime, Transformation t) {
   int targetTop = 0;
   int endTarget = 0;
   if (!mUsingCustomStart) {
     endTarget = (int) (mSpinnerFinalOffset - Math.abs(mOriginalOffsetTop));
   } else {
     endTarget = (int) mSpinnerFinalOffset; // mSpinnerFinalOffset;
   }
   targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime));
   int offset = targetTop - mCircleView.getTop();
   setTargetOffsetTopAndBottom(offset, false /* requires update */);
   mProgress.setArrowScale(1 - interpolatedTime);
 }
 /** One of DEFAULT, or LARGE. */
 public void setSize(int size) {
   if (size != MaterialProgressDrawable.LARGE && size != MaterialProgressDrawable.DEFAULT) {
     return;
   }
   final DisplayMetrics metrics = getResources().getDisplayMetrics();
   if (size == MaterialProgressDrawable.LARGE) {
     mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER_LARGE * metrics.density);
   } else {
     mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density);
   }
   // force the bounds of the progress circle inside the circle view to
   // update by setting it to null before updating its size and then
   // re-setting it
   mCircleView.setImageDrawable(null);
   mProgress.updateSizes(size);
   mCircleView.setImageDrawable(mProgress);
 }
 private void startScaleUpAnimation(AnimationListener listener) {
   mCircleView.setVisibility(View.VISIBLE);
   if (android.os.Build.VERSION.SDK_INT >= 11) {
     // Pre API 11, alpha is used in place of scale up to show the
     // progress circle appearing.
     // Don't adjust the alpha during appearance otherwise.
     mProgress.setAlpha(MAX_ALPHA);
   }
   mScaleAnimation =
       new Animation() {
         @Override
         public void applyTransformation(float interpolatedTime, Transformation t) {
           setAnimationProgress(interpolatedTime);
         }
       };
   mScaleAnimation.setDuration(mMediumAnimationDuration);
   if (listener != null) {
     mCircleView.setAnimationListener(listener);
   }
   mCircleView.clearAnimation();
   mCircleView.startAnimation(mScaleAnimation);
 }
  private void moveSpinner(float overscrollTop) {
    mProgress.showArrow(true);
    float originalDragPercent = overscrollTop / mTotalDragDistance;

    float dragPercent = Math.min(1f, Math.abs(originalDragPercent));
    float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3;
    float extraOS = Math.abs(overscrollTop) - mTotalDragDistance;
    float slingshotDist =
        mUsingCustomStart ? mSpinnerFinalOffset - mOriginalOffsetTop : mSpinnerFinalOffset;
    float tensionSlingshotPercent =
        Math.max(0, Math.min(extraOS, slingshotDist * 2) / slingshotDist);
    float tensionPercent =
        (float) ((tensionSlingshotPercent / 4) - Math.pow((tensionSlingshotPercent / 4), 2)) * 2f;
    float extraMove = (slingshotDist) * tensionPercent * 2;

    int targetY = mOriginalOffsetTop + (int) ((slingshotDist * dragPercent) + extraMove);
    // where 1.0f is a full circle
    if (mCircleView.getVisibility() != View.VISIBLE) {
      mCircleView.setVisibility(View.VISIBLE);
    }
    if (!mScale) {
      ViewCompat.setScaleX(mCircleView, 1f);
      ViewCompat.setScaleY(mCircleView, 1f);
    }
    if (overscrollTop < mTotalDragDistance) {
      if (mScale) {
        setAnimationProgress(overscrollTop / mTotalDragDistance);
      }
      if (mProgress.getAlpha() > STARTING_PROGRESS_ALPHA
          && !isAnimationRunning(mAlphaStartAnimation)) {
        // Animate the alpha
        startProgressAlphaStartAnimation();
      }
      float strokeStart = adjustedPercent * .8f;
      mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart));
      mProgress.setArrowScale(Math.min(1f, adjustedPercent));
    } else {
      if (mProgress.getAlpha() < MAX_ALPHA && !isAnimationRunning(mAlphaMaxAnimation)) {
        // Animate the alpha
        startProgressAlphaMaxAnimation();
      }
    }
    float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f;
    mProgress.setProgressRotation(rotation);
    setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop, true /* requires update */);
  }
 private void startScaleDownReturnToStartAnimation(
     int from, Animation.AnimationListener listener) {
   mFrom = from;
   if (isAlphaUsedForScale()) {
     mStartingScale = mProgress.getAlpha();
   } else {
     mStartingScale = ViewCompat.getScaleX(mCircleView);
   }
   mScaleDownToStartAnimation =
       new Animation() {
         @Override
         public void applyTransformation(float interpolatedTime, Transformation t) {
           float targetScale = (mStartingScale + (-mStartingScale * interpolatedTime));
           setAnimationProgress(targetScale);
           moveToStart(interpolatedTime);
         }
       };
   mScaleDownToStartAnimation.setDuration(SCALE_DOWN_DURATION);
   if (listener != null) {
     mCircleView.setAnimationListener(listener);
   }
   mCircleView.clearAnimation();
   mCircleView.startAnimation(mScaleDownToStartAnimation);
 }
 /**
  * Set the colors used in the progress animation. The first color will also be the color of the
  * bar that grows in response to a user swipe gesture.
  *
  * @param colors
  */
 @ColorInt
 public void setColorSchemeColors(int... colors) {
   ensureTarget();
   mProgress.setColorSchemeColors(colors);
 }
 /**
  * Set the background color of the progress spinner disc.
  *
  * @param color
  */
 public void setProgressBackgroundColorSchemeColor(@ColorInt int color) {
   mCircleView.setBackgroundColor(color);
   mProgress.setBackgroundColor(color);
 }
 private void startProgressAlphaMaxAnimation() {
   mAlphaMaxAnimation = startAlphaAnimation(mProgress.getAlpha(), MAX_ALPHA);
 }
 private void startProgressAlphaStartAnimation() {
   mAlphaStartAnimation = startAlphaAnimation(mProgress.getAlpha(), STARTING_PROGRESS_ALPHA);
 }
 private void setColorViewAlpha(int targetAlpha) {
   mCircleView.getBackground().setAlpha(targetAlpha);
   mProgress.setAlpha(targetAlpha);
 }