FileDocCategorySizeDatePackage
RadialTimePickerView.javaAPI DocAndroid 5.1 API65478Thu Mar 12 22:22:10 GMT 2015android.widget

RadialTimePickerView

public class RadialTimePickerView extends android.view.View implements View.OnTouchListener
View to show a clock circle picker (with one or two picking circles)
hide

Fields Summary
private static final String
TAG
private static final boolean
DEBUG
private static final int
DEBUG_COLOR
private static final int
DEBUG_TEXT_COLOR
private static final int
DEBUG_STROKE_WIDTH
private static final int
HOURS
private static final int
MINUTES
private static final int
HOURS_INNER
private static final int
SELECTOR_CIRCLE
private static final int
SELECTOR_DOT
private static final int
SELECTOR_LINE
private static final int
AM
private static final int
PM
private static final int
ALPHA_OPAQUE
private static final int
ALPHA_TRANSPARENT
private static final int
ALPHA_SELECTOR
private static final float
COSINE_30_DEGREES
private static final float
SINE_30_DEGREES
private static final int
DEGREES_FOR_ONE_HOUR
private static final int
DEGREES_FOR_ONE_MINUTE
private static final int[]
HOURS_NUMBERS
private static final int[]
HOURS_NUMBERS_24
private static final int[]
MINUTES_NUMBERS
private static final int
CENTER_RADIUS
private static int[]
sSnapPrefer30sMap
private final InvalidateUpdateListener
mInvalidateUpdateListener
private final String[]
mHours12Texts
private final String[]
mOuterHours24Texts
private final String[]
mInnerHours24Texts
private final String[]
mMinutesTexts
private final android.graphics.Paint[]
mPaint
private final int[]
mColor
private final IntHolder[]
mAlpha
private final android.graphics.Paint
mPaintCenter
private final android.graphics.Paint[]
mPaintSelector
private final int[]
mColorSelector
private final IntHolder[]
mAlphaSelector
private final android.graphics.Paint
mPaintBackground
private final android.graphics.Paint
mPaintDebug
private final android.graphics.Typeface
mTypeface
private final float[]
mCircleRadius
private final float[]
mTextSize
private final float[]
mTextGridHeights
private final float[]
mTextGridWidths
private final float[]
mInnerTextGridHeights
private final float[]
mInnerTextGridWidths
private final float[]
mCircleRadiusMultiplier
private final float[]
mNumbersRadiusMultiplier
private final float[]
mTextSizeMultiplier
private final float[]
mAnimationRadiusMultiplier
private final float
mTransitionMidRadiusMultiplier
private final float
mTransitionEndRadiusMultiplier
private final int[]
mLineLength
private final int[]
mSelectionRadius
private final float
mSelectionRadiusMultiplier
private final int[]
mSelectionDegrees
private final ArrayList
mHoursToMinutesAnims
private final ArrayList
mMinuteToHoursAnims
private final RadialPickerTouchHelper
mTouchHelper
private float
mInnerTextSize
private boolean
mIs24HourMode
private boolean
mShowHours
private boolean
mIsOnInnerCircle
When in 24-hour mode, indicates that the current hour is between 1 and 12 (inclusive).
private int
mXCenter
private int
mYCenter
private int
mMinHypotenuseForInnerNumber
private int
mMaxHypotenuseForOuterNumber
private int
mHalfwayHypotenusePoint
private String[]
mOuterTextHours
private String[]
mInnerTextHours
private String[]
mOuterTextMinutes
private android.animation.AnimatorSet
mTransition
private int
mAmOrPm
private int
mDisabledAlpha
private OnValueSelectedListener
mListener
private boolean
mInputEnabled
boolean
mChangedDuringTouch
Constructors Summary
public RadialTimePickerView(android.content.Context context)

        this(context, null);
    
public RadialTimePickerView(android.content.Context context, android.util.AttributeSet attrs)

        this(context, attrs, R.attr.timePickerStyle);
    
public RadialTimePickerView(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr)

        this(context, attrs, defStyleAttr, 0);
    
