16e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein/*
26e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein * Copyright (C) 2013 The Android Open Source Project
36e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein *
46e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein * Licensed under the Apache License, Version 2.0 (the "License");
56e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein * you may not use this file except in compliance with the License.
66e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein * You may obtain a copy of the License at
76e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein *
86e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein *      http://www.apache.org/licenses/LICENSE-2.0
96e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein *
106e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein * Unless required by applicable law or agreed to in writing, software
116e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein * distributed under the License is distributed on an "AS IS" BASIS,
126e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
136e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein * See the License for the specific language governing permissions and
146e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein * limitations under the License.
156e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein */
166e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
17b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzsteinpackage com.android.datetimepicker.time;
186e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
196e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.animation.Keyframe;
206e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.animation.ObjectAnimator;
216e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.animation.PropertyValuesHolder;
226e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.animation.ValueAnimator;
236e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.animation.ValueAnimator.AnimatorUpdateListener;
246e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.content.Context;
256e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.content.res.Resources;
266e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.graphics.Canvas;
276e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.graphics.Paint;
286e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.util.Log;
296e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.view.View;
306e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
316e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport com.android.datetimepicker.R;
321f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzsteinimport com.android.datetimepicker.Utils;
336e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
34f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein/**
35f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein * View to show what number is selected. This will draw a blue circle over the number, with a blue
36f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein * line coming from the center of the main circle to the edge of the blue selection.
37f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein */
386e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinpublic class RadialSelectorView extends View {
396e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private static final String TAG = "RadialSelectorView";
406e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
411f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein    // Alpha level for selected circle.
421f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein    private static final int SELECTED_ALPHA = Utils.SELECTED_ALPHA;
431f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein    private static final int SELECTED_ALPHA_THEME_DARK = Utils.SELECTED_ALPHA_THEME_DARK;
441f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein    // Alpha level for the line.
451f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein    private static final int FULL_ALPHA = Utils.FULL_ALPHA;
461f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein
476e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private final Paint mPaint = new Paint();
486e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
496e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mIsInitialized;
506e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mDrawValuesReady;
516e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
526e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mCircleRadiusMultiplier;
536e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mAmPmCircleRadiusMultiplier;
546e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mInnerNumbersRadiusMultiplier;
556e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mOuterNumbersRadiusMultiplier;
566e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mNumbersRadiusMultiplier;
576e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mSelectionRadiusMultiplier;
586e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mAnimationRadiusMultiplier;
596e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mIs24HourMode;
606e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mHasInnerCircle;
611f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein    private int mSelectionAlpha;
626e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
636e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mXCenter;
646e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mYCenter;
656e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mCircleRadius;
666e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mTransitionMidRadiusMultiplier;
676e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mTransitionEndRadiusMultiplier;
686e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mLineLength;
696e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mSelectionRadius;
706e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private InvalidateUpdateListener mInvalidateUpdateListener;
716e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
726e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mSelectionDegrees;
736e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private double mSelectionRadians;
746e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mForceDrawDot;
756e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
766e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public RadialSelectorView(Context context) {
776e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        super(context);
786e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mIsInitialized = false;
796e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
806e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
81f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
82f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Initialize this selector with the state of the picker.
83f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param context Current context.
84f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param is24HourMode Whether the selector is in 24-hour mode, which will tell us
85f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * whether the circle's center is moved up slightly to make room for the AM/PM circles.
86f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param hasInnerCircle Whether we have both an inner and an outer circle of numbers
87f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * that may be selected. Should be true for 24-hour mode in the hours circle.
88f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param disappearsOut Whether the numbers' animation will have them disappearing out
89f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * or disappearing in.
90f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param selectionDegrees The initial degrees to be selected.
91f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param isInnerCircle Whether the initial selection is in the inner or outer circle.
92f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Will be ignored when hasInnerCircle is false.
93f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
94b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    public void initialize(Context context, boolean is24HourMode, boolean hasInnerCircle,
95b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            boolean disappearsOut, int selectionDegrees, boolean isInnerCircle) {
966e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (mIsInitialized) {
976e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            Log.e(TAG, "This RadialSelectorView may only be initialized once.");
986e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return;
996e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
1006e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1016e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        Resources res = context.getResources();
1026e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1036e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int blue = res.getColor(R.color.blue);
1046e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mPaint.setColor(blue);
1056e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mPaint.setAntiAlias(true);
1061f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein        mSelectionAlpha = SELECTED_ALPHA;
1076e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
108f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Calculate values for the circle radius size.
1096e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mIs24HourMode = is24HourMode;
1106e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (is24HourMode) {
1116e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mCircleRadiusMultiplier = Float.parseFloat(
1126e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    res.getString(R.string.circle_radius_multiplier_24HourMode));
1136e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else {
1146e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mCircleRadiusMultiplier = Float.parseFloat(
1156e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    res.getString(R.string.circle_radius_multiplier));
1166e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mAmPmCircleRadiusMultiplier =
1176e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    Float.parseFloat(res.getString(R.string.ampm_circle_radius_multiplier));
1186e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
1196e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
120f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Calculate values for the radius size(s) of the numbers circle(s).
1216e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mHasInnerCircle = hasInnerCircle;
1226e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (hasInnerCircle) {
1236e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mInnerNumbersRadiusMultiplier =
1246e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    Float.parseFloat(res.getString(R.string.numbers_radius_multiplier_inner));
1256e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mOuterNumbersRadiusMultiplier =
1266e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    Float.parseFloat(res.getString(R.string.numbers_radius_multiplier_outer));
1276e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else {
1286e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mNumbersRadiusMultiplier =
1296e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    Float.parseFloat(res.getString(R.string.numbers_radius_multiplier_normal));
1306e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
1316e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mSelectionRadiusMultiplier =
1326e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                Float.parseFloat(res.getString(R.string.selection_radius_multiplier));
1336e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
134f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Calculate values for the transition mid-way states.
1356e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mAnimationRadiusMultiplier = 1;
1366e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mTransitionMidRadiusMultiplier = 1f + (0.05f * (disappearsOut? -1 : 1));
1376e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mTransitionEndRadiusMultiplier = 1f + (0.3f * (disappearsOut? 1 : -1));
1386e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mInvalidateUpdateListener = new InvalidateUpdateListener();
1396e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
140f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        setSelection(selectionDegrees, isInnerCircle, false);
1416e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mIsInitialized = true;
1426e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
1436e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1441f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein    /* package */ void setTheme(Context context, boolean themeDark) {
1451f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein        Resources res = context.getResources();
1461f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein        int color;
1471f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein        if (themeDark) {
1481f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein            color = res.getColor(R.color.red);
1491f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein            mSelectionAlpha = SELECTED_ALPHA_THEME_DARK;
1501f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein        } else {
1511f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein            color = res.getColor(R.color.blue);
1521f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein            mSelectionAlpha = SELECTED_ALPHA;
1531f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein        }
1541f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein        mPaint.setColor(color);
1551f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein    }
1561f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein
157f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
158f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Set the selection.
159f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param selectionDegrees The degrees to be selected.
160f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param isInnerCircle Whether the selection should be in the inner circle or outer. Will be
161f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * ignored if hasInnerCircle was initialized to false.
162f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param forceDrawDot Whether to force the dot in the center of the selection circle to be
163f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * drawn. If false, the dot will be drawn only when the degrees is not a multiple of 30, i.e.
164f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * the selection is not on a visible number.
165f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
166f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    public void setSelection(int selectionDegrees, boolean isInnerCircle, boolean forceDrawDot) {
1676e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mSelectionDegrees = selectionDegrees;
1686e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mSelectionRadians = selectionDegrees * Math.PI / 180;
1696e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mForceDrawDot = forceDrawDot;
1706e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1716e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (mHasInnerCircle) {
1726e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            if (isInnerCircle) {
1736e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                mNumbersRadiusMultiplier = mInnerNumbersRadiusMultiplier;
1746e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            } else {
1756e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                mNumbersRadiusMultiplier = mOuterNumbersRadiusMultiplier;
1766e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            }
1776e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
1786e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
1796e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
180f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
181f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Allows for smoother animations.
182f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
1833d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein    @Override
1843d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein    public boolean hasOverlappingRendering() {
1853d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein        return false;
1863d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein    }
1873d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein
188f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
189f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Set the multiplier for the radius. Will be used during animations to move in/out.
190f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
1916e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public void setAnimationRadiusMultiplier(float animationRadiusMultiplier) {
1926e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mAnimationRadiusMultiplier = animationRadiusMultiplier;
1936e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
1946e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1956e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public int getDegreesFromCoords(float pointX, float pointY, boolean forceLegal,
1966e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            final Boolean[] isInnerCircle) {
1976e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (!mDrawValuesReady) {
1986e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return -1;
1996e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
2006e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2016e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        double hypotenuse = Math.sqrt(
2026e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                (pointY - mYCenter)*(pointY - mYCenter) +
2036e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                (pointX - mXCenter)*(pointX - mXCenter));
204f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Check if we're outside the range
2056e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (mHasInnerCircle) {
2066e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            if (forceLegal) {
2076e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // If we're told to force the coordinates to be legal, we'll set the isInnerCircle
2086e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // boolean based based off whichever number the coordinates are closer to.
2096e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int innerNumberRadius = (int) (mCircleRadius * mInnerNumbersRadiusMultiplier);
2106e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int distanceToInnerNumber = (int) Math.abs(hypotenuse - innerNumberRadius);
2116e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int outerNumberRadius = (int) (mCircleRadius * mOuterNumbersRadiusMultiplier);
2126e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int distanceToOuterNumber = (int) Math.abs(hypotenuse - outerNumberRadius);
2136e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2146e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                isInnerCircle[0] = (distanceToInnerNumber <= distanceToOuterNumber);
2156e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            } else {
2166e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // Otherwise, if we're close enough to either number (with the space between the
2176e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // two allotted equally), set the isInnerCircle boolean as the closer one.
2186e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // appropriately, but otherwise return -1.
2196e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int minAllowedHypotenuseForInnerNumber =
2206e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        (int) (mCircleRadius * mInnerNumbersRadiusMultiplier) - mSelectionRadius;
2216e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int maxAllowedHypotenuseForOuterNumber =
2226e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        (int) (mCircleRadius * mOuterNumbersRadiusMultiplier) + mSelectionRadius;
2236e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int halfwayHypotenusePoint = (int) (mCircleRadius *
2246e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        ((mOuterNumbersRadiusMultiplier + mInnerNumbersRadiusMultiplier) / 2));
2256e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2266e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                if (hypotenuse >= minAllowedHypotenuseForInnerNumber &&
2276e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        hypotenuse <= halfwayHypotenusePoint) {
2286e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    isInnerCircle[0] = true;
2296e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                } else if (hypotenuse <= maxAllowedHypotenuseForOuterNumber &&
2306e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        hypotenuse >= halfwayHypotenusePoint) {
2316e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    isInnerCircle[0] = false;
2326e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                } else {
2336e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    return -1;
2346e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                }
2356e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            }
2366e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else {
2376e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            // If there's just one circle, we'll need to return -1 if:
2386e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            // we're not told to force the coordinates to be legal, and
2396e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            // the coordinates' distance to the number is within the allowed distance.
2406e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            if (!forceLegal) {
2416e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int distanceToNumber = (int) Math.abs(hypotenuse - mLineLength);
2426e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // The max allowed distance will be defined as the distance from the center of the
2436e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // number to the edge of the circle.
2446e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int maxAllowedDistance = (int) (mCircleRadius * (1 - mNumbersRadiusMultiplier));
2456e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                if (distanceToNumber > maxAllowedDistance) {
2466e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    return -1;
2476e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                }
2486e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            }
2496e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
2506e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2516e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2526e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float opposite = Math.abs(pointY - mYCenter);
2536e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        double radians = Math.asin(opposite / hypotenuse);
2546e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int degrees = (int) (radians * 180 / Math.PI);
2556e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2566e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        // Now we have to translate to the correct quadrant.
2576e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        boolean rightSide = (pointX > mXCenter);
2586e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        boolean topSide = (pointY < mYCenter);
2596e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (rightSide && topSide) {
2606e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            degrees = 90 - degrees;
2616e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else if (rightSide && !topSide) {
2626e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            degrees = 90 + degrees;
2636e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else if (!rightSide && !topSide) {
2646e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            degrees = 270 - degrees;
2656e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else if (!rightSide && topSide) {
2666e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            degrees = 270 + degrees;
2676e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
2686e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        return degrees;
2696e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
2706e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2716e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    @Override
2726e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public void onDraw(Canvas canvas) {
2736e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int viewWidth = getWidth();
2746e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (viewWidth == 0 || !mIsInitialized) {
2756e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return;
2766e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
2776e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2786e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (!mDrawValuesReady) {
2796e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mXCenter = getWidth() / 2;
2806e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mYCenter = getHeight() / 2;
2816e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mCircleRadius = (int) (Math.min(mXCenter, mYCenter) * mCircleRadiusMultiplier);
2826e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2836e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            if (!mIs24HourMode) {
2846e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // We'll need to draw the AM/PM circles, so the main circle will need to have
2856e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // a slightly higher center. To keep the entire view centered vertically, we'll
2866e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // have to push it up by half the radius of the AM/PM circles.
2876e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int amPmCircleRadius = (int) (mCircleRadius * mAmPmCircleRadiusMultiplier);
2886e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                mYCenter -= amPmCircleRadius / 2;
2896e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            }
2906e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2916e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mSelectionRadius = (int) (mCircleRadius * mSelectionRadiusMultiplier);
2926e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2936e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mDrawValuesReady = true;
2946e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
2956e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
296f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Calculate the current radius at which to place the selection circle.
2976e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mLineLength = (int) (mCircleRadius * mNumbersRadiusMultiplier * mAnimationRadiusMultiplier);
2986e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int pointX = mXCenter + (int) (mLineLength * Math.sin(mSelectionRadians));
2996e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int pointY = mYCenter - (int) (mLineLength * Math.cos(mSelectionRadians));
3006e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
301f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Draw the selection circle.
3021f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein        mPaint.setAlpha(mSelectionAlpha);
3036e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        canvas.drawCircle(pointX, pointY, mSelectionRadius, mPaint);
3046e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3056e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (mForceDrawDot | mSelectionDegrees % 30 != 0) {
306f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            // We're not on a direct tick (or we've been told to draw the dot anyway).
3071f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein            mPaint.setAlpha(FULL_ALPHA);
3083d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein            canvas.drawCircle(pointX, pointY, (mSelectionRadius * 2 / 7), mPaint);
3096e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else {
310f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            // We're not drawing the dot, so shorten the line to only go as far as the edge of the
311f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            // selection circle.
3126e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            int lineLength = mLineLength;
3136e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            lineLength -= mSelectionRadius;
3146e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            pointX = mXCenter + (int) (lineLength * Math.sin(mSelectionRadians));
3156e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            pointY = mYCenter - (int) (lineLength * Math.cos(mSelectionRadians));
3166e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
3176e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
318f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Draw the line from the center of the circle.
319f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        mPaint.setAlpha(255);
320f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        mPaint.setStrokeWidth(1);
321f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        canvas.drawLine(mXCenter, mYCenter, pointX, pointY, mPaint);
3226e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
3236e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3246e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public ObjectAnimator getDisappearAnimator() {
3256e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (!mIsInitialized || !mDrawValuesReady) {
3266e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            Log.e(TAG, "RadialSelectorView was not ready for animation.");
3276e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return null;
3286e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
3296e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3306e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        Keyframe kf0, kf1, kf2;
3316e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float midwayPoint = 0.2f;
3326e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int duration = 500;
3336e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3346e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf0 = Keyframe.ofFloat(0f, 1);
3356e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf1 = Keyframe.ofFloat(midwayPoint, mTransitionMidRadiusMultiplier);
3366e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf2 = Keyframe.ofFloat(1f, mTransitionEndRadiusMultiplier);
3376e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        PropertyValuesHolder radiusDisappear = PropertyValuesHolder.ofKeyframe(
3386e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                "animationRadiusMultiplier", kf0, kf1, kf2);
3396e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3406e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf0 = Keyframe.ofFloat(0f, 1f);
3416e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf1 = Keyframe.ofFloat(1f, 0f);
3426e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        PropertyValuesHolder fadeOut = PropertyValuesHolder.ofKeyframe("alpha", kf0, kf1);
3436e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3446e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        ObjectAnimator disappearAnimator = ObjectAnimator.ofPropertyValuesHolder(
3456e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                this, radiusDisappear, fadeOut).setDuration(duration);
3466e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        disappearAnimator.addUpdateListener(mInvalidateUpdateListener);
3476e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3486e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        return disappearAnimator;
3496e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
3506e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3516e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public ObjectAnimator getReappearAnimator() {
3526e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (!mIsInitialized || !mDrawValuesReady) {
3536e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            Log.e(TAG, "RadialSelectorView was not ready for animation.");
3546e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return null;
3556e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
3566e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3576e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        Keyframe kf0, kf1, kf2, kf3;
3586e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float midwayPoint = 0.2f;
3596e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int duration = 500;
3606e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3616e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        // The time points are half of what they would normally be, because this animation is
3626e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        // staggered against the disappear so they happen seamlessly. The reappear starts
3636e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        // halfway into the disappear.
364b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        float delayMultiplier = 0.25f;
365b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        float transitionDurationMultiplier = 1f;
3666e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier;
3676e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int totalDuration = (int) (duration * totalDurationMultiplier);
3686e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float delayPoint = (delayMultiplier * duration) / totalDuration;
3696e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        midwayPoint = 1 - (midwayPoint * (1 - delayPoint));
3706e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3716e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf0 = Keyframe.ofFloat(0f, mTransitionEndRadiusMultiplier);
3726e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf1 = Keyframe.ofFloat(delayPoint, mTransitionEndRadiusMultiplier);
3736e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf2 = Keyframe.ofFloat(midwayPoint, mTransitionMidRadiusMultiplier);
3746e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf3 = Keyframe.ofFloat(1f, 1);
3756e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        PropertyValuesHolder radiusReappear = PropertyValuesHolder.ofKeyframe(
3766e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                "animationRadiusMultiplier", kf0, kf1, kf2, kf3);
3776e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3786e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf0 = Keyframe.ofFloat(0f, 0f);
3796e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf1 = Keyframe.ofFloat(delayPoint, 0f);
3806e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf2 = Keyframe.ofFloat(1f, 1f);
3816e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        PropertyValuesHolder fadeIn = PropertyValuesHolder.ofKeyframe("alpha", kf0, kf1, kf2);
3826e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3836e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        ObjectAnimator reappearAnimator = ObjectAnimator.ofPropertyValuesHolder(
3846e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                this, radiusReappear, fadeIn).setDuration(totalDuration);
3856e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        reappearAnimator.addUpdateListener(mInvalidateUpdateListener);
3866e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        return reappearAnimator;
3876e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
3886e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
389f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
390f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * We'll need to invalidate during the animation.
391f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
3926e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private class InvalidateUpdateListener implements AnimatorUpdateListener {
3936e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        @Override
3946e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        public void onAnimationUpdate(ValueAnimator animation) {
3956e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            RadialSelectorView.this.invalidate();
3966e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
3976e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
3986e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein}
399