RadialSelectorView.java revision 3d5a23b698cb8c59f43914ea2f9bb4fb36575f88
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
176e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinpackage com.android.datetimepicker;
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.app.Service;
256e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.content.Context;
266e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.content.res.Resources;
276e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.graphics.Canvas;
286e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.graphics.Paint;
296e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.graphics.Typeface;
306e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.os.SystemClock;
316e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.os.Vibrator;
326e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.util.AttributeSet;
336e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.util.Log;
346e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.view.MotionEvent;
356e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.view.View;
366e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.view.View.OnTouchListener;
376e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
386e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport com.android.datetimepicker.R;
396e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
406e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinpublic class RadialSelectorView extends View {
416e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private static final String TAG = "RadialSelectorView";
426e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
436e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private final Paint mPaint = new Paint();
446e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
456e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mIsInitialized;
466e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mDrawValuesReady;
476e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
486e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mCircleRadiusMultiplier;
496e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mAmPmCircleRadiusMultiplier;
506e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mInnerNumbersRadiusMultiplier;
516e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mOuterNumbersRadiusMultiplier;
526e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mNumbersRadiusMultiplier;
536e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mSelectionRadiusMultiplier;
546e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mAnimationRadiusMultiplier;
556e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mIs24HourMode;
566e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mHasInnerCircle;
576e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
586e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mXCenter;
596e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mYCenter;
606e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mCircleRadius;
616e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mTransitionMidRadiusMultiplier;
626e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mTransitionEndRadiusMultiplier;
636e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mLineLength;
646e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mSelectionRadius;
656e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private InvalidateUpdateListener mInvalidateUpdateListener;
666e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
676e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mSelectionDegrees;
686e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private double mSelectionRadians;
696e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mDrawLine;
706e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mForceDrawDot;
716e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
726e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public RadialSelectorView(Context context) {
736e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        super(context);
746e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mIsInitialized = false;
756e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
766e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
776e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public void initialize(Context context, int selectionDegrees, boolean is24HourMode,
786e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            boolean hasInnerCircle, boolean isInnerCircle, boolean disappearsOut) {
796e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (mIsInitialized) {
806e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            Log.e(TAG, "This RadialSelectorView may only be initialized once.");
816e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return;
826e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
836e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
846e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        Resources res = context.getResources();
856e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
866e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int blue = res.getColor(R.color.blue);
876e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mPaint.setColor(blue);
886e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mPaint.setAntiAlias(true);
896e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
906e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mIs24HourMode = is24HourMode;
916e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (is24HourMode) {
926e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mCircleRadiusMultiplier = Float.parseFloat(
936e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    res.getString(R.string.circle_radius_multiplier_24HourMode));
946e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else {
956e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mCircleRadiusMultiplier = Float.parseFloat(
966e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    res.getString(R.string.circle_radius_multiplier));
976e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mAmPmCircleRadiusMultiplier =
986e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    Float.parseFloat(res.getString(R.string.ampm_circle_radius_multiplier));
996e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
1006e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1016e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mHasInnerCircle = hasInnerCircle;
1026e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (hasInnerCircle) {
1036e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mInnerNumbersRadiusMultiplier =
1046e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    Float.parseFloat(res.getString(R.string.numbers_radius_multiplier_inner));
1056e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mOuterNumbersRadiusMultiplier =
1066e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    Float.parseFloat(res.getString(R.string.numbers_radius_multiplier_outer));
1076e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else {
1086e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mNumbersRadiusMultiplier =
1096e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    Float.parseFloat(res.getString(R.string.numbers_radius_multiplier_normal));
1106e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
1116e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mSelectionRadiusMultiplier =
1126e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                Float.parseFloat(res.getString(R.string.selection_radius_multiplier));
1136e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1146e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        setSelection(selectionDegrees, isInnerCircle, false, false);
1156e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1166e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mAnimationRadiusMultiplier = 1;
1176e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mTransitionMidRadiusMultiplier = 1f + (0.05f * (disappearsOut? -1 : 1));
1186e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mTransitionEndRadiusMultiplier = 1f + (0.3f * (disappearsOut? 1 : -1));
1196e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mInvalidateUpdateListener = new InvalidateUpdateListener();
1206e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1216e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mIsInitialized = true;
1226e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
1236e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1246e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public void setSelection(int selectionDegrees, boolean isInnerCircle,
1256e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            boolean drawLine, boolean forceDrawDot) {
1266e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mSelectionDegrees = selectionDegrees;
1276e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mSelectionRadians = selectionDegrees * Math.PI / 180;
1286e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mDrawLine = drawLine;
1296e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mForceDrawDot = forceDrawDot;
1306e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1316e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (mHasInnerCircle) {
1326e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            if (isInnerCircle) {
1336e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                mNumbersRadiusMultiplier = mInnerNumbersRadiusMultiplier;
1346e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            } else {
1356e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                mNumbersRadiusMultiplier = mOuterNumbersRadiusMultiplier;
1366e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            }
1376e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
1386e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
1396e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1406e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public void setDrawLine(boolean drawLine) {
1416e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mDrawLine = drawLine;
1426e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
1436e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1443d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein    @Override
1453d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein    public boolean hasOverlappingRendering() {
1463d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein        return false;
1473d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein    }
1483d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein
1496e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public void setAnimationRadiusMultiplier(float animationRadiusMultiplier) {
1506e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mAnimationRadiusMultiplier = animationRadiusMultiplier;
1516e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
1526e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1536e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public int getDegreesFromCoords(float pointX, float pointY, boolean forceLegal,
1546e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            final Boolean[] isInnerCircle) {
1556e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (!mDrawValuesReady) {
1566e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return -1;
1576e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
1586e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1596e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        double hypotenuse = Math.sqrt(
1606e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                (pointY - mYCenter)*(pointY - mYCenter) +
1616e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                (pointX - mXCenter)*(pointX - mXCenter));
1626e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        // Check if we're outside the range
1636e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (mHasInnerCircle) {
1646e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            if (forceLegal) {
1656e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // If we're told to force the coordinates to be legal, we'll set the isInnerCircle
1666e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // boolean based based off whichever number the coordinates are closer to.
1676e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int innerNumberRadius = (int) (mCircleRadius * mInnerNumbersRadiusMultiplier);
1686e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int distanceToInnerNumber = (int) Math.abs(hypotenuse - innerNumberRadius);
1696e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int outerNumberRadius = (int) (mCircleRadius * mOuterNumbersRadiusMultiplier);
1706e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int distanceToOuterNumber = (int) Math.abs(hypotenuse - outerNumberRadius);
1716e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1726e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                isInnerCircle[0] = (distanceToInnerNumber <= distanceToOuterNumber);
1736e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            } else {
1746e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // Otherwise, if we're close enough to either number (with the space between the
1756e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // two allotted equally), set the isInnerCircle boolean as the closer one.
1766e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // appropriately, but otherwise return -1.
1776e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int minAllowedHypotenuseForInnerNumber =
1786e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        (int) (mCircleRadius * mInnerNumbersRadiusMultiplier) - mSelectionRadius;
1796e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int maxAllowedHypotenuseForOuterNumber =
1806e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        (int) (mCircleRadius * mOuterNumbersRadiusMultiplier) + mSelectionRadius;
1816e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int halfwayHypotenusePoint = (int) (mCircleRadius *
1826e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        ((mOuterNumbersRadiusMultiplier + mInnerNumbersRadiusMultiplier) / 2));
1836e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1846e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                if (hypotenuse >= minAllowedHypotenuseForInnerNumber &&
1856e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        hypotenuse <= halfwayHypotenusePoint) {
1866e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    isInnerCircle[0] = true;
1876e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                } else if (hypotenuse <= maxAllowedHypotenuseForOuterNumber &&
1886e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        hypotenuse >= halfwayHypotenusePoint) {
1896e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    isInnerCircle[0] = false;
1906e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                } else {
1916e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    return -1;
1926e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                }
1936e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            }
1946e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else {
1956e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            // If there's just one circle, we'll need to return -1 if:
1966e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            // we're not told to force the coordinates to be legal, and
1976e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            // the coordinates' distance to the number is within the allowed distance.
1986e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            if (!forceLegal) {
1996e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int distanceToNumber = (int) Math.abs(hypotenuse - mLineLength);
2006e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // The max allowed distance will be defined as the distance from the center of the
2016e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // number to the edge of the circle.
2026e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int maxAllowedDistance = (int) (mCircleRadius * (1 - mNumbersRadiusMultiplier));
2036e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                if (distanceToNumber > maxAllowedDistance) {
2046e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    return -1;
2056e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                }
2066e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            }
2076e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
2086e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2096e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2106e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float opposite = Math.abs(pointY - mYCenter);
2116e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        double radians = Math.asin(opposite / hypotenuse);
2126e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int degrees = (int) (radians * 180 / Math.PI);
2136e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2146e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        // Now we have to translate to the correct quadrant.
2156e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        boolean rightSide = (pointX > mXCenter);
2166e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        boolean topSide = (pointY < mYCenter);
2176e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (rightSide && topSide) {
2186e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            degrees = 90 - degrees;
2196e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else if (rightSide && !topSide) {
2206e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            degrees = 90 + degrees;
2216e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else if (!rightSide && !topSide) {
2226e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            degrees = 270 - degrees;
2236e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else if (!rightSide && topSide) {
2246e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            degrees = 270 + degrees;
2256e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
2266e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        return degrees;
2276e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
2286e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2296e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    @Override
2306e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public void onDraw(Canvas canvas) {
2316e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int viewWidth = getWidth();
2326e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (viewWidth == 0 || !mIsInitialized) {
2336e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return;
2346e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
2356e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2366e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (!mDrawValuesReady) {
2376e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mXCenter = getWidth() / 2;
2386e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mYCenter = getHeight() / 2;
2396e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mCircleRadius = (int) (Math.min(mXCenter, mYCenter) * mCircleRadiusMultiplier);
2406e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2416e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            if (!mIs24HourMode) {
2426e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // We'll need to draw the AM/PM circles, so the main circle will need to have
2436e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // a slightly higher center. To keep the entire view centered vertically, we'll
2446e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // have to push it up by half the radius of the AM/PM circles.
2456e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int amPmCircleRadius = (int) (mCircleRadius * mAmPmCircleRadiusMultiplier);
2466e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                mYCenter -= amPmCircleRadius / 2;
2476e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            }
2486e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2496e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mSelectionRadius = (int) (mCircleRadius * mSelectionRadiusMultiplier);
2506e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2516e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mDrawValuesReady = true;
2526e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
2536e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2546e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mLineLength = (int) (mCircleRadius * mNumbersRadiusMultiplier * mAnimationRadiusMultiplier);
2556e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int pointX = mXCenter + (int) (mLineLength * Math.sin(mSelectionRadians));
2566e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int pointY = mYCenter - (int) (mLineLength * Math.cos(mSelectionRadians));
2576e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2583d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein        mPaint.setAlpha(60);
2596e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        canvas.drawCircle(pointX, pointY, mSelectionRadius, mPaint);
2606e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2616e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (mForceDrawDot | mSelectionDegrees % 30 != 0) {
2626e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            // We're not on a direct tick.
2636e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mPaint.setAlpha(255);
2643d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein            canvas.drawCircle(pointX, pointY, (mSelectionRadius * 2 / 7), mPaint);
2656e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else {
2666e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            int lineLength = mLineLength;
2676e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            lineLength -= mSelectionRadius;
2686e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            pointX = mXCenter + (int) (lineLength * Math.sin(mSelectionRadians));
2696e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            pointY = mYCenter - (int) (lineLength * Math.cos(mSelectionRadians));
2706e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
2716e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2726e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (mDrawLine || true) {
2736e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mPaint.setAlpha(255);
2746e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mPaint.setStrokeWidth(1);
2756e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            canvas.drawLine(mXCenter, mYCenter, pointX, pointY, mPaint);
2766e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
2776e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
2786e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2796e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public ObjectAnimator getDisappearAnimator() {
2806e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (!mIsInitialized || !mDrawValuesReady) {
2816e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            Log.e(TAG, "RadialSelectorView was not ready for animation.");
2826e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return null;
2836e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
2846e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2856e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        Keyframe kf0, kf1, kf2;
2866e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float midwayPoint = 0.2f;
2876e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int duration = 500;
2886e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2896e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf0 = Keyframe.ofFloat(0f, 1);
2906e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf1 = Keyframe.ofFloat(midwayPoint, mTransitionMidRadiusMultiplier);
2916e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf2 = Keyframe.ofFloat(1f, mTransitionEndRadiusMultiplier);
2926e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        PropertyValuesHolder radiusDisappear = PropertyValuesHolder.ofKeyframe(
2936e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                "animationRadiusMultiplier", kf0, kf1, kf2);
2946e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2956e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf0 = Keyframe.ofFloat(0f, 1f);
2966e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf1 = Keyframe.ofFloat(1f, 0f);
2976e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        PropertyValuesHolder fadeOut = PropertyValuesHolder.ofKeyframe("alpha", kf0, kf1);
2986e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2996e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        ObjectAnimator disappearAnimator = ObjectAnimator.ofPropertyValuesHolder(
3006e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                this, radiusDisappear, fadeOut).setDuration(duration);
3016e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        disappearAnimator.addUpdateListener(mInvalidateUpdateListener);
3026e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3036e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        return disappearAnimator;
3046e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
3056e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3066e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public ObjectAnimator getReappearAnimator() {
3076e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (!mIsInitialized || !mDrawValuesReady) {
3086e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            Log.e(TAG, "RadialSelectorView was not ready for animation.");
3096e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return null;
3106e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
3116e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3126e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        Keyframe kf0, kf1, kf2, kf3;
3136e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float midwayPoint = 0.2f;
3146e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int duration = 500;
3156e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3166e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        // The time points are half of what they would normally be, because this animation is
3176e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        // staggered against the disappear so they happen seamlessly. The reappear starts
3186e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        // halfway into the disappear.
3196e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float delayMultiplier = 0.5f;
3206e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float transitionDurationMultiplier = 0.75f;
3216e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier;
3226e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int totalDuration = (int) (duration * totalDurationMultiplier);
3236e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float delayPoint = (delayMultiplier * duration) / totalDuration;
3246e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        midwayPoint = 1 - (midwayPoint * (1 - delayPoint));
3256e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3266e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf0 = Keyframe.ofFloat(0f, mTransitionEndRadiusMultiplier);
3276e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf1 = Keyframe.ofFloat(delayPoint, mTransitionEndRadiusMultiplier);
3286e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf2 = Keyframe.ofFloat(midwayPoint, mTransitionMidRadiusMultiplier);
3296e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf3 = Keyframe.ofFloat(1f, 1);
3306e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        PropertyValuesHolder radiusReappear = PropertyValuesHolder.ofKeyframe(
3316e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                "animationRadiusMultiplier", kf0, kf1, kf2, kf3);
3326e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3336e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf0 = Keyframe.ofFloat(0f, 0f);
3346e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf1 = Keyframe.ofFloat(delayPoint, 0f);
3356e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf2 = Keyframe.ofFloat(1f, 1f);
3366e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        PropertyValuesHolder fadeIn = PropertyValuesHolder.ofKeyframe("alpha", kf0, kf1, kf2);
3376e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3386e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        ObjectAnimator reappearAnimator = ObjectAnimator.ofPropertyValuesHolder(
3396e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                this, radiusReappear, fadeIn).setDuration(totalDuration);
3406e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        reappearAnimator.addUpdateListener(mInvalidateUpdateListener);
3416e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        return reappearAnimator;
3426e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
3436e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3446e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private class InvalidateUpdateListener implements AnimatorUpdateListener {
3456e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        @Override
3466e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        public void onAnimationUpdate(ValueAnimator animation) {
3476e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            RadialSelectorView.this.invalidate();
3486e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
3496e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
3506e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein}
351