public RadialTimePickerView(android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr, int defStyleRes)

        super(context, attrs);

        // Pull disabled alpha from theme.
        final TypedValue outValue = new TypedValue();
        context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true);
        mDisabledAlpha = (int) (outValue.getFloat() * 255 + 0.5f);

        // process style attributes
        final Resources res = getResources();
        final TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TimePicker,
                defStyleAttr, defStyleRes);

        mTypeface = Typeface.create("sans-serif", Typeface.NORMAL);

        // Initialize all alpha values to opaque.
        for (int i = 0; i < mAlpha.length; i++) {
            mAlpha[i] = new IntHolder(ALPHA_OPAQUE);
        }
        for (int i = 0; i < mAlphaSelector.length; i++) {
            for (int j = 0; j < mAlphaSelector[i].length; j++) {
                mAlphaSelector[i][j] = new IntHolder(ALPHA_OPAQUE);
            }
        }

        final int numbersTextColor = a.getColor(R.styleable.TimePicker_numbersTextColor,
                res.getColor(R.color.timepicker_default_text_color_material));

        mPaint[HOURS] = new Paint();
        mPaint[HOURS].setAntiAlias(true);
        mPaint[HOURS].setTextAlign(Paint.Align.CENTER);
        mColor[HOURS] = numbersTextColor;

        mPaint[MINUTES] = new Paint();
        mPaint[MINUTES].setAntiAlias(true);
        mPaint[MINUTES].setTextAlign(Paint.Align.CENTER);
        mColor[MINUTES] = numbersTextColor;

        mPaintCenter.setColor(numbersTextColor);
        mPaintCenter.setAntiAlias(true);
        mPaintCenter.setTextAlign(Paint.Align.CENTER);

        mPaintSelector[HOURS][SELECTOR_CIRCLE] = new Paint();
        mPaintSelector[HOURS][SELECTOR_CIRCLE].setAntiAlias(true);
        mColorSelector[HOURS][SELECTOR_CIRCLE] = a.getColor(
                R.styleable.TimePicker_numbersSelectorColor,
                R.color.timepicker_default_selector_color_material);

        mPaintSelector[HOURS][SELECTOR_DOT] = new Paint();
        mPaintSelector[HOURS][SELECTOR_DOT].setAntiAlias(true);
        mColorSelector[HOURS][SELECTOR_DOT] = a.getColor(
                R.styleable.TimePicker_numbersSelectorColor,
                R.color.timepicker_default_selector_color_material);

        mPaintSelector[HOURS][SELECTOR_LINE] = new Paint();
        mPaintSelector[HOURS][SELECTOR_LINE].setAntiAlias(true);
        mPaintSelector[HOURS][SELECTOR_LINE].setStrokeWidth(2);
        mColorSelector[HOURS][SELECTOR_LINE] = a.getColor(
                R.styleable.TimePicker_numbersSelectorColor,
                R.color.timepicker_default_selector_color_material);

        mPaintSelector[MINUTES][SELECTOR_CIRCLE] = new Paint();
        mPaintSelector[MINUTES][SELECTOR_CIRCLE].setAntiAlias(true);
        mColorSelector[MINUTES][SELECTOR_CIRCLE] = a.getColor(
                R.styleable.TimePicker_numbersSelectorColor,
                R.color.timepicker_default_selector_color_material);

        mPaintSelector[MINUTES][SELECTOR_DOT] = new Paint();
        mPaintSelector[MINUTES][SELECTOR_DOT].setAntiAlias(true);
        mColorSelector[MINUTES][SELECTOR_DOT] = a.getColor(
                R.styleable.TimePicker_numbersSelectorColor,
                R.color.timepicker_default_selector_color_material);

        mPaintSelector[MINUTES][SELECTOR_LINE] = new Paint();
        mPaintSelector[MINUTES][SELECTOR_LINE].setAntiAlias(true);
        mPaintSelector[MINUTES][SELECTOR_LINE].setStrokeWidth(2);
        mColorSelector[MINUTES][SELECTOR_LINE] = a.getColor(
                R.styleable.TimePicker_numbersSelectorColor,
                R.color.timepicker_default_selector_color_material);

        mPaintBackground.setColor(a.getColor(R.styleable.TimePicker_numbersBackgroundColor,
                res.getColor(R.color.timepicker_default_numbers_background_color_material)));
        mPaintBackground.setAntiAlias(true);

        if (DEBUG) {
            mPaintDebug.setColor(DEBUG_COLOR);
            mPaintDebug.setAntiAlias(true);
            mPaintDebug.setStrokeWidth(DEBUG_STROKE_WIDTH);
            mPaintDebug.setStyle(Paint.Style.STROKE);
            mPaintDebug.setTextAlign(Paint.Align.CENTER);
        }

        mShowHours = true;
        mIs24HourMode = false;
        mAmOrPm = AM;

        // Set up accessibility components.
        mTouchHelper = new RadialPickerTouchHelper();
        setAccessibilityDelegate(mTouchHelper);

        if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
        }

        initHoursAndMinutesText();
        initData();

        mTransitionMidRadiusMultiplier =  Float.parseFloat(
                res.getString(R.string.timepicker_transition_mid_radius_multiplier));
        mTransitionEndRadiusMultiplier = Float.parseFloat(
                res.getString(R.string.timepicker_transition_end_radius_multiplier));

        mTextGridHeights[HOURS] = new float[7];
        mTextGridHeights[MINUTES] = new float[7];

        mSelectionRadiusMultiplier = Float.parseFloat(
                res.getString(R.string.timepicker_selection_radius_multiplier));

        a.recycle();

        setOnTouchListener(this);
        setClickable(true);

        // Initial values
        final Calendar calendar = Calendar.getInstance(Locale.getDefault());
        final int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
        final int currentMinute = calendar.get(Calendar.MINUTE);

        setCurrentHourInternal(currentHour, false, false);
        setCurrentMinuteInternal(currentMinute, false);

        setHapticFeedbackEnabled(true);
    
Methods Summary
private static voidcalculateGridSizes(android.graphics.Paint paint, float numbersRadius, float xCenter, float yCenter, float textSize, float[] textGridHeights, float[] textGridWidths)
Using the trigonometric Unit Circle, calculate the positions that the text will need to be drawn at based on the specified circle radius. Place the values in the textGridHeights and textGridWidths parameters.

        /*
         * The numbers need to be drawn in a 7x7 grid, representing the points on the Unit Circle.
         */
        final float offset1 = numbersRadius;
        // cos(30) = a / r => r * cos(30)
        final float offset2 = numbersRadius * COSINE_30_DEGREES;
        // sin(30) = o / r => r * sin(30)
        final float offset3 = numbersRadius * SINE_30_DEGREES;

        paint.setTextSize(textSize);
        // We'll need yTextBase to be slightly lower to account for the text's baseline.
        yCenter -= (paint.descent() + paint.ascent()) / 2;

        textGridHeights[0] = yCenter - offset1;
        textGridWidths[0] = xCenter - offset1;
        textGridHeights[1] = yCenter - offset2;
        textGridWidths[1] = xCenter - offset2;
        textGridHeights[2] = yCenter - offset3;
        textGridWidths[2] = xCenter - offset3;
        textGridHeights[3] = yCenter;
        textGridWidths[3] = xCenter;
        textGridHeights[4] = yCenter + offset3;
        textGridWidths[4] = xCenter + offset3;
        textGridHeights[5] = yCenter + offset2;
        textGridWidths[5] = xCenter + offset2;
        textGridHeights[6] = yCenter + offset1;
        textGridWidths[6] = xCenter + offset1;
    
