/** * Set the progress of the CircularSeekBar. If the progress is the same, then any listener will * not receive a onProgressChanged event. * * @param progress The progress to set the CircularSeekBar to. */ public void setProgress(int progress) { if (mProgress != progress) { mProgress = progress; if (mOnCircularSeekBarChangeListener != null) { mOnCircularSeekBarChangeListener.onProgressChanged(this, progress, false); } recalculateAll(); invalidate(); } }
/** * Set the max of the CircularSeekBar. If the new max is less than the current progress, then the * progress will be set to zero. If the progress is changed as a result, then any listener will * receive a onProgressChanged event. * * @param max The new max for the CircularSeekBar. */ public void setMax(int max) { if (!(max <= 0)) { // Check to make sure it's greater than zero if (max <= mProgress) { mProgress = 0; // If the new max is less than current progress, set progress to zero if (mOnCircularSeekBarChangeListener != null) { mOnCircularSeekBarChangeListener.onProgressChanged(this, mProgress, false); } } mMax = max; recalculateAll(); invalidate(); } }
@Override public boolean onTouchEvent(MotionEvent event) { if (!isTouchEnabled) { return false; } // Convert coordinates to our internal coordinate system float x = event.getX() - getWidth() / 2; float y = event.getY() - getHeight() / 2; // Get the distance from the center of the circle in terms of x and y float distanceX = mCircleRectF.centerX() - x; float distanceY = mCircleRectF.centerY() - y; // Get the distance from the center of the circle in terms of a radius float touchEventRadius = (float) Math.sqrt((Math.pow(distanceX, 2) + Math.pow(distanceY, 2))); float minimumTouchTarget = MIN_TOUCH_TARGET_DP * DPTOPX_SCALE; // Convert minimum touch target into px float additionalRadius; // Either uses the minimumTouchTarget size or larger if the ring/pointer // is larger if (mCircleStrokeWidth < minimumTouchTarget) { // If the width is less than the minimumTouchTarget, use the // minimumTouchTarget additionalRadius = minimumTouchTarget / 2; } else { additionalRadius = mCircleStrokeWidth / 2; // Otherwise use the width } float outerRadius = Math.max(mCircleHeight, mCircleWidth) + additionalRadius; // Max outer radius of the circle, including the minimumTouchTarget // or wheel width float innerRadius = Math.min(mCircleHeight, mCircleWidth) - additionalRadius; // Min inner radius of the circle, including the minimumTouchTarget // or wheel width if (mPointerRadius < (minimumTouchTarget / 2)) { // If the pointer radius is less than the minimumTouchTarget, use the // minimumTouchTarget additionalRadius = minimumTouchTarget / 2; } else { additionalRadius = mPointerRadius; // Otherwise use the radius } float touchAngle; touchAngle = (float) ((Math.atan2(y, x) / Math.PI * 180) % 360); // Verified touchAngle = (touchAngle < 0 ? 360 + touchAngle : touchAngle); // Verified cwDistanceFromStart = touchAngle - mStartAngle; // Verified cwDistanceFromStart = (cwDistanceFromStart < 0 ? 360f + cwDistanceFromStart : cwDistanceFromStart); // Verified ccwDistanceFromStart = 360f - cwDistanceFromStart; // Verified cwDistanceFromEnd = touchAngle - mEndAngle; // Verified cwDistanceFromEnd = (cwDistanceFromEnd < 0 ? 360f + cwDistanceFromEnd : cwDistanceFromEnd); // Verified ccwDistanceFromEnd = 360f - cwDistanceFromEnd; // Verified switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // These are only used for ACTION_DOWN for handling if the pointer was the part that was // touched float pointerRadiusDegrees = (float) ((mPointerRadius * 180) / (Math.PI * Math.max(mCircleHeight, mCircleWidth))); cwDistanceFromPointer = touchAngle - mPointerPosition; cwDistanceFromPointer = (cwDistanceFromPointer < 0 ? 360f + cwDistanceFromPointer : cwDistanceFromPointer); ccwDistanceFromPointer = 360f - cwDistanceFromPointer; // This is for if the first touch is on the actual pointer. if (((touchEventRadius >= innerRadius) && (touchEventRadius <= outerRadius)) && ((cwDistanceFromPointer <= pointerRadiusDegrees) || (ccwDistanceFromPointer <= pointerRadiusDegrees))) { setProgressBasedOnAngle(mPointerPosition); lastCWDistanceFromStart = cwDistanceFromStart; mIsMovingCW = true; mPointerHaloPaint.setAlpha(mPointerAlphaOnTouch); mPointerHaloPaint.setColor(mPointerHaloColorOnTouch); recalculateAll(); invalidate(); if (mOnCircularSeekBarChangeListener != null) { mOnCircularSeekBarChangeListener.onStartTrackingTouch(this); } mUserIsMovingPointer = true; lockAtEnd = false; lockAtStart = false; } else if (cwDistanceFromStart > mTotalCircleDegrees) { // If the user is touching outside of the start AND end mUserIsMovingPointer = false; return false; } else if ((touchEventRadius >= innerRadius) && (touchEventRadius <= outerRadius)) { // If the user is touching near the circle setProgressBasedOnAngle(touchAngle); lastCWDistanceFromStart = cwDistanceFromStart; mIsMovingCW = true; mPointerHaloPaint.setAlpha(mPointerAlphaOnTouch); mPointerHaloPaint.setColor(mPointerHaloColorOnTouch); recalculateAll(); invalidate(); if (mOnCircularSeekBarChangeListener != null) { mOnCircularSeekBarChangeListener.onStartTrackingTouch(this); mOnCircularSeekBarChangeListener.onProgressChanged(this, mProgress, true); } mUserIsMovingPointer = true; lockAtEnd = false; lockAtStart = false; } else { // If the user is not touching near the circle mUserIsMovingPointer = false; return false; } break; case MotionEvent.ACTION_MOVE: if (mUserIsMovingPointer) { if (lastCWDistanceFromStart < cwDistanceFromStart) { if ((cwDistanceFromStart - lastCWDistanceFromStart) > 180f && !mIsMovingCW) { lockAtStart = true; lockAtEnd = false; } else { mIsMovingCW = true; } } else { if ((lastCWDistanceFromStart - cwDistanceFromStart) > 180f && mIsMovingCW) { lockAtEnd = true; lockAtStart = false; } else { mIsMovingCW = false; } } if (lockAtStart && mIsMovingCW) { lockAtStart = false; } if (lockAtEnd && !mIsMovingCW) { lockAtEnd = false; } if (lockAtStart && !mIsMovingCW && (ccwDistanceFromStart > 90)) { lockAtStart = false; } if (lockAtEnd && mIsMovingCW && (cwDistanceFromEnd > 90)) { lockAtEnd = false; } // Fix for passing the end of a semi-circle quickly if (!lockAtEnd && cwDistanceFromStart > mTotalCircleDegrees && mIsMovingCW && lastCWDistanceFromStart < mTotalCircleDegrees) { lockAtEnd = true; } if (lockAtStart && lockEnabled) { // TODO: Add a check if mProgress is already 0, in which case don't call the listener mProgress = 0; recalculateAll(); invalidate(); if (mOnCircularSeekBarChangeListener != null) { mOnCircularSeekBarChangeListener.onProgressChanged(this, mProgress, true); } } else if (lockAtEnd && lockEnabled) { mProgress = mMax; recalculateAll(); invalidate(); if (mOnCircularSeekBarChangeListener != null) { mOnCircularSeekBarChangeListener.onProgressChanged(this, mProgress, true); } } else if ((mMoveOutsideCircle) || (touchEventRadius <= outerRadius)) { if (!(cwDistanceFromStart > mTotalCircleDegrees)) { setProgressBasedOnAngle(touchAngle); } recalculateAll(); invalidate(); if (mOnCircularSeekBarChangeListener != null) { mOnCircularSeekBarChangeListener.onProgressChanged(this, mProgress, true); } } else { break; } lastCWDistanceFromStart = cwDistanceFromStart; } else { return false; } break; case MotionEvent.ACTION_UP: mPointerHaloPaint.setAlpha(mPointerAlpha); mPointerHaloPaint.setColor(mPointerHaloColor); if (mUserIsMovingPointer) { mUserIsMovingPointer = false; invalidate(); if (mOnCircularSeekBarChangeListener != null) { mOnCircularSeekBarChangeListener.onStopTrackingTouch(this); } } else { return false; } break; case MotionEvent .ACTION_CANCEL: // Used when the parent view intercepts touches for things like scrolling mPointerHaloPaint.setAlpha(mPointerAlpha); mPointerHaloPaint.setColor(mPointerHaloColor); mUserIsMovingPointer = false; invalidate(); break; } if (event.getAction() == MotionEvent.ACTION_MOVE && getParent() != null) { getParent().requestDisallowInterceptTouchEvent(true); } return true; }