@Override
 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   super.onMeasure(widthMeasureSpec, heightMeasureSpec);
   if (mTarget == null) {
     ensureTarget();
   }
   if (mTarget == null) {
     return;
   }
   mTarget.measure(
       MeasureSpec.makeMeasureSpec(
           getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY),
       MeasureSpec.makeMeasureSpec(
           getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY));
   mCircleView.measure(
       MeasureSpec.makeMeasureSpec(mCircleWidth, MeasureSpec.EXACTLY),
       MeasureSpec.makeMeasureSpec(mCircleHeight, MeasureSpec.EXACTLY));
   if (!mUsingCustomStart && !mOriginalOffsetCalculated) {
     mOriginalOffsetCalculated = true;
     mCurrentTargetOffsetTop = mOriginalOffsetTop = -mCircleView.getMeasuredHeight();
   }
   mCircleViewIndex = -1;
   // Get the index of the circleview.
   for (int index = 0; index < getChildCount(); index++) {
     if (getChildAt(index) == mCircleView) {
       mCircleViewIndex = index;
       break;
     }
   }
 }
 @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();
 }
 @Override
 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
   final int width = getMeasuredWidth();
   final int height = getMeasuredHeight();
   if (getChildCount() == 0) {
     return;
   }
   if (mTarget == null) {
     ensureTarget();
   }
   if (mTarget == null) {
     return;
   }
   final View child = mTarget;
   final int childLeft = getPaddingLeft();
   final int childTop = getPaddingTop();
   final int childWidth = width - getPaddingLeft() - getPaddingRight();
   final int childHeight = height - getPaddingTop() - getPaddingBottom();
   child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
   int circleWidth = mCircleView.getMeasuredWidth();
   int circleHeight = mCircleView.getMeasuredHeight();
   mCircleView.layout(
       (width / 2 - circleWidth / 2),
       mCurrentTargetOffsetTop,
       (width / 2 + circleWidth / 2),
       mCurrentTargetOffsetTop + circleHeight);
 }
 private void setTargetOffsetTopAndBottom(int offset, boolean requiresUpdate) {
   mCircleView.bringToFront();
   mCircleView.offsetTopAndBottom(offset);
   mCurrentTargetOffsetTop = mCircleView.getTop();
   if (requiresUpdate && android.os.Build.VERSION.SDK_INT < 11) {
     invalidate();
   }
 }
 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);
 }
 /**
  * The refresh indicator starting and resting position is always positioned near the top of the
  * refreshing content. This position is a consistent location, but can be adjusted in either
  * direction based on whether or not there is a toolbar or actionbar present.
  *
  * @param scale Set to true if there is no view at a higher z-order than where the progress
  *     spinner is set to appear.
  * @param start The offset in pixels from the top of this view at which the progress spinner
  *     should appear.
  * @param end The offset in pixels from the top of this view at which the progress spinner should
  *     come to rest after a successful swipe gesture.
  */
 public void setProgressViewOffset(boolean scale, int start, int end) {
   mScale = scale;
   mCircleView.setVisibility(View.GONE);
   mOriginalOffsetTop = mCurrentTargetOffsetTop = start;
   mSpinnerFinalOffset = end;
   mUsingCustomStart = true;
   mCircleView.invalidate();
 }
 private void animateOffsetToCorrectPosition(int from, AnimationListener listener) {
   mFrom = from;
   mAnimateToCorrectPosition.reset();
   mAnimateToCorrectPosition.setDuration(ANIMATE_TO_TRIGGER_DURATION);
   mAnimateToCorrectPosition.setInterpolator(mDecelerateInterpolator);
   if (listener != null) {
     mCircleView.setAnimationListener(listener);
   }
   mCircleView.clearAnimation();
   mCircleView.startAnimation(mAnimateToCorrectPosition);
 }
 private void startScaleDownAnimation(Animation.AnimationListener listener) {
   mScaleDownAnimation =
       new Animation() {
         @Override
         public void applyTransformation(float interpolatedTime, Transformation t) {
           setAnimationProgress(1 - interpolatedTime);
         }
       };
   mScaleDownAnimation.setDuration(SCALE_DOWN_DURATION);
   mCircleView.setAnimationListener(listener);
   mCircleView.clearAnimation();
   mCircleView.startAnimation(mScaleDownAnimation);
 }
    @Override
    public boolean onSingleTapUp(MotionEvent e) {
      mTappedViewsPostition = pointToPosition(e.getX(), e.getY());
      if (mTappedViewsPostition >= 0) {
        mTappedView = getChildAt(mTappedViewsPostition);
        mTappedView.setPressed(true);
      } else {
        float centerX = circleWidth / 2;
        float centerY = circleHeight / 2;

        if (e.getX() < centerX + (childWidth / 2)
            && e.getX() > centerX - childWidth / 2
            && e.getY() < centerY + (childHeight / 2)
            && e.getY() > centerY - (childHeight / 2)) {
          if (mOnCenterClickListener != null) {
            mOnCenterClickListener.onCenterClick();
            return true;
          }
        }
      }

      if (mTappedView != null) {
        CircleImageView view = (CircleImageView) (mTappedView);
        if (selected != mTappedViewsPostition) {
          rotateViewToCenter(view, false);
          if (!rotateToCenter) {
            if (mOnItemSelectedListener != null) {
              mOnItemSelectedListener.onItemSelected(
                  mTappedView, mTappedViewsPostition, mTappedView.getId(), view.getName());
            }

            if (mOnItemClickListener != null) {
              mOnItemClickListener.onItemClick(
                  mTappedView, mTappedViewsPostition, mTappedView.getId(), view.getName());
            }
          }
        } else {
          rotateViewToCenter(view, false);

          if (mOnItemClickListener != null) {
            mOnItemClickListener.onItemClick(
                mTappedView, mTappedViewsPostition, mTappedView.getId(), view.getName());
          }
        }
        return true;
      }
      return super.onSingleTapUp(e);
    }
 private void animateOffsetToStartPosition(int from, AnimationListener listener) {
   if (mScale) {
     // Scale the item back down
     startScaleDownReturnToStartAnimation(from, listener);
   } else {
     mFrom = from;
     mAnimateToStartPosition.reset();
     mAnimateToStartPosition.setDuration(ANIMATE_TO_START_DURATION);
     mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator);
     if (listener != null) {
       mCircleView.setAnimationListener(listener);
     }
     mCircleView.clearAnimation();
     mCircleView.startAnimation(mAnimateToStartPosition);
   }
 }
  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int layoutWidth = r - l;
    int layoutHeight = b - t;

    // Laying out the child views
    final int childCount = getChildCount();
    int left, top;
    radius = (layoutWidth <= layoutHeight) ? layoutWidth / 3 : layoutHeight / 3;

    childWidth = (int) (radius / 1.5);
    childHeight = (int) (radius / 1.5);

    float angleDelay = 360 / getChildCount();

    for (int i = 0; i < childCount; i++) {
      final CircleImageView child = (CircleImageView) getChildAt(i);
      if (child.getVisibility() == GONE) {
        continue;
      }

      if (angle > 360) {
        angle -= 360;
      } else {
        if (angle < 0) {
          angle += 360;
        }
      }

      child.setAngle(angle);
      child.setPosition(i);

      left =
          Math.round(
              (float)
                  (((layoutWidth / 2) - childWidth / 2)
                      + radius * Math.cos(Math.toRadians(angle))));
      top =
          Math.round(
              (float)
                  (((layoutHeight / 2) - childHeight / 2)
                      + radius * Math.sin(Math.toRadians(angle))));

      child.layout(left, top, left + childWidth, top + childHeight);
      angle += angleDelay;
    }
  }
 /** 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);
 }
  @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;
  }
  /**
   * Rotate the buttons.
   *
   * @param degrees The degrees, the menu items should get rotated.
   */
  private void rotateButtons(float degrees) {
    int left, top, childCount = getChildCount();
    float angleDelay = 360 / childCount;
    angle += degrees;

    if (angle > 360) {
      angle -= 360;
    } else {
      if (angle < 0) {
        angle += 360;
      }
    }

    for (int i = 0; i < childCount; i++) {
      if (angle > 360) {
        angle -= 360;
      } else {
        if (angle < 0) {
          angle += 360;
        }
      }

      final CircleImageView child = (CircleImageView) getChildAt(i);
      if (child.getVisibility() == GONE) {
        continue;
      }
      left =
          Math.round(
              (float)
                  (((circleWidth / 2) - childWidth / 2)
                      + radius * Math.cos(Math.toRadians(angle))));
      top =
          Math.round(
              (float)
                  (((circleHeight / 2) - childHeight / 2)
                      + radius * Math.sin(Math.toRadians(angle))));

      child.setAngle(angle);

      if (Math.abs(angle - firstChildPos) < (angleDelay / 2) && selected != child.getPosition()) {
        selected = child.getPosition();

        if (mOnItemSelectedListener != null && rotateToCenter) {
          mOnItemSelectedListener.onItemSelected(child, selected, child.getId(), child.getName());
        }
      }

      child.layout(left, top, left + childWidth, top + childHeight);
      angle += angleDelay;
    }
  }
 private Animation startAlphaAnimation(final int startingAlpha, final int endingAlpha) {
   // Pre API 11, alpha is used in place of scale. Don't also use it to
   // show the trigger point.
   if (mScale && isAlphaUsedForScale()) {
     return null;
   }
   Animation alpha =
       new Animation() {
         @Override
         public void applyTransformation(float interpolatedTime, Transformation t) {
           mProgress.setAlpha(
               (int) (startingAlpha + ((endingAlpha - startingAlpha) * interpolatedTime)));
         }
       };
   alpha.setDuration(ALPHA_ANIMATION_DURATION);
   // Clear out the previous animation listeners.
   mCircleView.setAnimationListener(null);
   mCircleView.clearAnimation();
   mCircleView.startAnimation(alpha);
   return alpha;
 }
 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);
 }
 @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;
   }
   targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime));
   int offset = targetTop - mCircleView.getTop();
   setTargetOffsetTopAndBottom(offset, false /* requires update */);
   mProgress.setArrowScale(1 - interpolatedTime);
 }
 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);
 }
  /**
   * Rotates the given view to the center of the menu.
   *
   * @param view the view to be rotated to the center
   * @param fromRunnable if the method is called from the runnable which animates the rotation then
   *     it should be true, otherwise false
   */
  private void rotateViewToCenter(CircleImageView view, boolean fromRunnable) {
    if (rotateToCenter) {
      float velocityTemp = 1;
      float destAngle = (float) (firstChildPos - view.getAngle());
      float startAngle = 0;
      int reverser = 1;

      if (destAngle < 0) {
        destAngle += 360;
      }

      if (destAngle > 180) {
        reverser = -1;
        destAngle = 360 - destAngle;
      }

      while (startAngle < destAngle) {
        startAngle += velocityTemp / 75;
        velocityTemp *= 1.0666F;
      }

      CircleLayout.this.post(new FlingRunnable(reverser * velocityTemp, !fromRunnable));
    }
  }
 /**
  * Get the diameter of the progress circle that is displayed as part of the swipe to refresh
  * layout. This is not valid until a measure pass has completed.
  *
  * @return Diameter in pixels of the progress circle view.
  */
 public int getProgressCircleDiameter() {
   return mCircleView != null ? mCircleView.getMeasuredHeight() : 0;
 }
 /**
  * The refresh indicator resting position is always positioned near the top of the refreshing
  * content. This position is a consistent location, but can be adjusted in either direction based
  * on whether or not there is a toolbar or actionbar present.
  *
  * @param scale Set to true if there is no view at a higher z-order than where the progress
  *     spinner is set to appear.
  * @param end The offset in pixels from the top of this view at which the progress spinner should
  *     come to rest after a successful swipe gesture.
  */
 public void setProgressViewEndTarget(boolean scale, int end) {
   mSpinnerFinalOffset = end;
   mScale = scale;
   mCircleView.invalidate();
 }
 /**
  * Set the background color of the progress spinner disc.
  *
  * @param color
  */
 public void setProgressBackgroundColorSchemeColor(int color) {
   mCircleView.setBackgroundColor(color);
   mProgress.setBackgroundColor(color);
 }
 private void setColorViewAlpha(int targetAlpha) {
   mCircleView.getBackground().setAlpha(targetAlpha);
   mProgress.setAlpha(targetAlpha);
 }
  @Override
  public boolean onTouchEvent(MotionEvent ev) {
    final int action = MotionEventCompat.getActionMasked(ev);

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

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

    switch (action) {
      case MotionEvent.ACTION_DOWN:
        mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
        mIsBeingDragged = false;
        break;

      case MotionEvent.ACTION_MOVE:
        {
          final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
          if (pointerIndex < 0) {
            Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
            return false;
          }

          final float y = MotionEventCompat.getY(ev, pointerIndex);
          final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
          if (mIsBeingDragged) {
            mProgress.showArrow(true);
            float originalDragPercent = overscrollTop / mTotalDragDistance;
            if (originalDragPercent < 0) {
              return false;
            }
            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 */);
          }
          break;
        }
      case MotionEventCompat.ACTION_POINTER_DOWN:
        {
          final int index = MotionEventCompat.getActionIndex(ev);
          mActivePointerId = MotionEventCompat.getPointerId(ev, index);
          break;
        }

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

      case MotionEvent.ACTION_UP:
      case MotionEvent.ACTION_CANCEL:
        {
          if (mActivePointerId == INVALID_POINTER) {
            if (action == MotionEvent.ACTION_UP) {
              Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id.");
            }
            return false;
          }
          final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
          final float y = MotionEventCompat.getY(ev, pointerIndex);
          final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
          mIsBeingDragged = false;
          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);
          }
          mActivePointerId = INVALID_POINTER;
          return false;
        }
    }

    return true;
  }
 private void moveToStart(float interpolatedTime) {
   int targetTop = 0;
   targetTop = (mFrom + (int) ((mOriginalOffsetTop - mFrom) * interpolatedTime));
   int offset = targetTop - mCircleView.getTop();
   setTargetOffsetTopAndBottom(offset, false /* requires update */);
 }