private voidcalculateGridSizesHours()

        // Calculate the text positions
        float numbersRadius = mCircleRadius[HOURS]
                * mNumbersRadiusMultiplier[HOURS] * mAnimationRadiusMultiplier[HOURS];

        // Calculate the positions for the 12 numbers in the main circle.
        calculateGridSizes(mPaint[HOURS], numbersRadius, mXCenter, mYCenter,
                mTextSize[HOURS], mTextGridHeights[HOURS], mTextGridWidths[HOURS]);

        // If we have an inner circle, calculate those positions too.
        if (mIs24HourMode) {
            float innerNumbersRadius = mCircleRadius[HOURS_INNER]
                    * mNumbersRadiusMultiplier[HOURS_INNER]
                    * mAnimationRadiusMultiplier[HOURS_INNER];

            calculateGridSizes(mPaint[HOURS], innerNumbersRadius, mXCenter, mYCenter,
                    mInnerTextSize, mInnerTextGridHeights, mInnerTextGridWidths);
        }
    
private voidcalculateGridSizesMinutes()

        // Calculate the text positions
        float numbersRadius = mCircleRadius[MINUTES]
                * mNumbersRadiusMultiplier[MINUTES] * mAnimationRadiusMultiplier[MINUTES];

        // Calculate the positions for the 12 numbers in the main circle.
        calculateGridSizes(mPaint[MINUTES], numbersRadius, mXCenter, mYCenter,
                mTextSize[MINUTES], mTextGridHeights[MINUTES], mTextGridWidths[MINUTES]);
    
public booleandispatchHoverEvent(android.view.MotionEvent event)

        // First right-of-refusal goes the touch exploration helper.
        if (mTouchHelper.dispatchHoverEvent(event)) {
            return true;
        }
        return super.dispatchHoverEvent(event);
    
private voiddrawCenter(android.graphics.Canvas canvas)

        canvas.drawCircle(mXCenter, mYCenter, CENTER_RADIUS, mPaintCenter);
    
private voiddrawCircleBackground(android.graphics.Canvas canvas)

        canvas.drawCircle(mXCenter, mYCenter, mCircleRadius[HOURS], mPaintBackground);
    
private voiddrawDebug(android.graphics.Canvas canvas)

        // Draw outer numbers circle
        final float outerRadius = mCircleRadius[HOURS] * mNumbersRadiusMultiplier[HOURS];
        canvas.drawCircle(mXCenter, mYCenter, outerRadius, mPaintDebug);

        // Draw inner numbers circle
        final float innerRadius = mCircleRadius[HOURS] * mNumbersRadiusMultiplier[HOURS_INNER];
        canvas.drawCircle(mXCenter, mYCenter, innerRadius, mPaintDebug);

        // Draw outer background circle
        canvas.drawCircle(mXCenter, mYCenter, mCircleRadius[HOURS], mPaintDebug);

        // Draw outer rectangle for circles
        float left = mXCenter - outerRadius;
        float top = mYCenter - outerRadius;
        float right = mXCenter + outerRadius;
        float bottom = mYCenter + outerRadius;
        canvas.drawRect(left, top, right, bottom, mPaintDebug);

        // Draw outer rectangle for background
        left = mXCenter - mCircleRadius[HOURS];
        top = mYCenter - mCircleRadius[HOURS];
        right = mXCenter + mCircleRadius[HOURS];
        bottom = mYCenter + mCircleRadius[HOURS];
        canvas.drawRect(left, top, right, bottom, mPaintDebug);

        // Draw outer view rectangle
        canvas.drawRect(0, 0, getWidth(), getHeight(), mPaintDebug);

        // Draw selected time
        final String selected = String.format("%02d:%02d", getCurrentHour(), getCurrentMinute());

        ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
        TextView tv = new TextView(getContext());
        tv.setLayoutParams(lp);
        tv.setText(selected);
        tv.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
        Paint paint = tv.getPaint();
        paint.setColor(DEBUG_TEXT_COLOR);

        final int width = tv.getMeasuredWidth();

        float height = paint.descent() - paint.ascent();
        float x = mXCenter - width / 2;
        float y = mYCenter + 1.5f * height;

        canvas.drawText(selected, x, y, paint);
    
private voiddrawSelector(android.graphics.Canvas canvas)

        drawSelector(canvas, mIsOnInnerCircle ? HOURS_INNER : HOURS);
        drawSelector(canvas, MINUTES);
    
private voiddrawSelector(android.graphics.Canvas canvas, int index)

        // Calculate the current radius at which to place the selection circle.
        mLineLength[index] = (int) (mCircleRadius[index]
                * mNumbersRadiusMultiplier[index] * mAnimationRadiusMultiplier[index]);

        double selectionRadians = Math.toRadians(mSelectionDegrees[index]);

        int pointX = mXCenter + (int) (mLineLength[index] * Math.sin(selectionRadians));
        int pointY = mYCenter - (int) (mLineLength[index] * Math.cos(selectionRadians));

        int color;
        int alpha;
        Paint paint;

        // Draw the selection circle
        color = mColorSelector[index % 2][SELECTOR_CIRCLE];
        alpha = mAlphaSelector[index % 2][SELECTOR_CIRCLE].getValue();
        paint = mPaintSelector[index % 2][SELECTOR_CIRCLE];
        paint.setColor(color);
        paint.setAlpha(getMultipliedAlpha(color, alpha));
        canvas.drawCircle(pointX, pointY, mSelectionRadius[index], paint);

        // Draw the dot if needed
        if (mSelectionDegrees[index] % 30 != 0) {
            // We're not on a direct tick
            color = mColorSelector[index % 2][SELECTOR_DOT];
            alpha = mAlphaSelector[index % 2][SELECTOR_DOT].getValue();
            paint = mPaintSelector[index % 2][SELECTOR_DOT];
            paint.setColor(color);
            paint.setAlpha(getMultipliedAlpha(color, alpha));
            canvas.drawCircle(pointX, pointY, (mSelectionRadius[index] * 2 / 7), paint);
        } else {
            // We're not drawing the dot, so shorten the line to only go as far as the edge of the
            // selection circle
            int lineLength = mLineLength[index] - mSelectionRadius[index];
            pointX = mXCenter + (int) (lineLength * Math.sin(selectionRadians));
            pointY = mYCenter - (int) (lineLength * Math.cos(selectionRadians));
        }

        // Draw the line
        color = mColorSelector[index % 2][SELECTOR_LINE];
        alpha = mAlphaSelector[index % 2][SELECTOR_LINE].getValue();
        paint = mPaintSelector[index % 2][SELECTOR_LINE];
        paint.setColor(color);
        paint.setAlpha(getMultipliedAlpha(color, alpha));
        canvas.drawLine(mXCenter, mYCenter, pointX, pointY, paint);
    
