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;
326e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
33f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein/**
34f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein * View to show what number is selected. This will draw a blue circle over the number, with a blue
35f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein * line coming from the center of the main circle to the edge of the blue selection.
36f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein */
376e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinpublic class RadialSelectorView extends View {
386e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private static final String TAG = "RadialSelectorView";
396e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
406e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private final Paint mPaint = new Paint();
416e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
426e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mIsInitialized;
436e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mDrawValuesReady;
446e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
456e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mCircleRadiusMultiplier;
466e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mAmPmCircleRadiusMultiplier;
476e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mInnerNumbersRadiusMultiplier;
486e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mOuterNumbersRadiusMultiplier;
496e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mNumbersRadiusMultiplier;
506e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mSelectionRadiusMultiplier;
516e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mAnimationRadiusMultiplier;
526e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mIs24HourMode;
536e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mHasInnerCircle;
546e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
556e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mXCenter;
566e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mYCenter;
576e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mCircleRadius;
586e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mTransitionMidRadiusMultiplier;
596e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mTransitionEndRadiusMultiplier;
606e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mLineLength;
616e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mSelectionRadius;
626e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private InvalidateUpdateListener mInvalidateUpdateListener;
636e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
646e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mSelectionDegrees;
656e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private double mSelectionRadians;
666e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mForceDrawDot;
676e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
686e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public RadialSelectorView(Context context) {
696e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        super(context);
706e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mIsInitialized = false;
716e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
726e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
73f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
74f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Initialize this selector with the state of the picker.
75f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param context Current context.
76f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param is24HourMode Whether the selector is in 24-hour mode, which will tell us
77f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * whether the circle's center is moved up slightly to make room for the AM/PM circles.
78f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param hasInnerCircle Whether we have both an inner and an outer circle of numbers
79f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * that may be selected. Should be true for 24-hour mode in the hours circle.
80f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param disappearsOut Whether the numbers' animation will have them disappearing out
81f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * or disappearing in.
82f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param selectionDegrees The initial degrees to be selected.
83f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param isInnerCircle Whether the initial selection is in the inner or outer circle.
84f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Will be ignored when hasInnerCircle is false.
85f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
86b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein    public void initialize(Context context, boolean is24HourMode, boolean hasInnerCircle,
87b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein            boolean disappearsOut, int selectionDegrees, boolean isInnerCircle) {
886e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (mIsInitialized) {
896e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            Log.e(TAG, "This RadialSelectorView may only be initialized once.");
906e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return;
916e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
926e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
936e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        Resources res = context.getResources();
946e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
956e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int blue = res.getColor(R.color.blue);
966e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mPaint.setColor(blue);
976e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mPaint.setAntiAlias(true);
986e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
99f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Calculate values for the circle radius size.
1006e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mIs24HourMode = is24HourMode;
1016e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (is24HourMode) {
1026e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mCircleRadiusMultiplier = Float.parseFloat(
1036e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    res.getString(R.string.circle_radius_multiplier_24HourMode));
1046e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else {
1056e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mCircleRadiusMultiplier = Float.parseFloat(
1066e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    res.getString(R.string.circle_radius_multiplier));
1076e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mAmPmCircleRadiusMultiplier =
1086e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    Float.parseFloat(res.getString(R.string.ampm_circle_radius_multiplier));
1096e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
1106e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
111f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Calculate values for the radius size(s) of the numbers circle(s).
1126e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mHasInnerCircle = hasInnerCircle;
1136e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (hasInnerCircle) {
1146e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mInnerNumbersRadiusMultiplier =
1156e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    Float.parseFloat(res.getString(R.string.numbers_radius_multiplier_inner));
1166e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mOuterNumbersRadiusMultiplier =
1176e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    Float.parseFloat(res.getString(R.string.numbers_radius_multiplier_outer));
1186e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else {
1196e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mNumbersRadiusMultiplier =
1206e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    Float.parseFloat(res.getString(R.string.numbers_radius_multiplier_normal));
1216e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
1226e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mSelectionRadiusMultiplier =
1236e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                Float.parseFloat(res.getString(R.string.selection_radius_multiplier));
1246e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
125f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Calculate values for the transition mid-way states.
1266e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mAnimationRadiusMultiplier = 1;
1276e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mTransitionMidRadiusMultiplier = 1f + (0.05f * (disappearsOut? -1 : 1));
1286e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mTransitionEndRadiusMultiplier = 1f + (0.3f * (disappearsOut? 1 : -1));
1296e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mInvalidateUpdateListener = new InvalidateUpdateListener();
1306e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
131f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        setSelection(selectionDegrees, isInnerCircle, false);
1326e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mIsInitialized = true;
1336e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
1346e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
135f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
136f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Set the selection.
137f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param selectionDegrees The degrees to be selected.
138f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param isInnerCircle Whether the selection should be in the inner circle or outer. Will be
139f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * ignored if hasInnerCircle was initialized to false.
140f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * @param forceDrawDot Whether to force the dot in the center of the selection circle to be
141f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * drawn. If false, the dot will be drawn only when the degrees is not a multiple of 30, i.e.
142f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * the selection is not on a visible number.
143f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
144f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    public void setSelection(int selectionDegrees, boolean isInnerCircle, boolean forceDrawDot) {
1456e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mSelectionDegrees = selectionDegrees;
1466e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mSelectionRadians = selectionDegrees * Math.PI / 180;
1476e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mForceDrawDot = forceDrawDot;
1486e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1496e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (mHasInnerCircle) {
1506e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            if (isInnerCircle) {
1516e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                mNumbersRadiusMultiplier = mInnerNumbersRadiusMultiplier;
1526e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            } else {
1536e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                mNumbersRadiusMultiplier = mOuterNumbersRadiusMultiplier;
1546e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            }
1556e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
1566e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
1576e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
158f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
159f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Allows for smoother animations.
160f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
1613d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein    @Override
1623d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein    public boolean hasOverlappingRendering() {
1633d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein        return false;
1643d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein    }
1653d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein
166f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
167f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Set the multiplier for the radius. Will be used during animations to move in/out.
168f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
1696e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public void setAnimationRadiusMultiplier(float animationRadiusMultiplier) {
1706e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mAnimationRadiusMultiplier = animationRadiusMultiplier;
1716e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
1726e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1736e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public int getDegreesFromCoords(float pointX, float pointY, boolean forceLegal,
1746e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            final Boolean[] isInnerCircle) {
1756e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (!mDrawValuesReady) {
1766e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return -1;
1776e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
1786e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1796e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        double hypotenuse = Math.sqrt(
1806e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                (pointY - mYCenter)*(pointY - mYCenter) +
1816e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                (pointX - mXCenter)*(pointX - mXCenter));
182f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Check if we're outside the range
1836e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (mHasInnerCircle) {
1846e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            if (forceLegal) {
1856e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // If we're told to force the coordinates to be legal, we'll set the isInnerCircle
1866e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // boolean based based off whichever number the coordinates are closer to.
1876e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int innerNumberRadius = (int) (mCircleRadius * mInnerNumbersRadiusMultiplier);
1886e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int distanceToInnerNumber = (int) Math.abs(hypotenuse - innerNumberRadius);
1896e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int outerNumberRadius = (int) (mCircleRadius * mOuterNumbersRadiusMultiplier);
1906e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int distanceToOuterNumber = (int) Math.abs(hypotenuse - outerNumberRadius);
1916e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1926e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                isInnerCircle[0] = (distanceToInnerNumber <= distanceToOuterNumber);
1936e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            } else {
1946e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // Otherwise, if we're close enough to either number (with the space between the
1956e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // two allotted equally), set the isInnerCircle boolean as the closer one.
1966e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // appropriately, but otherwise return -1.
1976e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int minAllowedHypotenuseForInnerNumber =
1986e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        (int) (mCircleRadius * mInnerNumbersRadiusMultiplier) - mSelectionRadius;
1996e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int maxAllowedHypotenuseForOuterNumber =
2006e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        (int) (mCircleRadius * mOuterNumbersRadiusMultiplier) + mSelectionRadius;
2016e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int halfwayHypotenusePoint = (int) (mCircleRadius *
2026e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        ((mOuterNumbersRadiusMultiplier + mInnerNumbersRadiusMultiplier) / 2));
2036e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2046e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                if (hypotenuse >= minAllowedHypotenuseForInnerNumber &&
2056e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        hypotenuse <= halfwayHypotenusePoint) {
2066e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    isInnerCircle[0] = true;
2076e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                } else if (hypotenuse <= maxAllowedHypotenuseForOuterNumber &&
2086e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        hypotenuse >= halfwayHypotenusePoint) {
2096e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    isInnerCircle[0] = false;
2106e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                } else {
2116e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    return -1;
2126e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                }
2136e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            }
2146e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else {
2156e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            // If there's just one circle, we'll need to return -1 if:
2166e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            // we're not told to force the coordinates to be legal, and
2176e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            // the coordinates' distance to the number is within the allowed distance.
2186e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            if (!forceLegal) {
2196e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int distanceToNumber = (int) Math.abs(hypotenuse - mLineLength);
2206e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // The max allowed distance will be defined as the distance from the center of the
2216e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // number to the edge of the circle.
2226e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int maxAllowedDistance = (int) (mCircleRadius * (1 - mNumbersRadiusMultiplier));
2236e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                if (distanceToNumber > maxAllowedDistance) {
2246e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    return -1;
2256e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                }
2266e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            }
2276e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
2286e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2296e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2306e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float opposite = Math.abs(pointY - mYCenter);
2316e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        double radians = Math.asin(opposite / hypotenuse);
2326e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int degrees = (int) (radians * 180 / Math.PI);
2336e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2346e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        // Now we have to translate to the correct quadrant.
2356e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        boolean rightSide = (pointX > mXCenter);
2366e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        boolean topSide = (pointY < mYCenter);
2376e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (rightSide && topSide) {
2386e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            degrees = 90 - degrees;
2396e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else if (rightSide && !topSide) {
2406e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            degrees = 90 + degrees;
2416e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else if (!rightSide && !topSide) {
2426e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            degrees = 270 - degrees;
2436e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else if (!rightSide && topSide) {
2446e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            degrees = 270 + degrees;
2456e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
2466e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        return degrees;
2476e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
2486e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2496e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    @Override
2506e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public void onDraw(Canvas canvas) {
2516e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int viewWidth = getWidth();
2526e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (viewWidth == 0 || !mIsInitialized) {
2536e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return;
2546e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
2556e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2566e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (!mDrawValuesReady) {
2576e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mXCenter = getWidth() / 2;
2586e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mYCenter = getHeight() / 2;
2596e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mCircleRadius = (int) (Math.min(mXCenter, mYCenter) * mCircleRadiusMultiplier);
2606e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2616e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            if (!mIs24HourMode) {
2626e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // We'll need to draw the AM/PM circles, so the main circle will need to have
2636e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // a slightly higher center. To keep the entire view centered vertically, we'll
2646e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // have to push it up by half the radius of the AM/PM circles.
2656e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                int amPmCircleRadius = (int) (mCircleRadius * mAmPmCircleRadiusMultiplier);
2666e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                mYCenter -= amPmCircleRadius / 2;
2676e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            }
2686e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2696e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mSelectionRadius = (int) (mCircleRadius * mSelectionRadiusMultiplier);
2706e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2716e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mDrawValuesReady = true;
2726e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
2736e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
274f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Calculate the current radius at which to place the selection circle.
2756e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mLineLength = (int) (mCircleRadius * mNumbersRadiusMultiplier * mAnimationRadiusMultiplier);
2766e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int pointX = mXCenter + (int) (mLineLength * Math.sin(mSelectionRadians));
2776e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int pointY = mYCenter - (int) (mLineLength * Math.cos(mSelectionRadians));
2786e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
279f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Draw the selection circle.
280d5ec70d18c6379015acd4725b8804ab79f30bfa0Sam Blitzstein        mPaint.setAlpha(51);
2816e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        canvas.drawCircle(pointX, pointY, mSelectionRadius, mPaint);
2826e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2836e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (mForceDrawDot | mSelectionDegrees % 30 != 0) {
284f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            // We're not on a direct tick (or we've been told to draw the dot anyway).
2856e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mPaint.setAlpha(255);
2863d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein            canvas.drawCircle(pointX, pointY, (mSelectionRadius * 2 / 7), mPaint);
2876e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else {
288f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            // We're not drawing the dot, so shorten the line to only go as far as the edge of the
289f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            // selection circle.
2906e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            int lineLength = mLineLength;
2916e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            lineLength -= mSelectionRadius;
2926e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            pointX = mXCenter + (int) (lineLength * Math.sin(mSelectionRadians));
2936e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            pointY = mYCenter - (int) (lineLength * Math.cos(mSelectionRadians));
2946e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
2956e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
296f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Draw the line from the center of the circle.
297f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        mPaint.setAlpha(255);
298f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        mPaint.setStrokeWidth(1);
299f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        canvas.drawLine(mXCenter, mYCenter, pointX, pointY, mPaint);
3006e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
3016e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3026e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public ObjectAnimator getDisappearAnimator() {
3036e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (!mIsInitialized || !mDrawValuesReady) {
3046e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            Log.e(TAG, "RadialSelectorView was not ready for animation.");
3056e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return null;
3066e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
3076e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3086e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        Keyframe kf0, kf1, kf2;
3096e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float midwayPoint = 0.2f;
3106e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int duration = 500;
3116e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3126e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf0 = Keyframe.ofFloat(0f, 1);
3136e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf1 = Keyframe.ofFloat(midwayPoint, mTransitionMidRadiusMultiplier);
3146e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf2 = Keyframe.ofFloat(1f, mTransitionEndRadiusMultiplier);
3156e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        PropertyValuesHolder radiusDisappear = PropertyValuesHolder.ofKeyframe(
3166e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                "animationRadiusMultiplier", kf0, kf1, kf2);
3176e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3186e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf0 = Keyframe.ofFloat(0f, 1f);
3196e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf1 = Keyframe.ofFloat(1f, 0f);
3206e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        PropertyValuesHolder fadeOut = PropertyValuesHolder.ofKeyframe("alpha", kf0, kf1);
3216e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3226e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        ObjectAnimator disappearAnimator = ObjectAnimator.ofPropertyValuesHolder(
3236e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                this, radiusDisappear, fadeOut).setDuration(duration);
3246e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        disappearAnimator.addUpdateListener(mInvalidateUpdateListener);
3256e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3266e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        return disappearAnimator;
3276e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
3286e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3296e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public ObjectAnimator getReappearAnimator() {
3306e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (!mIsInitialized || !mDrawValuesReady) {
3316e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            Log.e(TAG, "RadialSelectorView was not ready for animation.");
3326e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return null;
3336e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
3346e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3356e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        Keyframe kf0, kf1, kf2, kf3;
3366e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float midwayPoint = 0.2f;
3376e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int duration = 500;
3386e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3396e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        // The time points are half of what they would normally be, because this animation is
3406e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        // staggered against the disappear so they happen seamlessly. The reappear starts
3416e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        // halfway into the disappear.
342b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        float delayMultiplier = 0.25f;
343b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        float transitionDurationMultiplier = 1f;
3446e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier;
3456e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int totalDuration = (int) (duration * totalDurationMultiplier);
3466e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float delayPoint = (delayMultiplier * duration) / totalDuration;
3476e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        midwayPoint = 1 - (midwayPoint * (1 - delayPoint));
3486e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3496e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf0 = Keyframe.ofFloat(0f, mTransitionEndRadiusMultiplier);
3506e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf1 = Keyframe.ofFloat(delayPoint, mTransitionEndRadiusMultiplier);
3516e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf2 = Keyframe.ofFloat(midwayPoint, mTransitionMidRadiusMultiplier);
3526e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf3 = Keyframe.ofFloat(1f, 1);
3536e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        PropertyValuesHolder radiusReappear = PropertyValuesHolder.ofKeyframe(
3546e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                "animationRadiusMultiplier", kf0, kf1, kf2, kf3);
3556e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3566e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf0 = Keyframe.ofFloat(0f, 0f);
3576e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf1 = Keyframe.ofFloat(delayPoint, 0f);
3586e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf2 = Keyframe.ofFloat(1f, 1f);
3596e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        PropertyValuesHolder fadeIn = PropertyValuesHolder.ofKeyframe("alpha", kf0, kf1, kf2);
3606e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3616e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        ObjectAnimator reappearAnimator = ObjectAnimator.ofPropertyValuesHolder(
3626e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                this, radiusReappear, fadeIn).setDuration(totalDuration);
3636e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        reappearAnimator.addUpdateListener(mInvalidateUpdateListener);
3646e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        return reappearAnimator;
3656e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
3666e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
367f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
368f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * We'll need to invalidate during the animation.
369f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
3706e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private class InvalidateUpdateListener implements AnimatorUpdateListener {
3716e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        @Override
3726e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        public void onAnimationUpdate(ValueAnimator animation) {
3736e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            RadialSelectorView.this.invalidate();
3746e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
3756e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
3766e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein}
377