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.graphics.Typeface;
296e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.graphics.Paint.Align;
306e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.util.Log;
316e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport android.view.View;
326e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
336e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinimport com.android.datetimepicker.R;
346e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
35f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein/**
36f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein * A view to show a series of numbers in a circular pattern.
37f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein */
386e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzsteinpublic class RadialTextsView extends View {
396e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private final static String TAG = "RadialTextsView";
406e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
416e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private final Paint mPaint = new Paint();
426e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
436e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mDrawValuesReady;
446e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mIsInitialized;
456e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
463d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein    private Typeface mTypefaceLight;
473d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein    private Typeface mTypefaceRegular;
486e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private String[] mTexts;
496e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private String[] mInnerTexts;
506e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mIs24HourMode;
516e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mHasInnerCircle;
526e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mCircleRadiusMultiplier;
536e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mAmPmCircleRadiusMultiplier;
546e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mNumbersRadiusMultiplier;
556e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mInnerNumbersRadiusMultiplier;
566e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mTextSizeMultiplier;
576e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mInnerTextSizeMultiplier;
586e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
596e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mXCenter;
606e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private int mYCenter;
616e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mCircleRadius;
626e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private boolean mTextGridValuesDirty;
636e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mTextSize;
646e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mInnerTextSize;
656e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float[] mTextGridHeights;
666e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float[] mTextGridWidths;
676e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float[] mInnerTextGridHeights;
686e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float[] mInnerTextGridWidths;
696e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
706e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mAnimationRadiusMultiplier;
716e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mTransitionMidRadiusMultiplier;
726e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private float mTransitionEndRadiusMultiplier;
736e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    ObjectAnimator mDisappearAnimator;
746e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    ObjectAnimator mReappearAnimator;
756e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private InvalidateUpdateListener mInvalidateUpdateListener;
766e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
776e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public RadialTextsView(Context context) {
786e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        super(context);
796e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mIsInitialized = false;
806e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
816e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
826e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public void initialize(Resources res, String[] texts, String[] innerTexts,
836e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            boolean is24HourMode, boolean disappearsOut) {
846e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (mIsInitialized) {
856e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            Log.e(TAG, "This RadialTextsView may only be initialized once.");
866e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return;
876e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
886e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
89f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Set up the paint.
90f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        int numbersTextColor = res.getColor(R.color.numbers_text_color);
91f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        mPaint.setColor(numbersTextColor);
923d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein        String typefaceFamily = res.getString(R.string.radial_numbers_typeface);
933d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein        mTypefaceLight = Typeface.create(typefaceFamily, Typeface.NORMAL);
943d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein        String typefaceFamilyRegular = res.getString(R.string.sans_serif);
953d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein        mTypefaceRegular = Typeface.create(typefaceFamilyRegular, Typeface.NORMAL);
966e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mPaint.setAntiAlias(true);
976e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mPaint.setTextAlign(Align.CENTER);
986e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
996e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mTexts = texts;
1006e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mInnerTexts = innerTexts;
1016e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mIs24HourMode = is24HourMode;
1026e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mHasInnerCircle = (innerTexts != null);
1036e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
104f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Calculate the radius for the main circle.
1056e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (is24HourMode) {
1066e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mCircleRadiusMultiplier = Float.parseFloat(
1076e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    res.getString(R.string.circle_radius_multiplier_24HourMode));
1086e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else {
1096e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mCircleRadiusMultiplier = Float.parseFloat(
1106e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    res.getString(R.string.circle_radius_multiplier));
1116e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mAmPmCircleRadiusMultiplier =
1126e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    Float.parseFloat(res.getString(R.string.ampm_circle_radius_multiplier));
1136e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
1146e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
115f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Initialize the widths and heights of the grid, and calculate the values for the numbers.
1166e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mTextGridHeights = new float[7];
1176e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mTextGridWidths = new float[7];
1186e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (mHasInnerCircle) {
1196e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mNumbersRadiusMultiplier = Float.parseFloat(
1206e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    res.getString(R.string.numbers_radius_multiplier_outer));
1216e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mTextSizeMultiplier = Float.parseFloat(
1226e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    res.getString(R.string.text_size_multiplier_outer));
1236e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mInnerNumbersRadiusMultiplier = Float.parseFloat(
1246e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    res.getString(R.string.numbers_radius_multiplier_inner));
1256e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mInnerTextSizeMultiplier = Float.parseFloat(
1266e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    res.getString(R.string.text_size_multiplier_inner));
1276e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1286e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mInnerTextGridHeights = new float[7];
1296e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mInnerTextGridWidths = new float[7];
1306e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        } else {
1316e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mNumbersRadiusMultiplier = Float.parseFloat(
1326e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    res.getString(R.string.numbers_radius_multiplier_normal));
1336e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mTextSizeMultiplier = Float.parseFloat(
1346e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    res.getString(R.string.text_size_multiplier_normal));
1356e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
1366e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1376e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mAnimationRadiusMultiplier = 1;
1386e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mTransitionMidRadiusMultiplier = 1f + (0.05f * (disappearsOut? -1 : 1));
1396e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mTransitionEndRadiusMultiplier = 1f + (0.3f * (disappearsOut? 1 : -1));
1406e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mInvalidateUpdateListener = new InvalidateUpdateListener();
1416e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1426e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mTextGridValuesDirty = true;
1436e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mIsInitialized = true;
1446e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
1456e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1461f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein    /* package */ void setTheme(Context context, boolean themeDark) {
1471f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein        Resources res = context.getResources();
1481f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein        int textColor;
1491f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein        if (themeDark) {
150a09b3c940e98b8606a16a94b48b6d0121d9d3635Scott Kennedy            textColor = res.getColor(android.R.color.white);
1511f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein        } else {
1521f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein            textColor = res.getColor(R.color.numbers_text_color);
1531f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein        }
1541f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein        mPaint.setColor(textColor);
1551f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein    }
1561f129e23db2dc5837a856f7734b15a5a8be6be94Sam Blitzstein
157f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
158f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Allows for smoother animation.
159f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
1603d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein    @Override
1613d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein    public boolean hasOverlappingRendering() {
1623d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein        return false;
1633d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein    }
1643d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein
165f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
166f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Used by the animation to move the numbers in and out.
167f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
1686e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public void setAnimationRadiusMultiplier(float animationRadiusMultiplier) {
1696e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mAnimationRadiusMultiplier = animationRadiusMultiplier;
1706e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mTextGridValuesDirty = true;
1716e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
1726e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1736e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    @Override
1746e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public void onDraw(Canvas canvas) {
1756e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int viewWidth = getWidth();
1766e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (viewWidth == 0 || !mIsInitialized) {
1776e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return;
1786e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
1796e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1806e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (!mDrawValuesReady) {
1816e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mXCenter = getWidth() / 2;
1826e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mYCenter = getHeight() / 2;
1836e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mCircleRadius = Math.min(mXCenter, mYCenter) * mCircleRadiusMultiplier;
1846e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            if (!mIs24HourMode) {
1856e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // We'll need to draw the AM/PM circles, so the main circle will need to have
1866e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // a slightly higher center. To keep the entire view centered vertically, we'll
1876e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                // have to push it up by half the radius of the AM/PM circles.
1886e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                float amPmCircleRadius = mCircleRadius * mAmPmCircleRadiusMultiplier;
1896e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                mYCenter -= amPmCircleRadius / 2;
1906e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            }
1916e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
1926e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mTextSize = mCircleRadius * mTextSizeMultiplier;
1936e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            if (mHasInnerCircle) {
1946e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                mInnerTextSize = mCircleRadius * mInnerTextSizeMultiplier;
1956e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            }
1966e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
197f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            // Because the text positions will be static, pre-render the animations.
1986e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            renderAnimations();
1996e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2006e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mTextGridValuesDirty = true;
2016e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mDrawValuesReady = true;
2026e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
2036e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
204f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Calculate the text positions, but only if they've changed since the last onDraw.
2056e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (mTextGridValuesDirty) {
2066e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            float numbersRadius =
2076e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    mCircleRadius * mNumbersRadiusMultiplier * mAnimationRadiusMultiplier;
2086e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
209f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein            // Calculate the positions for the 12 numbers in the main circle.
2106e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            calculateGridSizes(numbersRadius, mXCenter, mYCenter,
2116e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    mTextSize, mTextGridHeights, mTextGridWidths);
2126e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            if (mHasInnerCircle) {
213f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein                // If we have an inner circle, calculate those positions too.
2146e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                float innerNumbersRadius =
2156e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        mCircleRadius * mInnerNumbersRadiusMultiplier * mAnimationRadiusMultiplier;
2166e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                calculateGridSizes(innerNumbersRadius, mXCenter, mYCenter,
2176e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                        mInnerTextSize, mInnerTextGridHeights, mInnerTextGridWidths);
2186e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            }
2196e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            mTextGridValuesDirty = false;
2206e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
2216e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
222f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // Draw the texts in the pre-calculated positions.
2233d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein        drawTexts(canvas, mTextSize, mTypefaceLight, mTexts, mTextGridWidths, mTextGridHeights);
2246e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        if (mHasInnerCircle) {
2253d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein            drawTexts(canvas, mInnerTextSize, mTypefaceRegular, mInnerTexts,
2266e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                    mInnerTextGridWidths, mInnerTextGridHeights);
2276e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
2286e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
2296e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
230f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
231f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Using the trigonometric Unit Circle, calculate the positions that the text will need to be
232f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * drawn at based on the specified circle radius. Place the values in the textGridHeights and
233f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * textGridWidths parameters.
234f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
2356e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private void calculateGridSizes(float numbersRadius, float xCenter, float yCenter,
2366e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            float textSize, float[] textGridHeights, float[] textGridWidths) {
2376e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        /*
238f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein         * The numbers need to be drawn in a 7x7 grid, representing the points on the Unit Circle.
2396e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein         */
2406e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float offset1 = numbersRadius;
2416e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        // cos(30) = a / r => r * cos(30) = a => r * √3/2 = a
2426e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float offset2 = numbersRadius * ((float) Math.sqrt(3)) / 2f;
2436e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        // sin(30) = o / r => r * sin(30) = o => r / 2 = a
2446e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float offset3 = numbersRadius / 2f;
2456e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mPaint.setTextSize(textSize);
246f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        // We'll need yTextBase to be slightly lower to account for the text's baseline.
2476e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        yCenter -= (mPaint.descent() + mPaint.ascent()) / 2;
248f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein
2496e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        textGridHeights[0] = yCenter - offset1;
2506e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        textGridWidths[0] = xCenter - offset1;
2516e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        textGridHeights[1] = yCenter - offset2;
2526e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        textGridWidths[1] = xCenter - offset2;
2536e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        textGridHeights[2] = yCenter - offset3;
2546e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        textGridWidths[2] = xCenter - offset3;
2556e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        textGridHeights[3] = yCenter;
2566e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        textGridWidths[3] = xCenter;
2576e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        textGridHeights[4] = yCenter + offset3;
2586e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        textGridWidths[4] = xCenter + offset3;
2596e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        textGridHeights[5] = yCenter + offset2;
2606e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        textGridWidths[5] = xCenter + offset2;
2616e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        textGridHeights[6] = yCenter + offset1;
2626e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        textGridWidths[6] = xCenter + offset1;
2636e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
2646e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
265f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
266f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Draw the 12 text values at the positions specified by the textGrid parameters.
267f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
2683d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein    private void drawTexts(Canvas canvas, float textSize, Typeface typeface, String[] texts,
2696e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            float[] textGridWidths, float[] textGridHeights) {
2706e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mPaint.setTextSize(textSize);
2713d5a23b698cb8c59f43914ea2f9bb4fb36575f88Sam Blitzstein        mPaint.setTypeface(typeface);
2726e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        canvas.drawText(texts[0], textGridWidths[3], textGridHeights[0], mPaint);
2736e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        canvas.drawText(texts[1], textGridWidths[4], textGridHeights[1], mPaint);
2746e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        canvas.drawText(texts[2], textGridWidths[5], textGridHeights[2], mPaint);
2756e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        canvas.drawText(texts[3], textGridWidths[6], textGridHeights[3], mPaint);
2766e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        canvas.drawText(texts[4], textGridWidths[5], textGridHeights[4], mPaint);
2776e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        canvas.drawText(texts[5], textGridWidths[4], textGridHeights[5], mPaint);
2786e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        canvas.drawText(texts[6], textGridWidths[3], textGridHeights[6], mPaint);
2796e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        canvas.drawText(texts[7], textGridWidths[2], textGridHeights[5], mPaint);
2806e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        canvas.drawText(texts[8], textGridWidths[1], textGridHeights[4], mPaint);
2816e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        canvas.drawText(texts[9], textGridWidths[0], textGridHeights[3], mPaint);
2826e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        canvas.drawText(texts[10], textGridWidths[1], textGridHeights[2], mPaint);
2836e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        canvas.drawText(texts[11], textGridWidths[2], textGridHeights[1], mPaint);
2846e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
2856e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
286f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein    /**
287f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     * Render the animations for appearing and disappearing.
288f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein     */
2896e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private void renderAnimations() {
2906e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        Keyframe kf0, kf1, kf2, kf3;
2916e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float midwayPoint = 0.2f;
2926e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int duration = 500;
2936e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
2946e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        // Set up animator for disappearing.
2956e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf0 = Keyframe.ofFloat(0f, 1);
2966e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf1 = Keyframe.ofFloat(midwayPoint, mTransitionMidRadiusMultiplier);
2976e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf2 = Keyframe.ofFloat(1f, mTransitionEndRadiusMultiplier);
2986e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        PropertyValuesHolder radiusDisappear = PropertyValuesHolder.ofKeyframe(
2996e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                "animationRadiusMultiplier", kf0, kf1, kf2);
3006e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3016e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf0 = Keyframe.ofFloat(0f, 1f);
3026e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf1 = Keyframe.ofFloat(1f, 0f);
3036e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        PropertyValuesHolder fadeOut = PropertyValuesHolder.ofKeyframe("alpha", kf0, kf1);
3046e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3056e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mDisappearAnimator = ObjectAnimator.ofPropertyValuesHolder(
3066e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                this, radiusDisappear, fadeOut).setDuration(duration);
3076e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mDisappearAnimator.addUpdateListener(mInvalidateUpdateListener);
3086e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3096e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3106e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        // Set up animator for reappearing.
311b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        float delayMultiplier = 0.25f;
312b8f95646fc0510eebfeaa27864023d630f34090fSam Blitzstein        float transitionDurationMultiplier = 1f;
3136e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier;
3146e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        int totalDuration = (int) (duration * totalDurationMultiplier);
3156e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        float delayPoint = (delayMultiplier * duration) / totalDuration;
3166e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        midwayPoint = 1 - (midwayPoint * (1 - delayPoint));
3176e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3186e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf0 = Keyframe.ofFloat(0f, mTransitionEndRadiusMultiplier);
3196e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf1 = Keyframe.ofFloat(delayPoint, mTransitionEndRadiusMultiplier);
3206e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf2 = Keyframe.ofFloat(midwayPoint, mTransitionMidRadiusMultiplier);
3216e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf3 = Keyframe.ofFloat(1f, 1);
3226e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        PropertyValuesHolder radiusReappear = PropertyValuesHolder.ofKeyframe(
3236e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                "animationRadiusMultiplier", kf0, kf1, kf2, kf3);
3246e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3256e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf0 = Keyframe.ofFloat(0f, 0f);
3266e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf1 = Keyframe.ofFloat(delayPoint, 0f);
3276e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        kf2 = Keyframe.ofFloat(1f, 1f);
3286e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        PropertyValuesHolder fadeIn = PropertyValuesHolder.ofKeyframe("alpha", kf0, kf1, kf2);
3296e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3306e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mReappearAnimator = ObjectAnimator.ofPropertyValuesHolder(
3316e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein                this, radiusReappear, fadeIn).setDuration(totalDuration);
3326e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        mReappearAnimator.addUpdateListener(mInvalidateUpdateListener);
3336e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
3346e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3356e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public ObjectAnimator getDisappearAnimator() {
336f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        if (!mIsInitialized || !mDrawValuesReady || mDisappearAnimator == null) {
3376e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            Log.e(TAG, "RadialTextView was not ready for animation.");
3386e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return null;
3396e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
3406e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3416e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        return mDisappearAnimator;
3426e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
3436e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3446e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    public ObjectAnimator getReappearAnimator() {
345f3b38bd61d583d31200c501f5a74392aac510657Sam Blitzstein        if (!mIsInitialized || !mDrawValuesReady || mReappearAnimator == null) {
3466e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            Log.e(TAG, "RadialTextView was not ready for animation.");
3476e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            return null;
3486e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
3496e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3506e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        return mReappearAnimator;
3516e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
3526e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein
3536e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    private class InvalidateUpdateListener implements AnimatorUpdateListener {
3546e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        @Override
3556e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        public void onAnimationUpdate(ValueAnimator animation) {
3566e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein            RadialTextsView.this.invalidate();
3576e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein        }
3586e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein    }
3596e896f805cac499b777c98755149f07ccd7ba5c3Sam Blitzstein}
360