private voiddrawTextElements(android.graphics.Canvas canvas, float textSize, android.graphics.Typeface typeface, java.lang.String[] texts, float[] textGridWidths, float[] textGridHeights, android.graphics.Paint paint, int color, int alpha)
Draw the 12 text values at the positions specified by the textGrid parameters.

        paint.setTextSize(textSize);
        paint.setTypeface(typeface);
        paint.setColor(color);
        paint.setAlpha(getMultipliedAlpha(color, alpha));
        canvas.drawText(texts[0], textGridWidths[3], textGridHeights[0], paint);
        canvas.drawText(texts[1], textGridWidths[4], textGridHeights[1], paint);
        canvas.drawText(texts[2], textGridWidths[5], textGridHeights[2], paint);
        canvas.drawText(texts[3], textGridWidths[6], textGridHeights[3], paint);
        canvas.drawText(texts[4], textGridWidths[5], textGridHeights[4], paint);
        canvas.drawText(texts[5], textGridWidths[4], textGridHeights[5], paint);
        canvas.drawText(texts[6], textGridWidths[3], textGridHeights[6], paint);
        canvas.drawText(texts[7], textGridWidths[2], textGridHeights[5], paint);
        canvas.drawText(texts[8], textGridWidths[1], textGridHeights[4], paint);
        canvas.drawText(texts[9], textGridWidths[0], textGridHeights[3], paint);
        canvas.drawText(texts[10], textGridWidths[1], textGridHeights[2], paint);
        canvas.drawText(texts[11], textGridWidths[2], textGridHeights[1], paint);
    
public intgetAmOrPm()

        return mAmOrPm;
    
public intgetCurrentHour()
Returns the current hour in 24-hour time.

return
the current hour between 0 and 23 (inclusive)

        return getHourForDegrees(
                mSelectionDegrees[mIsOnInnerCircle ? HOURS_INNER : HOURS], mIsOnInnerCircle);
    
public intgetCurrentItemShowing()

        return mShowHours ? HOURS : MINUTES;
    
public intgetCurrentMinute()

        return getMinuteForDegrees(mSelectionDegrees[MINUTES]);
    
private intgetDegreesForHour(int hour)

        // Convert to be 0-11.
        if (mIs24HourMode) {
            if (hour >= 12) {
                hour -= 12;
            }
        } else if (hour == 12) {
            hour = 0;
        }
        return hour * DEGREES_FOR_ONE_HOUR;
    
private intgetDegreesForMinute(int minute)

        return minute * DEGREES_FOR_ONE_MINUTE;
    
private intgetDegreesFromXY(float x, float y)

        final double hypotenuse = Math.sqrt(
                (y - mYCenter) * (y - mYCenter) + (x - mXCenter) * (x - mXCenter));

        // Basic check if we're outside the range of the disk
        if (hypotenuse > mCircleRadius[HOURS]) {
            return -1;
        }
        // Check
        if (mIs24HourMode && mShowHours) {
            if (hypotenuse >= mMinHypotenuseForInnerNumber
                    && hypotenuse <= mHalfwayHypotenusePoint) {
                mIsOnInnerCircle = true;
            } else if (hypotenuse <= mMaxHypotenuseForOuterNumber
                    && hypotenuse >= mHalfwayHypotenusePoint) {
                mIsOnInnerCircle = false;
            } else {
                return -1;
            }
        } else {
            final int index =  (mShowHours) ? HOURS : MINUTES;
            final float length = (mCircleRadius[index] * mNumbersRadiusMultiplier[index]);
            final int distanceToNumber = (int) Math.abs(hypotenuse - length);
            final int maxAllowedDistance =
                    (int) (mCircleRadius[index] * (1 - mNumbersRadiusMultiplier[index]));
            if (distanceToNumber > maxAllowedDistance) {
                return -1;
            }
        }

        final float opposite = Math.abs(y - mYCenter);
        int degrees = (int) (Math.toDegrees(Math.asin(opposite / hypotenuse)) + 0.5);

        // Now we have to translate to the correct quadrant.
        final boolean rightSide = (x > mXCenter);
        final boolean topSide = (y < mYCenter);
        if (rightSide) {
            if (topSide) {
                degrees = 90 - degrees;
            } else {
                degrees = 90 + degrees;
            }
        } else {
            if (topSide) {
                degrees = 270 + degrees;
            } else {
                degrees = 270 - degrees;
            }
        }
        return degrees;
    
private static android.animation.ObjectAnimatorgetFadeInAnimator(android.widget.RadialTimePickerView$IntHolder target, int startAlpha, int endAlpha, android.widget.RadialTimePickerView$InvalidateUpdateListener updateListener)

        Keyframe kf0, kf1, kf2;
        int duration = 500;

        // Set up animator for reappearing.
        float delayMultiplier = 0.25f;
        float transitionDurationMultiplier = 1f;
        float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier;
        int totalDuration = (int) (duration * totalDurationMultiplier);
        float delayPoint = (delayMultiplier * duration) / totalDuration;

        kf0 = Keyframe.ofInt(0f, startAlpha);
        kf1 = Keyframe.ofInt(delayPoint, startAlpha);
        kf2 = Keyframe.ofInt(1f, endAlpha);
        PropertyValuesHolder fadeIn = PropertyValuesHolder.ofKeyframe("value", kf0, kf1, kf2);

        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(
                target, fadeIn).setDuration(totalDuration);
        animator.addUpdateListener(updateListener);
        return animator;
    
private static android.animation.ObjectAnimatorgetFadeOutAnimator(android.widget.RadialTimePickerView$IntHolder target, int startAlpha, int endAlpha, android.widget.RadialTimePickerView$InvalidateUpdateListener updateListener)

        int duration = 500;
        ObjectAnimator animator = ObjectAnimator.ofInt(target, "value", startAlpha, endAlpha);
        animator.setDuration(duration);
        animator.addUpdateListener(updateListener);

        return animator;
    
private intgetHourForDegrees(int degrees, boolean innerCircle)

        int hour = (degrees / DEGREES_FOR_ONE_HOUR) % 12;
        if (mIs24HourMode) {
            // Convert the 12-hour value into 24-hour time based on where the
            // selector is positioned.
            if (innerCircle && hour == 0) {
                // Inner circle is 1 through 12.
                hour = 12;
            } else if (!innerCircle && hour != 0) {
                // Outer circle is 13 through 23 and 0.
                hour += 12;
            }
        } else if (mAmOrPm == PM) {
            hour += 12;
        }
        return hour;
    
private intgetMinuteForDegrees(int degrees)

        return degrees / DEGREES_FOR_ONE_MINUTE;
    
private intgetMultipliedAlpha(int argb, int alpha)

        return (int) (Color.alpha(argb) * (alpha / 255.0) + 0.5);
    
private static android.animation.ObjectAnimatorgetRadiusDisappearAnimator(java.lang.Object target, java.lang.String radiusPropertyName, android.widget.RadialTimePickerView$InvalidateUpdateListener updateListener, float midRadiusMultiplier, float endRadiusMultiplier)

        Keyframe kf0, kf1, kf2;
        float midwayPoint = 0.2f;
        int duration = 500;

        kf0 = Keyframe.ofFloat(0f, 1);
        kf1 = Keyframe.ofFloat(midwayPoint, midRadiusMultiplier);
        kf2 = Keyframe.ofFloat(1f, endRadiusMultiplier);
        PropertyValuesHolder radiusDisappear = PropertyValuesHolder.ofKeyframe(
                radiusPropertyName, kf0, kf1, kf2);

        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(
                target, radiusDisappear).setDuration(duration);
        animator.addUpdateListener(updateListener);
        return animator;
    
private static android.animation.ObjectAnimatorgetRadiusReappearAnimator(java.lang.Object target, java.lang.String radiusPropertyName, android.widget.RadialTimePickerView$InvalidateUpdateListener updateListener, float midRadiusMultiplier, float endRadiusMultiplier)

        Keyframe kf0, kf1, kf2, kf3;
        float midwayPoint = 0.2f;
        int duration = 500;

        // Set up animator for reappearing.
        float delayMultiplier = 0.25f;
        float transitionDurationMultiplier = 1f;
        float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier;
        int totalDuration = (int) (duration * totalDurationMultiplier);
        float delayPoint = (delayMultiplier * duration) / totalDuration;
        midwayPoint = 1 - (midwayPoint * (1 - delayPoint));

        kf0 = Keyframe.ofFloat(0f, endRadiusMultiplier);
        kf1 = Keyframe.ofFloat(delayPoint, endRadiusMultiplier);
        kf2 = Keyframe.ofFloat(midwayPoint, midRadiusMultiplier);
        kf3 = Keyframe.ofFloat(1f, 1);
        PropertyValuesHolder radiusReappear = PropertyValuesHolder.ofKeyframe(
                radiusPropertyName, kf0, kf1, kf2, kf3);

        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(
                target, radiusReappear).setDuration(totalDuration);
        animator.addUpdateListener(updateListener);
        return animator;
    
private booleanhandleTouchInput(float x, float y, boolean forceSelection, boolean autoAdvance)

        // Calling getDegreesFromXY has side effects, so cache
        // whether we used to be on the inner circle.
        final boolean wasOnInnerCircle = mIsOnInnerCircle;
        final int degrees = getDegreesFromXY(x, y);
        if (degrees == -1) {
            return false;
        }

        final int[] selectionDegrees = mSelectionDegrees;
        final int type;
        final int newValue;
        final boolean valueChanged;

        if (mShowHours) {
            final int snapDegrees = snapOnly30s(degrees, 0) % 360;
            valueChanged = selectionDegrees[HOURS] != snapDegrees
                    || selectionDegrees[HOURS_INNER] != snapDegrees
                    || wasOnInnerCircle != mIsOnInnerCircle;

            selectionDegrees[HOURS] = snapDegrees;
            selectionDegrees[HOURS_INNER] = snapDegrees;
            type = HOURS;
            newValue = getCurrentHour();
        } else {
            final int snapDegrees = snapPrefer30s(degrees) % 360;
            valueChanged = selectionDegrees[MINUTES] != snapDegrees;

            selectionDegrees[MINUTES] = snapDegrees;
            type = MINUTES;
            newValue = getCurrentMinute();
        }

        if (valueChanged || forceSelection || autoAdvance) {
            // Fire the listener even if we just need to auto-advance.
            if (mListener != null) {
                mListener.onValueSelected(type, newValue, autoAdvance);
            }

            // Only provide feedback if the value actually changed.
            if (valueChanged || forceSelection) {
                performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
                invalidate();
            }
            return true;
        }

        return false;
    
private voidinitData()

        if (mIs24HourMode) {
            mOuterTextHours = mOuterHours24Texts;
            mInnerTextHours = mInnerHours24Texts;
        } else {
            mOuterTextHours = mHours12Texts;
            mInnerTextHours = null;
        }

        mOuterTextMinutes = mMinutesTexts;

        final Resources res = getResources();

        if (mShowHours) {
            if (mIs24HourMode) {
                mCircleRadiusMultiplier[HOURS] = Float.parseFloat(
                        res.getString(R.string.timepicker_circle_radius_multiplier_24HourMode));
                mNumbersRadiusMultiplier[HOURS] = Float.parseFloat(
                        res.getString(R.string.timepicker_numbers_radius_multiplier_outer));
                mTextSizeMultiplier[HOURS] = Float.parseFloat(
                        res.getString(R.string.timepicker_text_size_multiplier_outer));

                mNumbersRadiusMultiplier[HOURS_INNER] = Float.parseFloat(
                        res.getString(R.string.timepicker_numbers_radius_multiplier_inner));
                mTextSizeMultiplier[HOURS_INNER] = Float.parseFloat(
                        res.getString(R.string.timepicker_text_size_multiplier_inner));
            } else {
                mCircleRadiusMultiplier[HOURS] = Float.parseFloat(
                        res.getString(R.string.timepicker_circle_radius_multiplier));
                mNumbersRadiusMultiplier[HOURS] = Float.parseFloat(
                        res.getString(R.string.timepicker_numbers_radius_multiplier_normal));
                mTextSizeMultiplier[HOURS] = Float.parseFloat(
                        res.getString(R.string.timepicker_text_size_multiplier_normal));
            }
        } else {
            mCircleRadiusMultiplier[MINUTES] = Float.parseFloat(
                    res.getString(R.string.timepicker_circle_radius_multiplier));
            mNumbersRadiusMultiplier[MINUTES] = Float.parseFloat(
                    res.getString(R.string.timepicker_numbers_radius_multiplier_normal));
            mTextSizeMultiplier[MINUTES] = Float.parseFloat(
                    res.getString(R.string.timepicker_text_size_multiplier_normal));
        }

        mAnimationRadiusMultiplier[HOURS] = 1;
        mAnimationRadiusMultiplier[HOURS_INNER] = 1;
        mAnimationRadiusMultiplier[MINUTES] = 1;

        mAlpha[HOURS].setValue(mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT);
        mAlpha[MINUTES].setValue(mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE);

        mAlphaSelector[HOURS][SELECTOR_CIRCLE].setValue(
                mShowHours ? ALPHA_SELECTOR : ALPHA_TRANSPARENT);
        mAlphaSelector[HOURS][SELECTOR_DOT].setValue(
                mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT);
        mAlphaSelector[HOURS][SELECTOR_LINE].setValue(
                mShowHours ? ALPHA_SELECTOR : ALPHA_TRANSPARENT);

        mAlphaSelector[MINUTES][SELECTOR_CIRCLE].setValue(
                mShowHours ? ALPHA_TRANSPARENT : ALPHA_SELECTOR);
        mAlphaSelector[MINUTES][SELECTOR_DOT].setValue(
                mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE);
        mAlphaSelector[MINUTES][SELECTOR_LINE].setValue(
                mShowHours ? ALPHA_TRANSPARENT : ALPHA_SELECTOR);
    
private voidinitHoursAndMinutesText()

        // Initialize the hours and minutes numbers.
        for (int i = 0; i < 12; i++) {
            mHours12Texts[i] = String.format("%d", HOURS_NUMBERS[i]);
            mOuterHours24Texts[i] = String.format("%02d", HOURS_NUMBERS_24[i]);
            mInnerHours24Texts[i] = String.format("%d", HOURS_NUMBERS[i]);
            mMinutesTexts[i] = String.format("%02d", MINUTES_NUMBERS[i]);
        }
    
public voidinitialize(int hour, int minute, boolean is24HourMode)

        if (mIs24HourMode != is24HourMode) {
            mIs24HourMode = is24HourMode;
            initData();
        }

        setCurrentHourInternal(hour, false, false);
        setCurrentMinuteInternal(minute, false);
    
public voidonDraw(android.graphics.Canvas canvas)

        if (!mInputEnabled) {
            canvas.saveLayerAlpha(0, 0, getWidth(), getHeight(), mDisabledAlpha);
        } else {
            canvas.save();
        }

        calculateGridSizesHours();
        calculateGridSizesMinutes();

        drawCircleBackground(canvas);
        drawSelector(canvas);

        drawTextElements(canvas, mTextSize[HOURS], mTypeface, mOuterTextHours,
                mTextGridWidths[HOURS], mTextGridHeights[HOURS], mPaint[HOURS],
                mColor[HOURS], mAlpha[HOURS].getValue());

        if (mIs24HourMode && mInnerTextHours != null) {
            drawTextElements(canvas, mInnerTextSize, mTypeface, mInnerTextHours,
                    mInnerTextGridWidths, mInnerTextGridHeights, mPaint[HOURS],
                    mColor[HOURS], mAlpha[HOURS].getValue());
        }

        drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mOuterTextMinutes,
                mTextGridWidths[MINUTES], mTextGridHeights[MINUTES], mPaint[MINUTES],
                mColor[MINUTES], mAlpha[MINUTES].getValue());

        drawCenter(canvas);

        if (DEBUG) {
            drawDebug(canvas);
        }

        canvas.restore();
    
protected voidonLayout(boolean changed, int left, int top, int right, int bottom)

        updateLayoutData();
    
public voidonMeasure(int widthMeasureSpec, int heightMeasureSpec)
Measure the view to end up as a square, based on the minimum of the height and width.

        int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int minDimension = Math.min(measuredWidth, measuredHeight);

        super.onMeasure(MeasureSpec.makeMeasureSpec(minDimension, widthMode),
                MeasureSpec.makeMeasureSpec(minDimension, heightMode));
    
public booleanonTouch(android.view.View v, android.view.MotionEvent event)


    
          
        if (!mInputEnabled) {
            return true;
        }

        final int action = event.getActionMasked();
        if (action == MotionEvent.ACTION_MOVE
                || action == MotionEvent.ACTION_UP
                || action == MotionEvent.ACTION_DOWN) {
            boolean forceSelection = false;
            boolean autoAdvance = false;

            if (action == MotionEvent.ACTION_DOWN) {
                // This is a new event stream, reset whether the value changed.
                mChangedDuringTouch = false;
            } else if (action == MotionEvent.ACTION_UP) {
                autoAdvance = true;

                // If we saw a down/up pair without the value changing, assume
                // this is a single-tap selection and force a change.
                if (!mChangedDuringTouch) {
                    forceSelection = true;
                }
            }

            mChangedDuringTouch |= handleTouchInput(
                    event.getX(), event.getY(), forceSelection, autoAdvance);
        }

        return true;
    
private static voidpreparePrefer30sMap()
Split up the 360 degrees of the circle among the 60 selectable values. Assigns a larger selectable area to each of the 12 visible values, such that the ratio of space apportioned to a visible value : space apportioned to a non-visible value will be 14 : 4. E.g. the output of 30 degrees should have a higher range of input associated with it than the output of 24 degrees, because 30 degrees corresponds to a visible number on the clock circle (5 on the minutes, 1 or 13 on the hours).


       
              
    

     
        // Prepare mapping to snap touchable degrees to selectable degrees.
        preparePrefer30sMap();
    
        // We'll split up the visible output and the non-visible output such that each visible
        // output will correspond to a range of 14 associated input degrees, and each non-visible
        // output will correspond to a range of 4 associate input degrees, so visible numbers
        // are more than 3 times easier to get than non-visible numbers:
        // {354-359,0-7}:0, {8-11}:6, {12-15}:12, {16-19}:18, {20-23}:24, {24-37}:30, etc.
        //
        // If an output of 30 degrees should correspond to a range of 14 associated degrees, then
        // we'll need any input between 24 - 37 to snap to 30. Working out from there, 20-23 should
        // snap to 24, while 38-41 should snap to 36. This is somewhat counter-intuitive, that you
        // can be touching 36 degrees but have the selection snapped to 30 degrees; however, this
        // inconsistency isn't noticeable at such fine-grained degrees, and it affords us the
        // ability to aggressively prefer the visible values by a factor of more than 3:1, which
        // greatly contributes to the selectability of these values.

        // The first output is 0, and each following output will increment by 6 {0, 6, 12, ...}.
        int snappedOutputDegrees = 0;
        // Count of how many inputs we've designated to the specified output.
        int count = 1;
        // How many input we expect for a specified output. This will be 14 for output divisible
        // by 30, and 4 for the remaining output. We'll special case the outputs of 0 and 360, so
        // the caller can decide which they need.
        int expectedCount = 8;
        // Iterate through the input.
        for (int degrees = 0; degrees < 361; degrees++) {
            // Save the input-output mapping.
            sSnapPrefer30sMap[degrees] = snappedOutputDegrees;
            // If this is the last input for the specified output, calculate the next output and
            // the next expected count.
            if (count == expectedCount) {
                snappedOutputDegrees += 6;
                if (snappedOutputDegrees == 360) {
                    expectedCount = 7;
                } else if (snappedOutputDegrees % 30 == 0) {
                    expectedCount = 14;
                } else {
                    expectedCount = 4;
                }
                count = 1;
            } else {
                count++;
            }
        }
    
public voidsetAmOrPm(int val)

        mAmOrPm = (val % 2);
        invalidate();
        mTouchHelper.invalidateRoot();
    
private voidsetAnimationRadiusMultiplierHours(float animationRadiusMultiplier)

        mAnimationRadiusMultiplier[HOURS] = animationRadiusMultiplier;
        mAnimationRadiusMultiplier[HOURS_INNER] = animationRadiusMultiplier;
    
private voidsetAnimationRadiusMultiplierMinutes(float animationRadiusMultiplier)

        mAnimationRadiusMultiplier[MINUTES] = animationRadiusMultiplier;
    
public voidsetCurrentHour(int hour)
Sets the current hour in 24-hour time.

param
hour the current hour between 0 and 23 (inclusive)

        setCurrentHourInternal(hour, true, false);
    
private voidsetCurrentHourInternal(int hour, boolean callback, boolean autoAdvance)
Sets the current hour.

param
hour The current hour
param
callback Whether the value listener should be invoked
param
autoAdvance Whether the listener should auto-advance to the next selection mode, e.g. hour to minutes

        final int degrees = (hour % 12) * DEGREES_FOR_ONE_HOUR;
        mSelectionDegrees[HOURS] = degrees;
        mSelectionDegrees[HOURS_INNER] = degrees;

        // 0 is 12 AM (midnight) and 12 is 12 PM (noon).
        final int amOrPm = (hour == 0 || (hour % 24) < 12) ? AM : PM;
        final boolean isOnInnerCircle = mIs24HourMode && hour >= 1 && hour <= 12;
        if (mAmOrPm != amOrPm || mIsOnInnerCircle != isOnInnerCircle) {
            mAmOrPm = amOrPm;
            mIsOnInnerCircle = isOnInnerCircle;

            initData();
            updateLayoutData();
            mTouchHelper.invalidateRoot();
        }

        invalidate();

        if (callback && mListener != null) {
            mListener.onValueSelected(HOURS, hour, autoAdvance);
        }
    
public voidsetCurrentItemShowing(int item, boolean animate)

        switch (item){
            case HOURS:
                showHours(animate);
                break;
            case MINUTES:
                showMinutes(animate);
                break;
            default:
                Log.e(TAG, "ClockView does not support showing item " + item);
        }
    
public voidsetCurrentMinute(int minute)

        setCurrentMinuteInternal(minute, true);
    
private voidsetCurrentMinuteInternal(int minute, boolean callback)

        mSelectionDegrees[MINUTES] = (minute % 60) * DEGREES_FOR_ONE_MINUTE;

        invalidate();

        if (callback && mListener != null) {
            mListener.onValueSelected(MINUTES, minute, false);
        }
    
public voidsetInputEnabled(boolean inputEnabled)

        mInputEnabled = inputEnabled;
        invalidate();
    
public voidsetOnValueSelectedListener(android.widget.RadialTimePickerView$OnValueSelectedListener listener)

        mListener = listener;
    
public voidshowHours(boolean animate)

        if (mShowHours) return;
        mShowHours = true;
        if (animate) {
            startMinutesToHoursAnimation();
        }
        initData();
        updateLayoutData();
        invalidate();
    
public voidshowMinutes(boolean animate)

        if (!mShowHours) return;
        mShowHours = false;
        if (animate) {
            startHoursToMinutesAnimation();
        }
        initData();
        updateLayoutData();
        invalidate();
    
private static intsnapOnly30s(int degrees, int forceHigherOrLower)
Returns mapping of any input degrees (0 to 360) to one of 12 visible output degrees (all multiples of 30), where the input will be "snapped" to the closest visible degrees.

param
degrees The input degrees
param
forceHigherOrLower The output may be forced to either the higher or lower step, or may be allowed to snap to whichever is closer. Use 1 to force strictly higher, -1 to force strictly lower, and 0 to snap to the closer one.
return
output degrees, will be a multiple of 30

        final int stepSize = DEGREES_FOR_ONE_HOUR;
        int floor = (degrees / stepSize) * stepSize;
        final int ceiling = floor + stepSize;
        if (forceHigherOrLower == 1) {
            degrees = ceiling;
        } else if (forceHigherOrLower == -1) {
            if (degrees == floor) {
                floor -= stepSize;
            }
            degrees = floor;
        } else {
            if ((degrees - floor) < (ceiling - degrees)) {
                degrees = floor;
            } else {
                degrees = ceiling;
            }
        }
        return degrees;
    
private static intsnapPrefer30s(int degrees)
Returns mapping of any input degrees (0 to 360) to one of 60 selectable output degrees, where the degrees corresponding to visible numbers (i.e. those divisible by 30) will be weighted heavier than the degrees corresponding to non-visible numbers. See {@link #preparePrefer30sMap()} documentation for the rationale and generation of the mapping.

        if (sSnapPrefer30sMap == null) {
            return -1;
        }
        return sSnapPrefer30sMap[degrees];
    
private voidstartHoursToMinutesAnimation()

        if (mHoursToMinutesAnims.size() == 0) {
            mHoursToMinutesAnims.add(getRadiusDisappearAnimator(this,
                    "animationRadiusMultiplierHours", mInvalidateUpdateListener,
                    mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
            mHoursToMinutesAnims.add(getFadeOutAnimator(mAlpha[HOURS],
                    ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
            mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_CIRCLE],
                    ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
            mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_DOT],
                    ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
            mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_LINE],
                    ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));

            mHoursToMinutesAnims.add(getRadiusReappearAnimator(this,
                    "animationRadiusMultiplierMinutes", mInvalidateUpdateListener,
                    mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
            mHoursToMinutesAnims.add(getFadeInAnimator(mAlpha[MINUTES],
                    ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
            mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_CIRCLE],
                    ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
            mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_DOT],
                    ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
            mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_LINE],
                    ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
        }

        if (mTransition != null && mTransition.isRunning()) {
            mTransition.end();
        }
        mTransition = new AnimatorSet();
        mTransition.playTogether(mHoursToMinutesAnims);
        mTransition.start();
    
private voidstartMinutesToHoursAnimation()

        if (mMinuteToHoursAnims.size() == 0) {
            mMinuteToHoursAnims.add(getRadiusDisappearAnimator(this,
                    "animationRadiusMultiplierMinutes", mInvalidateUpdateListener,
                    mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
            mMinuteToHoursAnims.add(getFadeOutAnimator(mAlpha[MINUTES],
                    ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
            mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_CIRCLE],
                    ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
            mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_DOT],
                    ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
            mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_LINE],
                    ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));

            mMinuteToHoursAnims.add(getRadiusReappearAnimator(this,
                    "animationRadiusMultiplierHours", mInvalidateUpdateListener,
                    mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
            mMinuteToHoursAnims.add(getFadeInAnimator(mAlpha[HOURS],
                    ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
            mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_CIRCLE],
                    ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
            mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_DOT],
                    ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
            mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_LINE],
                    ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
        }

        if (mTransition != null && mTransition.isRunning()) {
            mTransition.end();
        }
        mTransition = new AnimatorSet();
        mTransition.playTogether(mMinuteToHoursAnims);
        mTransition.start();
    
private voidupdateLayoutData()

        mXCenter = getWidth() / 2;
        mYCenter = getHeight() / 2;

        final int min = Math.min(mXCenter, mYCenter);

        mCircleRadius[HOURS] = min * mCircleRadiusMultiplier[HOURS];
        mCircleRadius[HOURS_INNER] = min * mCircleRadiusMultiplier[HOURS];
        mCircleRadius[MINUTES] = min * mCircleRadiusMultiplier[MINUTES];

        mMinHypotenuseForInnerNumber = (int) (mCircleRadius[HOURS]
                * mNumbersRadiusMultiplier[HOURS_INNER]) - mSelectionRadius[HOURS];
        mMaxHypotenuseForOuterNumber = (int) (mCircleRadius[HOURS]
                * mNumbersRadiusMultiplier[HOURS]) + mSelectionRadius[HOURS];
        mHalfwayHypotenusePoint = (int) (mCircleRadius[HOURS]
                * ((mNumbersRadiusMultiplier[HOURS] + mNumbersRadiusMultiplier[HOURS_INNER]) / 2));

        mTextSize[HOURS] = mCircleRadius[HOURS] * mTextSizeMultiplier[HOURS];
        mTextSize[MINUTES] = mCircleRadius[MINUTES] * mTextSizeMultiplier[MINUTES];

        if (mIs24HourMode) {
            mInnerTextSize = mCircleRadius[HOURS] * mTextSizeMultiplier[HOURS_INNER];
        }

        calculateGridSizesHours();
        calculateGridSizesMinutes();

        mSelectionRadius[HOURS] = (int) (mCircleRadius[HOURS] * mSelectionRadiusMultiplier);
        mSelectionRadius[HOURS_INNER] = mSelectionRadius[HOURS];
        mSelectionRadius[MINUTES] = (int) (mCircleRadius[MINUTES] * mSelectionRadiusMultiplier);

        mTouchHelper.invalidateRoot();