1eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio/* 2eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Copyright (C) 2013 The Android Open Source Project 3eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * 4eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Licensed under the Apache License, Version 2.0 (the "License"); 5eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * you may not use this file except in compliance with the License. 6eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * You may obtain a copy of the License at 7eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * 8eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * http://www.apache.org/licenses/LICENSE-2.0 9eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * 10eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Unless required by applicable law or agreed to in writing, software 11eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * distributed under the License is distributed on an "AS IS" BASIS, 12eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * See the License for the specific language governing permissions and 14eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * limitations under the License. 15eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */ 16eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 17eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Megliopackage android.widget; 18eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 19eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.animation.ObjectAnimator; 20eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.Context; 21ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.content.res.ColorStateList; 22eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.res.Resources; 23eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.res.TypedArray; 24eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Canvas; 25eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Color; 26eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Paint; 27ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.graphics.Path; 28ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.graphics.Rect; 29ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.graphics.Region; 30eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Typeface; 31eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.os.Bundle; 32eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.util.AttributeSet; 332b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viveretteimport android.util.FloatProperty; 34ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.util.IntArray; 35eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.util.Log; 36ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.util.MathUtils; 37ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.util.StateSet; 385134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viveretteimport android.util.TypedValue; 39eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.HapticFeedbackConstants; 40eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.MotionEvent; 41eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.View; 42eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.accessibility.AccessibilityEvent; 43eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.accessibility.AccessibilityNodeInfo; 44ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 45eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette 46eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport com.android.internal.R; 47ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport com.android.internal.widget.ExploreByTouchHelper; 48eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 49eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport java.util.Calendar; 50eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport java.util.Locale; 51eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 52eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio/** 53eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * View to show a clock circle picker (with one or two picking circles) 54eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * 55eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * @hide 56eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */ 57ec9fe1ad663d59829075029508673b2e913538d3Alan Viverettepublic class RadialTimePickerView extends View { 582b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 59ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private static final String TAG = "RadialTimePickerView"; 60eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 61b0f5461eeca28d6c4be6ba1f826a43a8961f587eAlan Viverette public static final int HOURS = 0; 62b0f5461eeca28d6c4be6ba1f826a43a8961f587eAlan Viverette public static final int MINUTES = 1; 63eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int HOURS_INNER = 2; 64eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 65eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int SELECTOR_CIRCLE = 0; 66eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int SELECTOR_DOT = 1; 67eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int SELECTOR_LINE = 2; 68eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 69eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int AM = 0; 70eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int PM = 1; 71eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 72fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette private static final int HOURS_IN_CIRCLE = 12; 73fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette private static final int MINUTES_IN_CIRCLE = 60; 74fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette private static final int DEGREES_FOR_ONE_HOUR = 360 / HOURS_IN_CIRCLE; 75fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette private static final int DEGREES_FOR_ONE_MINUTE = 360 / MINUTES_IN_CIRCLE; 76eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 77eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int[] HOURS_NUMBERS = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; 78eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int[] HOURS_NUMBERS_24 = {0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; 79eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int[] MINUTES_NUMBERS = {0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55}; 80eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 812b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private static final int ANIM_DURATION_NORMAL = 500; 822b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private static final int ANIM_DURATION_TOUCH = 60; 83ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 84ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private static final int[] SNAP_PREFER_30S_MAP = new int[361]; 85ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 86ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private static final int NUM_POSITIONS = 12; 87ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private static final float[] COS_30 = new float[NUM_POSITIONS]; 88ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private static final float[] SIN_30 = new float[NUM_POSITIONS]; 89ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 902b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette /** "Something is wrong" color used when a color attribute is missing. */ 912b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private static final int MISSING_COLOR = Color.MAGENTA; 922b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 93ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette static { 94ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // Prepare mapping to snap touchable degrees to selectable degrees. 95ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette preparePrefer30sMap(); 96ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 97ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette final double increment = 2.0 * Math.PI / NUM_POSITIONS; 98ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette double angle = Math.PI / 2.0; 99ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette for (int i = 0; i < NUM_POSITIONS; i++) { 100ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette COS_30[i] = (float) Math.cos(angle); 101ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette SIN_30[i] = (float) Math.sin(angle); 102ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette angle += increment; 103ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 104ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 105eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 1062b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private final FloatProperty<RadialTimePickerView> HOURS_TO_MINUTES = 1072b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette new FloatProperty<RadialTimePickerView>("hoursToMinutes") { 1082b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette @Override 1092b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette public Float get(RadialTimePickerView radialTimePickerView) { 1102b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette return radialTimePickerView.mHoursToMinutes; 1112b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 1122b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 1132b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette @Override 1142b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette public void setValue(RadialTimePickerView object, float value) { 1152b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette object.mHoursToMinutes = value; 1162b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette object.invalidate(); 1172b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 1182b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette }; 119ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 120eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private final String[] mHours12Texts = new String[12]; 121eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private final String[] mOuterHours24Texts = new String[12]; 122eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private final String[] mInnerHours24Texts = new String[12]; 123eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private final String[] mMinutesTexts = new String[12]; 124eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 125eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private final Paint[] mPaint = new Paint[2]; 126eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private final Paint mPaintCenter = new Paint(); 1272b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private final Paint[] mPaintSelector = new Paint[3]; 128eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private final Paint mPaintBackground = new Paint(); 129eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 130ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final Typeface mTypeface; 131ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 132adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private final ColorStateList[] mTextColor = new ColorStateList[3]; 133adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private final int[] mTextSize = new int[3]; 134adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private final int[] mTextInset = new int[3]; 135ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 136ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private final float[][] mOuterTextX = new float[2][12]; 137ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private final float[][] mOuterTextY = new float[2][12]; 138ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 139ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private final float[] mInnerTextX = new float[12]; 140ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private final float[] mInnerTextY = new float[12]; 141ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 14288e51032320ab92057666e1230cc6548ca163c51Alan Viverette private final int[] mSelectionDegrees = new int[2]; 143ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 144ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final RadialPickerTouchHelper mTouchHelper; 145ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 146f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette private final Path mSelectorPath = new Path(); 147f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette 148eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private boolean mIs24HourMode; 149eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private boolean mShowHours; 150d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette 1512b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private ObjectAnimator mHoursToMinutesAnimator; 1522b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private float mHoursToMinutes; 1532b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 154d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette /** 155d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * When in 24-hour mode, indicates that the current hour is between 156d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * 1 and 12 (inclusive). 157d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette */ 158eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private boolean mIsOnInnerCircle; 159eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 160adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private int mSelectorRadius; 16162c79e9a64c3b2cafd5500ed3064977dff7b7da3Alan Viverette private int mSelectorStroke; 162adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private int mSelectorDotRadius; 163adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private int mCenterDotRadius; 164adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette 1652b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private int mSelectorColor; 1662b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private int mSelectorDotColor; 1672b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 168adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private int mXCenter; 169adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private int mYCenter; 170adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private int mCircleRadius; 171eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 17288e51032320ab92057666e1230cc6548ca163c51Alan Viverette private int mMinDistForInnerNumber; 17388e51032320ab92057666e1230cc6548ca163c51Alan Viverette private int mMaxDistForOuterNumber; 17488e51032320ab92057666e1230cc6548ca163c51Alan Viverette private int mHalfwayDist; 175eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 176eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private String[] mOuterTextHours; 177eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private String[] mInnerTextHours; 17888e51032320ab92057666e1230cc6548ca163c51Alan Viverette private String[] mMinutesText; 179eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 180eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private int mAmOrPm; 181adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette 182adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private float mDisabledAlpha; 1835134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette 184eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private OnValueSelectedListener mListener; 185eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 186ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private boolean mInputEnabled = true; 187eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 188eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public interface OnValueSelectedListener { 189eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance); 190eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 191eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 192eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio /** 193eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Split up the 360 degrees of the circle among the 60 selectable values. Assigns a larger 194eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * selectable area to each of the 12 visible values, such that the ratio of space apportioned 195eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * to a visible value : space apportioned to a non-visible value will be 14 : 4. 196eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * E.g. the output of 30 degrees should have a higher range of input associated with it than 197eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * the output of 24 degrees, because 30 degrees corresponds to a visible number on the clock 198eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * circle (5 on the minutes, 1 or 13 on the hours). 199eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */ 200eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static void preparePrefer30sMap() { 201eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // We'll split up the visible output and the non-visible output such that each visible 202eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // output will correspond to a range of 14 associated input degrees, and each non-visible 203eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // output will correspond to a range of 4 associate input degrees, so visible numbers 204eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // are more than 3 times easier to get than non-visible numbers: 205eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // {354-359,0-7}:0, {8-11}:6, {12-15}:12, {16-19}:18, {20-23}:24, {24-37}:30, etc. 206eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // 207eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // If an output of 30 degrees should correspond to a range of 14 associated degrees, then 208eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // we'll need any input between 24 - 37 to snap to 30. Working out from there, 20-23 should 209eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // snap to 24, while 38-41 should snap to 36. This is somewhat counter-intuitive, that you 210eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // can be touching 36 degrees but have the selection snapped to 30 degrees; however, this 211eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // inconsistency isn't noticeable at such fine-grained degrees, and it affords us the 212eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // ability to aggressively prefer the visible values by a factor of more than 3:1, which 213eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // greatly contributes to the selectability of these values. 214eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 215eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // The first output is 0, and each following output will increment by 6 {0, 6, 12, ...}. 216eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio int snappedOutputDegrees = 0; 217eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Count of how many inputs we've designated to the specified output. 218eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio int count = 1; 219eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // How many input we expect for a specified output. This will be 14 for output divisible 220eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // by 30, and 4 for the remaining output. We'll special case the outputs of 0 and 360, so 221eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // the caller can decide which they need. 222eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio int expectedCount = 8; 223eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Iterate through the input. 224eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio for (int degrees = 0; degrees < 361; degrees++) { 225eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Save the input-output mapping. 226ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette SNAP_PREFER_30S_MAP[degrees] = snappedOutputDegrees; 227eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // If this is the last input for the specified output, calculate the next output and 228eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // the next expected count. 229eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (count == expectedCount) { 230eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio snappedOutputDegrees += 6; 231eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (snappedOutputDegrees == 360) { 232eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio expectedCount = 7; 233eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else if (snappedOutputDegrees % 30 == 0) { 234eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio expectedCount = 14; 235eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 236eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio expectedCount = 4; 237eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 238eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio count = 1; 239eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 240eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio count++; 241eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 242eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 243eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 244eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 245eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio /** 246eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Returns mapping of any input degrees (0 to 360) to one of 60 selectable output degrees, 247eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * where the degrees corresponding to visible numbers (i.e. those divisible by 30) will be 248eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * weighted heavier than the degrees corresponding to non-visible numbers. 249eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * See {@link #preparePrefer30sMap()} documentation for the rationale and generation of the 250eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * mapping. 251eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */ 252eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static int snapPrefer30s(int degrees) { 253ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette if (SNAP_PREFER_30S_MAP == null) { 254eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio return -1; 255eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 256ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette return SNAP_PREFER_30S_MAP[degrees]; 257eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 258eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 259eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio /** 260eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Returns mapping of any input degrees (0 to 360) to one of 12 visible output degrees (all 261eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * multiples of 30), where the input will be "snapped" to the closest visible degrees. 262eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * @param degrees The input degrees 263eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * @param forceHigherOrLower The output may be forced to either the higher or lower step, or may 264eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * be allowed to snap to whichever is closer. Use 1 to force strictly higher, -1 to force 265eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * strictly lower, and 0 to snap to the closer one. 266eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * @return output degrees, will be a multiple of 30 267eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */ 268eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static int snapOnly30s(int degrees, int forceHigherOrLower) { 269eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio final int stepSize = DEGREES_FOR_ONE_HOUR; 270eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio int floor = (degrees / stepSize) * stepSize; 271eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio final int ceiling = floor + stepSize; 272eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (forceHigherOrLower == 1) { 273eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio degrees = ceiling; 274eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else if (forceHigherOrLower == -1) { 275eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (degrees == floor) { 276eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio floor -= stepSize; 277eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 278eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio degrees = floor; 279eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 280eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if ((degrees - floor) < (ceiling - degrees)) { 281eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio degrees = floor; 282eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 283eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio degrees = ceiling; 284eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 285eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 286eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio return degrees; 287eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 288eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 289ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @SuppressWarnings("unused") 290ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public RadialTimePickerView(Context context) { 291ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette this(context, null); 292ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 293ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 294eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public RadialTimePickerView(Context context, AttributeSet attrs) { 295eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio this(context, attrs, R.attr.timePickerStyle); 296eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 297eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 298ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public RadialTimePickerView(Context context, AttributeSet attrs, int defStyleAttr) { 299ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette this(context, attrs, defStyleAttr, 0); 300ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 301ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 302ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public RadialTimePickerView( 303ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 304eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio super(context, attrs); 305eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 3062b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette applyAttributes(attrs, defStyleAttr, defStyleRes); 3072b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 3085134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette // Pull disabled alpha from theme. 3095134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette final TypedValue outValue = new TypedValue(); 3105134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true); 311adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mDisabledAlpha = outValue.getFloat(); 3125134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette 313eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mTypeface = Typeface.create("sans-serif", Typeface.NORMAL); 314eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 315eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaint[HOURS] = new Paint(); 316eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaint[HOURS].setAntiAlias(true); 317eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaint[HOURS].setTextAlign(Paint.Align.CENTER); 318eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 319eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaint[MINUTES] = new Paint(); 320eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaint[MINUTES].setAntiAlias(true); 321eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaint[MINUTES].setTextAlign(Paint.Align.CENTER); 322eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 323eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaintCenter.setAntiAlias(true); 324ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 3252b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintSelector[SELECTOR_CIRCLE] = new Paint(); 3262b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintSelector[SELECTOR_CIRCLE].setAntiAlias(true); 327eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 3282b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintSelector[SELECTOR_DOT] = new Paint(); 3292b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintSelector[SELECTOR_DOT].setAntiAlias(true); 330eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 3312b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintSelector[SELECTOR_LINE] = new Paint(); 3322b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintSelector[SELECTOR_LINE].setAntiAlias(true); 3332b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintSelector[SELECTOR_LINE].setStrokeWidth(2); 334eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 335eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaintBackground.setAntiAlias(true); 336eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 3372b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final Resources res = getResources(); 338adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mSelectorRadius = res.getDimensionPixelSize(R.dimen.timepicker_selector_radius); 33962c79e9a64c3b2cafd5500ed3064977dff7b7da3Alan Viverette mSelectorStroke = res.getDimensionPixelSize(R.dimen.timepicker_selector_stroke); 340adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mSelectorDotRadius = res.getDimensionPixelSize(R.dimen.timepicker_selector_dot_radius); 341adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mCenterDotRadius = res.getDimensionPixelSize(R.dimen.timepicker_center_dot_radius); 342adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette 343adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mTextSize[HOURS] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_normal); 344adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mTextSize[MINUTES] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_normal); 345adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mTextSize[HOURS_INNER] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_inner); 346adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette 347adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mTextInset[HOURS] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_normal); 348adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mTextInset[MINUTES] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_normal); 349adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mTextInset[HOURS_INNER] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_inner); 350eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 351eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mShowHours = true; 3522b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mHoursToMinutes = HOURS; 353eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mIs24HourMode = false; 354eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mAmOrPm = AM; 355eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 356ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // Set up accessibility components. 357ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mTouchHelper = new RadialPickerTouchHelper(); 358ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setAccessibilityDelegate(mTouchHelper); 359ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 360ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 361ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 362ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 363ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 364eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio initHoursAndMinutesText(); 365eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio initData(); 366eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 367eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Initial values 368eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio final Calendar calendar = Calendar.getInstance(Locale.getDefault()); 369eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio final int currentHour = calendar.get(Calendar.HOUR_OF_DAY); 370eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio final int currentMinute = calendar.get(Calendar.MINUTE); 371eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 372ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentHourInternal(currentHour, false, false); 373ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentMinuteInternal(currentMinute, false); 374eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 375eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio setHapticFeedbackEnabled(true); 376eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 377eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 3782b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette void applyAttributes(AttributeSet attrs, int defStyleAttr, int defStyleRes) { 3792b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final Context context = getContext(); 3802b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final TypedArray a = getContext().obtainStyledAttributes(attrs, 3812b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette R.styleable.TimePicker, defStyleAttr, defStyleRes); 3822b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 3832b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final ColorStateList numbersTextColor = a.getColorStateList( 3842b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette R.styleable.TimePicker_numbersTextColor); 3852b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final ColorStateList numbersInnerTextColor = a.getColorStateList( 3862b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette R.styleable.TimePicker_numbersInnerTextColor); 3872b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mTextColor[HOURS] = numbersTextColor == null ? 3882b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette ColorStateList.valueOf(MISSING_COLOR) : numbersTextColor; 3892b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mTextColor[HOURS_INNER] = numbersInnerTextColor == null ? 3902b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette ColorStateList.valueOf(MISSING_COLOR) : numbersInnerTextColor; 3912b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mTextColor[MINUTES] = mTextColor[HOURS]; 3922b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 3932b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // Set up various colors derived from the selector "activated" state. 3942b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final ColorStateList selectorColors = a.getColorStateList( 3952b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette R.styleable.TimePicker_numbersSelectorColor); 3962b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int selectorActivatedColor; 3972b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (selectorColors != null) { 3982b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int[] stateSetEnabledActivated = StateSet.get( 3992b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED); 4002b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette selectorActivatedColor = selectorColors.getColorForState( 4012b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette stateSetEnabledActivated, 0); 4022b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } else { 4032b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette selectorActivatedColor = MISSING_COLOR; 4042b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 4052b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 4062b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintCenter.setColor(selectorActivatedColor); 4072b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 4082b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int[] stateSetActivated = StateSet.get( 4092b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED); 4102b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 4112b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mSelectorColor = selectorActivatedColor; 4122b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mSelectorDotColor = mTextColor[HOURS].getColorForState(stateSetActivated, 0); 4132b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 4142b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintBackground.setColor(a.getColor(R.styleable.TimePicker_numbersBackgroundColor, 4152b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette context.getColor(R.color.timepicker_default_numbers_background_color_material))); 4162b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 4172b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette a.recycle(); 4182b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 4192b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 420eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void initialize(int hour, int minute, boolean is24HourMode) { 421448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette if (mIs24HourMode != is24HourMode) { 422448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette mIs24HourMode = is24HourMode; 423448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette initData(); 424448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette } 425ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 426ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentHourInternal(hour, false, false); 427ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentMinuteInternal(minute, false); 428eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 429eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 430eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void setCurrentItemShowing(int item, boolean animate) { 431eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio switch (item){ 432eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio case HOURS: 433eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio showHours(animate); 434eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio break; 435eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio case MINUTES: 436eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio showMinutes(animate); 437eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio break; 438eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio default: 439eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio Log.e(TAG, "ClockView does not support showing item " + item); 440eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 441eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 442eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 443eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public int getCurrentItemShowing() { 444eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio return mShowHours ? HOURS : MINUTES; 445eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 446eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 447eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void setOnValueSelectedListener(OnValueSelectedListener listener) { 448eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mListener = listener; 449eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 450eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 451d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette /** 452d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * Sets the current hour in 24-hour time. 453d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * 454d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * @param hour the current hour between 0 and 23 (inclusive) 455d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette */ 456eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void setCurrentHour(int hour) { 457ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentHourInternal(hour, true, false); 458ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 459ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 460ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette /** 461ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette * Sets the current hour. 462ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette * 463ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette * @param hour The current hour 464ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette * @param callback Whether the value listener should be invoked 465ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette * @param autoAdvance Whether the listener should auto-advance to the next 466ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette * selection mode, e.g. hour to minutes 467ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette */ 468ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private void setCurrentHourInternal(int hour, boolean callback, boolean autoAdvance) { 469eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio final int degrees = (hour % 12) * DEGREES_FOR_ONE_HOUR; 470eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mSelectionDegrees[HOURS] = degrees; 471d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette 472d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette // 0 is 12 AM (midnight) and 12 is 12 PM (noon). 473ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int amOrPm = (hour == 0 || (hour % 24) < 12) ? AM : PM; 47488e51032320ab92057666e1230cc6548ca163c51Alan Viverette final boolean isOnInnerCircle = getInnerCircleForHour(hour); 475ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (mAmOrPm != amOrPm || mIsOnInnerCircle != isOnInnerCircle) { 476ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mAmOrPm = amOrPm; 477ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mIsOnInnerCircle = isOnInnerCircle; 478ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 479ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette initData(); 480ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mTouchHelper.invalidateRoot(); 481ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 482d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette 483eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio invalidate(); 484ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 485ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (callback && mListener != null) { 486ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mListener.onValueSelected(HOURS, hour, autoAdvance); 487ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 488eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 489eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 490d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette /** 491d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * Returns the current hour in 24-hour time. 492d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * 493d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * @return the current hour between 0 and 23 (inclusive) 494d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette */ 495eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public int getCurrentHour() { 49688e51032320ab92057666e1230cc6548ca163c51Alan Viverette return getHourForDegrees(mSelectionDegrees[HOURS], mIsOnInnerCircle); 497ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 498ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 499ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int getHourForDegrees(int degrees, boolean innerCircle) { 500ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette int hour = (degrees / DEGREES_FOR_ONE_HOUR) % 12; 501eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (mIs24HourMode) { 502d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette // Convert the 12-hour value into 24-hour time based on where the 503d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette // selector is positioned. 50488e51032320ab92057666e1230cc6548ca163c51Alan Viverette if (!innerCircle && hour == 0) { 50588e51032320ab92057666e1230cc6548ca163c51Alan Viverette // Outer circle is 1 through 12. 506d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette hour = 12; 50788e51032320ab92057666e1230cc6548ca163c51Alan Viverette } else if (innerCircle && hour != 0) { 50888e51032320ab92057666e1230cc6548ca163c51Alan Viverette // Inner circle is 13 through 23 and 0. 509d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette hour += 12; 510eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 511d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette } else if (mAmOrPm == PM) { 512d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette hour += 12; 513eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 514d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette return hour; 515eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 516eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 51788e51032320ab92057666e1230cc6548ca163c51Alan Viverette /** 51888e51032320ab92057666e1230cc6548ca163c51Alan Viverette * @param hour the hour in 24-hour time or 12-hour time 51988e51032320ab92057666e1230cc6548ca163c51Alan Viverette */ 520ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int getDegreesForHour(int hour) { 521ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // Convert to be 0-11. 522ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (mIs24HourMode) { 523ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (hour >= 12) { 524ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette hour -= 12; 525ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 526ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else if (hour == 12) { 527ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette hour = 0; 528ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 529ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return hour * DEGREES_FOR_ONE_HOUR; 530ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 531ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 53288e51032320ab92057666e1230cc6548ca163c51Alan Viverette /** 53388e51032320ab92057666e1230cc6548ca163c51Alan Viverette * @param hour the hour in 24-hour time or 12-hour time 53488e51032320ab92057666e1230cc6548ca163c51Alan Viverette */ 53588e51032320ab92057666e1230cc6548ca163c51Alan Viverette private boolean getInnerCircleForHour(int hour) { 53688e51032320ab92057666e1230cc6548ca163c51Alan Viverette return mIs24HourMode && (hour == 0 || hour > 12); 53788e51032320ab92057666e1230cc6548ca163c51Alan Viverette } 53888e51032320ab92057666e1230cc6548ca163c51Alan Viverette 539eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void setCurrentMinute(int minute) { 540ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentMinuteInternal(minute, true); 541ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 542ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 543ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private void setCurrentMinuteInternal(int minute, boolean callback) { 544fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette mSelectionDegrees[MINUTES] = (minute % MINUTES_IN_CIRCLE) * DEGREES_FOR_ONE_MINUTE; 545ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 546eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio invalidate(); 547ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 548ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (callback && mListener != null) { 549ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mListener.onValueSelected(MINUTES, minute, false); 550ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 551eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 552eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 553eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Returns minutes in 0-59 range 554eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public int getCurrentMinute() { 555ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return getMinuteForDegrees(mSelectionDegrees[MINUTES]); 556ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 557ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 558ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int getMinuteForDegrees(int degrees) { 559ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return degrees / DEGREES_FOR_ONE_MINUTE; 560ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 561ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 562ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int getDegreesForMinute(int minute) { 563ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return minute * DEGREES_FOR_ONE_MINUTE; 564eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 565eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 56630b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette /** 56730b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette * Sets whether the picker is showing AM or PM hours. Has no effect when 56830b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette * in 24-hour mode. 56930b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette * 57030b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette * @param amOrPm {@link #AM} or {@link #PM} 57130b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette * @return {@code true} if the value changed from what was previously set, 57230b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette * or {@code false} otherwise 57330b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette */ 57430b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette public boolean setAmOrPm(int amOrPm) { 57530b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette if (mAmOrPm == amOrPm || mIs24HourMode) { 57630b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette return false; 57730b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette } 57830b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette 57930b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette mAmOrPm = amOrPm; 580eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio invalidate(); 581ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mTouchHelper.invalidateRoot(); 58230b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette return true; 583eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 584eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 585eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public int getAmOrPm() { 586eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio return mAmOrPm; 587eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 588eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 589eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void showHours(boolean animate) { 5902b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette showPicker(true, animate); 591eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 592eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 593eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void showMinutes(boolean animate) { 5942b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette showPicker(false, animate); 595eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 596eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 597eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private void initHoursAndMinutesText() { 598eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Initialize the hours and minutes numbers. 599eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio for (int i = 0; i < 12; i++) { 600eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mHours12Texts[i] = String.format("%d", HOURS_NUMBERS[i]); 601f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette mInnerHours24Texts[i] = String.format("%02d", HOURS_NUMBERS_24[i]); 602f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette mOuterHours24Texts[i] = String.format("%d", HOURS_NUMBERS[i]); 603eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mMinutesTexts[i] = String.format("%02d", MINUTES_NUMBERS[i]); 604eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 605eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 606eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 607eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private void initData() { 608eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (mIs24HourMode) { 609eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mOuterTextHours = mOuterHours24Texts; 610eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mInnerTextHours = mInnerHours24Texts; 611eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 612eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mOuterTextHours = mHours12Texts; 613f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette mInnerTextHours = mHours12Texts; 614eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 615eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 61688e51032320ab92057666e1230cc6548ca163c51Alan Viverette mMinutesText = mMinutesTexts; 617eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 618eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 619eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio @Override 620eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 621ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette if (!changed) { 622ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette return; 623ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 624eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 625eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mXCenter = getWidth() / 2; 626eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mYCenter = getHeight() / 2; 627ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette mCircleRadius = Math.min(mXCenter, mYCenter); 628eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 62988e51032320ab92057666e1230cc6548ca163c51Alan Viverette mMinDistForInnerNumber = mCircleRadius - mTextInset[HOURS_INNER] - mSelectorRadius; 63088e51032320ab92057666e1230cc6548ca163c51Alan Viverette mMaxDistForOuterNumber = mCircleRadius - mTextInset[HOURS] + mSelectorRadius; 63188e51032320ab92057666e1230cc6548ca163c51Alan Viverette mHalfwayDist = mCircleRadius - (mTextInset[HOURS] + mTextInset[HOURS_INNER]) / 2; 632eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 633ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette calculatePositionsHours(); 634ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette calculatePositionsMinutes(); 635eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 636ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mTouchHelper.invalidateRoot(); 637eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 638eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 639eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio @Override 640eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void onDraw(Canvas canvas) { 641adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette final float alphaMod = mInputEnabled ? 1 : mDisabledAlpha; 642eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 643eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio drawCircleBackground(canvas); 6442b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 6452b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final Path selectorPath = mSelectorPath; 6462b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawSelector(canvas, selectorPath); 6472b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawHours(canvas, selectorPath, alphaMod); 6482b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawMinutes(canvas, selectorPath, alphaMod); 649adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette drawCenter(canvas, alphaMod); 650adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette } 651eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 6522b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private void showPicker(boolean hours, boolean animate) { 6532b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (mShowHours == hours) { 6542b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette return; 6552b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 6562b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 6572b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mShowHours = hours; 6582b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 6592b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (animate) { 6602b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette animatePicker(hours, ANIM_DURATION_NORMAL); 66132f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette } else { 66232f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette // If we have a pending or running animator, cancel it. 66332f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette if (mHoursToMinutesAnimator != null && mHoursToMinutesAnimator.isStarted()) { 66432f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette mHoursToMinutesAnimator.cancel(); 66532f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette mHoursToMinutesAnimator = null; 66632f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette } 66732f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette mHoursToMinutes = hours ? 0.0f : 1.0f; 6682b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 6692b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 6702b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette initData(); 6712b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette invalidate(); 6722b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mTouchHelper.invalidateRoot(); 6732b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 6742b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 6752b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private void animatePicker(boolean hoursToMinutes, long duration) { 6762b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final float target = hoursToMinutes ? HOURS : MINUTES; 6772b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (mHoursToMinutes == target) { 6782b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // If we have a pending or running animator, cancel it. 6792b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (mHoursToMinutesAnimator != null && mHoursToMinutesAnimator.isStarted()) { 6802b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mHoursToMinutesAnimator.cancel(); 6812b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mHoursToMinutesAnimator = null; 6822b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 6832b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 6842b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // We're already showing the correct picker. 6852b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette return; 6862b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 6872b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 6882b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mHoursToMinutesAnimator = ObjectAnimator.ofFloat(this, HOURS_TO_MINUTES, target); 6892b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mHoursToMinutesAnimator.setAutoCancel(true); 6902b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mHoursToMinutesAnimator.setDuration(duration); 6912b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mHoursToMinutesAnimator.start(); 6922b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 6932b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 694adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private void drawCircleBackground(Canvas canvas) { 695adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette canvas.drawCircle(mXCenter, mYCenter, mCircleRadius, mPaintBackground); 696adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette } 697adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette 6982b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private void drawHours(Canvas canvas, Path selectorPath, float alphaMod) { 6992b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int hoursAlpha = (int) (255f * (1f - mHoursToMinutes) * alphaMod + 0.5f); 700ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette if (hoursAlpha > 0) { 7012b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // Exclude the selector region, then draw inner/outer hours with no 7022b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // activated states. 7032b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.save(Canvas.CLIP_SAVE_FLAG); 7042b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.clipPath(selectorPath, Region.Op.DIFFERENCE); 7052b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawHoursClipped(canvas, hoursAlpha, false); 7062b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.restore(); 7072b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 7082b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // Intersect the selector region, then draw minutes with only 7092b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // activated states. 7102b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.save(Canvas.CLIP_SAVE_FLAG); 7112b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.clipPath(selectorPath, Region.Op.INTERSECT); 7122b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawHoursClipped(canvas, hoursAlpha, true); 7132b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.restore(); 714eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 715adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette } 716eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 7172b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private void drawHoursClipped(Canvas canvas, int hoursAlpha, boolean showActivated) { 7182b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // Draw outer hours. 7192b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawTextElements(canvas, mTextSize[HOURS], mTypeface, mTextColor[HOURS], mOuterTextHours, 7202b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mOuterTextX[HOURS], mOuterTextY[HOURS], mPaint[HOURS], hoursAlpha, 7212b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette showActivated && !mIsOnInnerCircle, mSelectionDegrees[HOURS], showActivated); 722ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 7232b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // Draw inner hours (13-00) for 24-hour time. 7242b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (mIs24HourMode && mInnerTextHours != null) { 7252b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawTextElements(canvas, mTextSize[HOURS_INNER], mTypeface, mTextColor[HOURS_INNER], 7262b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mInnerTextHours, mInnerTextX, mInnerTextY, mPaint[HOURS], hoursAlpha, 7272b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette showActivated && mIsOnInnerCircle, mSelectionDegrees[HOURS], showActivated); 7282b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 7292b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 7302b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 7312b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private void drawMinutes(Canvas canvas, Path selectorPath, float alphaMod) { 7322b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int minutesAlpha = (int) (255f * mHoursToMinutes * alphaMod + 0.5f); 7332b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (minutesAlpha > 0) { 734ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // Exclude the selector region, then draw minutes with no 735ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // activated states. 736ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette canvas.save(Canvas.CLIP_SAVE_FLAG); 7372b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.clipPath(selectorPath, Region.Op.DIFFERENCE); 7382b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawMinutesClipped(canvas, minutesAlpha, false); 739ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette canvas.restore(); 740ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 741ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // Intersect the selector region, then draw minutes with only 742ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // activated states. 743ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette canvas.save(Canvas.CLIP_SAVE_FLAG); 7442b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.clipPath(selectorPath, Region.Op.INTERSECT); 7452b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawMinutesClipped(canvas, minutesAlpha, true); 746ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette canvas.restore(); 747ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 748eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 749eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 7502b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private void drawMinutesClipped(Canvas canvas, int minutesAlpha, boolean showActivated) { 7512b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES], mMinutesText, 7522b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], minutesAlpha, 7532b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette showActivated, mSelectionDegrees[MINUTES], showActivated); 7542b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 7552b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 756adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private void drawCenter(Canvas canvas, float alphaMod) { 757adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mPaintCenter.setAlpha((int) (255 * alphaMod + 0.5f)); 758adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette canvas.drawCircle(mXCenter, mYCenter, mCenterDotRadius, mPaintCenter); 759eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 760eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 761eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette private int getMultipliedAlpha(int argb, int alpha) { 762eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette return (int) (Color.alpha(argb) * (alpha / 255.0) + 0.5); 763eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette } 764eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette 7652b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private void drawSelector(Canvas canvas, Path selectorPath) { 7662b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // Determine the current length, angle, and dot scaling factor. 7672b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int hoursIndex = mIsOnInnerCircle ? HOURS_INNER : HOURS; 7682b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int hoursInset = mTextInset[hoursIndex]; 7692b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int hoursAngleDeg = mSelectionDegrees[hoursIndex % 2]; 7702b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final float hoursDotScale = mSelectionDegrees[hoursIndex % 2] % 30 != 0 ? 1 : 0; 7712b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 7722b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int minutesIndex = MINUTES; 7732b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int minutesInset = mTextInset[minutesIndex]; 7742b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int minutesAngleDeg = mSelectionDegrees[minutesIndex]; 7752b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final float minutesDotScale = mSelectionDegrees[minutesIndex] % 30 != 0 ? 1 : 0; 776eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 777f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette // Calculate the current radius at which to place the selection circle. 778f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette final int selRadius = mSelectorRadius; 7792b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final float selLength = 7802b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mCircleRadius - MathUtils.lerp(hoursInset, minutesInset, mHoursToMinutes); 7812b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final double selAngleRad = 7822b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette Math.toRadians(MathUtils.lerpDeg(hoursAngleDeg, minutesAngleDeg, mHoursToMinutes)); 783f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette final float selCenterX = mXCenter + selLength * (float) Math.sin(selAngleRad); 784f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette final float selCenterY = mYCenter - selLength * (float) Math.cos(selAngleRad); 785f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette 786f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette // Draw the selection circle. 7872b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final Paint paint = mPaintSelector[SELECTOR_CIRCLE]; 7882b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette paint.setColor(mSelectorColor); 789f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette canvas.drawCircle(selCenterX, selCenterY, selRadius, paint); 790eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 791ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // If needed, set up the clip path for later. 792ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette if (selectorPath != null) { 793f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette selectorPath.reset(); 794f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette selectorPath.addCircle(selCenterX, selCenterY, selRadius, Path.Direction.CCW); 795ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 796ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 797f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette // Draw the dot if we're between two items. 7982b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final float dotScale = MathUtils.lerp(hoursDotScale, minutesDotScale, mHoursToMinutes); 7992b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (dotScale > 0) { 8002b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final Paint dotPaint = mPaintSelector[SELECTOR_DOT]; 80188e51032320ab92057666e1230cc6548ca163c51Alan Viverette dotPaint.setColor(mSelectorDotColor); 8022b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.drawCircle(selCenterX, selCenterY, mSelectorDotRadius * dotScale, dotPaint); 803eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 804eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 805adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette // Shorten the line to only go from the edge of the center dot to the 806adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette // edge of the selection circle. 807f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette final double sin = Math.sin(selAngleRad); 808f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette final double cos = Math.cos(selAngleRad); 8092b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final float lineLength = selLength - selRadius; 810adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette final int centerX = mXCenter + (int) (mCenterDotRadius * sin); 811adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette final int centerY = mYCenter - (int) (mCenterDotRadius * cos); 812f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette final float linePointX = centerX + (int) (lineLength * sin); 813f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette final float linePointY = centerY - (int) (lineLength * cos); 814ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 815f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette // Draw the line. 8162b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final Paint linePaint = mPaintSelector[SELECTOR_LINE]; 8172b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette linePaint.setColor(mSelectorColor); 818f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette linePaint.setStrokeWidth(mSelectorStroke); 819f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette canvas.drawLine(mXCenter, mYCenter, linePointX, linePointY, linePaint); 820eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 821eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 822ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private void calculatePositionsHours() { 823eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Calculate the text positions 824adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette final float numbersRadius = mCircleRadius - mTextInset[HOURS]; 825eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 826eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Calculate the positions for the 12 numbers in the main circle. 827ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette calculatePositions(mPaint[HOURS], numbersRadius, mXCenter, mYCenter, 828ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette mTextSize[HOURS], mOuterTextX[HOURS], mOuterTextY[HOURS]); 829eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 830eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // If we have an inner circle, calculate those positions too. 831eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (mIs24HourMode) { 832adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette final int innerNumbersRadius = mCircleRadius - mTextInset[HOURS_INNER]; 833ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette calculatePositions(mPaint[HOURS], innerNumbersRadius, mXCenter, mYCenter, 834ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette mTextSize[HOURS_INNER], mInnerTextX, mInnerTextY); 835eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 836eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 837eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 838ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private void calculatePositionsMinutes() { 839eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Calculate the text positions 840adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette final float numbersRadius = mCircleRadius - mTextInset[MINUTES]; 841eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 842eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Calculate the positions for the 12 numbers in the main circle. 843ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette calculatePositions(mPaint[MINUTES], numbersRadius, mXCenter, mYCenter, 844ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette mTextSize[MINUTES], mOuterTextX[MINUTES], mOuterTextY[MINUTES]); 845eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 846eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 847eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio /** 848eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Using the trigonometric Unit Circle, calculate the positions that the text will need to be 849eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * drawn at based on the specified circle radius. Place the values in the textGridHeights and 850eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * textGridWidths parameters. 851eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */ 852ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private static void calculatePositions(Paint paint, float radius, float xCenter, float yCenter, 853ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette float textSize, float[] x, float[] y) { 854ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // Adjust yCenter to account for the text's baseline. 855eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio paint.setTextSize(textSize); 856eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio yCenter -= (paint.descent() + paint.ascent()) / 2; 857eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 858ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette for (int i = 0; i < NUM_POSITIONS; i++) { 859ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette x[i] = xCenter - radius * COS_30[i]; 860ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette y[i] = yCenter - radius * SIN_30[i]; 861ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 862eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 863eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 864eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio /** 865eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Draw the 12 text values at the positions specified by the textGrid parameters. 866eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */ 867adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private void drawTextElements(Canvas canvas, float textSize, Typeface typeface, 868adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette ColorStateList textColor, String[] texts, float[] textX, float[] textY, Paint paint, 869adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette int alpha, boolean showActivated, int activatedDegrees, boolean activatedOnly) { 870eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio paint.setTextSize(textSize); 871eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio paint.setTypeface(typeface); 872eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 873ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // The activated index can touch a range of elements. 874ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette final float activatedIndex = activatedDegrees / (360.0f / NUM_POSITIONS); 875ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette final int activatedFloor = (int) activatedIndex; 876ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette final int activatedCeil = ((int) Math.ceil(activatedIndex)) % NUM_POSITIONS; 877eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 878ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette for (int i = 0; i < 12; i++) { 879ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette final boolean activated = (activatedFloor == i || activatedCeil == i); 880ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette if (activatedOnly && !activated) { 881ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette continue; 882ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 883eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 884ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette final int stateMask = StateSet.VIEW_STATE_ENABLED 885ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette | (showActivated && activated ? StateSet.VIEW_STATE_ACTIVATED : 0); 886adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette final int color = textColor.getColorForState(StateSet.get(stateMask), 0); 887ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette paint.setColor(color); 888ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette paint.setAlpha(getMultipliedAlpha(color, alpha)); 889eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 890ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette canvas.drawText(texts[i], textX[i], textY[i], paint); 891ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 892eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 893eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 894ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private int getDegreesFromXY(float x, float y, boolean constrainOutside) { 89588e51032320ab92057666e1230cc6548ca163c51Alan Viverette // Ensure the point is inside the touchable area. 89688e51032320ab92057666e1230cc6548ca163c51Alan Viverette final int innerBound; 89788e51032320ab92057666e1230cc6548ca163c51Alan Viverette final int outerBound; 89888e51032320ab92057666e1230cc6548ca163c51Alan Viverette if (mIs24HourMode && mShowHours) { 89988e51032320ab92057666e1230cc6548ca163c51Alan Viverette innerBound = mMinDistForInnerNumber; 90088e51032320ab92057666e1230cc6548ca163c51Alan Viverette outerBound = mMaxDistForOuterNumber; 90188e51032320ab92057666e1230cc6548ca163c51Alan Viverette } else { 90288e51032320ab92057666e1230cc6548ca163c51Alan Viverette final int index = mShowHours ? HOURS : MINUTES; 90388e51032320ab92057666e1230cc6548ca163c51Alan Viverette final int center = mCircleRadius - mTextInset[index]; 90488e51032320ab92057666e1230cc6548ca163c51Alan Viverette innerBound = center - mSelectorRadius; 90588e51032320ab92057666e1230cc6548ca163c51Alan Viverette outerBound = center + mSelectorRadius; 90688e51032320ab92057666e1230cc6548ca163c51Alan Viverette } 907eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 90888e51032320ab92057666e1230cc6548ca163c51Alan Viverette final double dX = x - mXCenter; 90988e51032320ab92057666e1230cc6548ca163c51Alan Viverette final double dY = y - mYCenter; 91088e51032320ab92057666e1230cc6548ca163c51Alan Viverette final double distFromCenter = Math.sqrt(dX * dX + dY * dY); 91188e51032320ab92057666e1230cc6548ca163c51Alan Viverette if (distFromCenter < innerBound || constrainOutside && distFromCenter > outerBound) { 912eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio return -1; 913eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 914ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 91588e51032320ab92057666e1230cc6548ca163c51Alan Viverette // Convert to degrees. 91688e51032320ab92057666e1230cc6548ca163c51Alan Viverette final int degrees = (int) (Math.toDegrees(Math.atan2(dY, dX) + Math.PI / 2) + 0.5); 91788e51032320ab92057666e1230cc6548ca163c51Alan Viverette if (degrees < 0) { 91888e51032320ab92057666e1230cc6548ca163c51Alan Viverette return degrees + 360; 919eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 92088e51032320ab92057666e1230cc6548ca163c51Alan Viverette return degrees; 921eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 92288e51032320ab92057666e1230cc6548ca163c51Alan Viverette } 923eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 92488e51032320ab92057666e1230cc6548ca163c51Alan Viverette private boolean getInnerCircleFromXY(float x, float y) { 92588e51032320ab92057666e1230cc6548ca163c51Alan Viverette if (mIs24HourMode && mShowHours) { 92688e51032320ab92057666e1230cc6548ca163c51Alan Viverette final double dX = x - mXCenter; 92788e51032320ab92057666e1230cc6548ca163c51Alan Viverette final double dY = y - mYCenter; 92888e51032320ab92057666e1230cc6548ca163c51Alan Viverette final double distFromCenter = Math.sqrt(dX * dX + dY * dY); 92988e51032320ab92057666e1230cc6548ca163c51Alan Viverette return distFromCenter <= mHalfwayDist; 930eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 93188e51032320ab92057666e1230cc6548ca163c51Alan Viverette return false; 932eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 933eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 934002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette boolean mChangedDuringTouch = false; 935002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 936eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio @Override 937ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette public boolean onTouchEvent(MotionEvent event) { 938002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette if (!mInputEnabled) { 939eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio return true; 940eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 941eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 942002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette final int action = event.getActionMasked(); 943002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette if (action == MotionEvent.ACTION_MOVE 944002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette || action == MotionEvent.ACTION_UP 945002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette || action == MotionEvent.ACTION_DOWN) { 946002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette boolean forceSelection = false; 947002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette boolean autoAdvance = false; 948002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 949002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette if (action == MotionEvent.ACTION_DOWN) { 950002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette // This is a new event stream, reset whether the value changed. 951002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette mChangedDuringTouch = false; 952002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } else if (action == MotionEvent.ACTION_UP) { 953002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette autoAdvance = true; 954002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 955002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette // If we saw a down/up pair without the value changing, assume 956002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette // this is a single-tap selection and force a change. 957002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette if (!mChangedDuringTouch) { 958002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette forceSelection = true; 959eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 960002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } 961eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 962002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette mChangedDuringTouch |= handleTouchInput( 963002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette event.getX(), event.getY(), forceSelection, autoAdvance); 964eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 965002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 966002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette return true; 967002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } 968002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 969002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette private boolean handleTouchInput( 970002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette float x, float y, boolean forceSelection, boolean autoAdvance) { 97188e51032320ab92057666e1230cc6548ca163c51Alan Viverette final boolean isOnInnerCircle = getInnerCircleFromXY(x, y); 972ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette final int degrees = getDegreesFromXY(x, y, false); 973002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette if (degrees == -1) { 974002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette return false; 975002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } 976002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 9772b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // Ensure we're showing the correct picker. 9782b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette animatePicker(mShowHours, ANIM_DURATION_TOUCH); 9792b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 98011a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette final int type; 98111a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette final int newValue; 98211a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette final boolean valueChanged; 983002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 984002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette if (mShowHours) { 985002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette final int snapDegrees = snapOnly30s(degrees, 0) % 360; 98688e51032320ab92057666e1230cc6548ca163c51Alan Viverette valueChanged = mIsOnInnerCircle != isOnInnerCircle 98788e51032320ab92057666e1230cc6548ca163c51Alan Viverette || mSelectionDegrees[HOURS] != snapDegrees; 98888e51032320ab92057666e1230cc6548ca163c51Alan Viverette mIsOnInnerCircle = isOnInnerCircle; 98988e51032320ab92057666e1230cc6548ca163c51Alan Viverette mSelectionDegrees[HOURS] = snapDegrees; 99011a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette type = HOURS; 99111a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette newValue = getCurrentHour(); 992002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } else { 993002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette final int snapDegrees = snapPrefer30s(degrees) % 360; 99488e51032320ab92057666e1230cc6548ca163c51Alan Viverette valueChanged = mSelectionDegrees[MINUTES] != snapDegrees; 99588e51032320ab92057666e1230cc6548ca163c51Alan Viverette mSelectionDegrees[MINUTES] = snapDegrees; 99611a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette type = MINUTES; 99711a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette newValue = getCurrentMinute(); 998002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } 999002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 100011a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette if (valueChanged || forceSelection || autoAdvance) { 100111a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette // Fire the listener even if we just need to auto-advance. 1002002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette if (mListener != null) { 1003002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette mListener.onValueSelected(type, newValue, autoAdvance); 1004002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } 100511a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette 100611a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette // Only provide feedback if the value actually changed. 100711a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette if (valueChanged || forceSelection) { 100811a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK); 100911a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette invalidate(); 101011a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette } 1011002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette return true; 1012002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } 1013002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 1014002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette return false; 1015eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1016eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 1017eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio @Override 1018ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public boolean dispatchHoverEvent(MotionEvent event) { 1019ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // First right-of-refusal goes the touch exploration helper. 1020ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (mTouchHelper.dispatchHoverEvent(event)) { 1021eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio return true; 1022eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1023ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return super.dispatchHoverEvent(event); 1024eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1025eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 1026ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public void setInputEnabled(boolean inputEnabled) { 1027ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mInputEnabled = inputEnabled; 1028ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette invalidate(); 1029ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1030ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1031ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private class RadialPickerTouchHelper extends ExploreByTouchHelper { 1032ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final Rect mTempRect = new Rect(); 1033ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1034ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final int TYPE_HOUR = 1; 1035ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final int TYPE_MINUTE = 2; 1036ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1037ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final int SHIFT_TYPE = 0; 1038ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final int MASK_TYPE = 0xF; 1039ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1040ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final int SHIFT_VALUE = 8; 1041ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final int MASK_VALUE = 0xFF; 1042ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1043ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette /** Increment in which virtual views are exposed for minutes. */ 1044ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final int MINUTE_INCREMENT = 5; 1045ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1046ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public RadialPickerTouchHelper() { 1047ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette super(RadialTimePickerView.this); 1048eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1049eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 1050ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @Override 1051ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 1052ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette super.onInitializeAccessibilityNodeInfo(host, info); 1053ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1054ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD); 1055ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD); 1056eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1057ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1058ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @Override 1059ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public boolean performAccessibilityAction(View host, int action, Bundle arguments) { 1060ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (super.performAccessibilityAction(host, action, arguments)) { 1061ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return true; 1062eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1063eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 1064ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette switch (action) { 1065ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: 1066ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette adjustPicker(1); 1067ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return true; 1068ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: 1069ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette adjustPicker(-1); 1070ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return true; 1071ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1072ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1073ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return false; 1074ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1075ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1076ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private void adjustPicker(int step) { 1077ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int stepSize; 10784a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette final int initialStep; 107967945c11a5e9547f71be91ceb99e7b9ff15a6292Alan Viverette final int maxValue; 1080ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int minValue; 1081eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (mShowHours) { 10824a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette stepSize = 1; 1083ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 10844a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette final int currentHour24 = getCurrentHour(); 1085eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (mIs24HourMode) { 10864a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette initialStep = currentHour24; 1087ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette minValue = 0; 10884a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette maxValue = 23; 1089eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 10904a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette initialStep = hour24To12(currentHour24); 1091eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio minValue = 1; 10924a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette maxValue = 12; 1093eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1094eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 10954a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette stepSize = 5; 10964a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette initialStep = getCurrentMinute() / stepSize; 1097ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette minValue = 0; 10984a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette maxValue = 55; 1099eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1100ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 11014a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette final int nextValue = (initialStep + step) * stepSize; 11024a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette final int clampedValue = MathUtils.constrain(nextValue, minValue, maxValue); 1103ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (mShowHours) { 1104ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentHour(clampedValue); 1105ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1106ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentMinute(clampedValue); 1107ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1108ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1109ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1110ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @Override 1111ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette protected int getVirtualViewAt(float x, float y) { 1112ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int id; 1113d8c2af55420d869c140278e6643d16cd90cfeb51Alan Viverette final int degrees = getDegreesFromXY(x, y, true); 1114ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (degrees != -1) { 1115ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int snapDegrees = snapOnly30s(degrees, 0) % 360; 1116ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (mShowHours) { 111788e51032320ab92057666e1230cc6548ca163c51Alan Viverette final boolean isOnInnerCircle = getInnerCircleFromXY(x, y); 11185efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette final int hour24 = getHourForDegrees(snapDegrees, isOnInnerCircle); 11195efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette final int hour = mIs24HourMode ? hour24 : hour24To12(hour24); 1120ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette id = makeId(TYPE_HOUR, hour); 1121ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1122ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int current = getCurrentMinute(); 1123ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int touched = getMinuteForDegrees(degrees); 1124ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int snapped = getMinuteForDegrees(snapDegrees); 1125ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1126ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // If the touched minute is closer to the current minute 1127ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // than it is to the snapped minute, return current. 1128fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette final int currentOffset = getCircularDiff(current, touched, MINUTES_IN_CIRCLE); 1129fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette final int snappedOffset = getCircularDiff(snapped, touched, MINUTES_IN_CIRCLE); 1130ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int minute; 113188e51032320ab92057666e1230cc6548ca163c51Alan Viverette if (currentOffset < snappedOffset) { 1132ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette minute = current; 1133ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1134ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette minute = snapped; 1135ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1136ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette id = makeId(TYPE_MINUTE, minute); 1137ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1138ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1139ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette id = INVALID_ID; 1140eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1141ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1142ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return id; 1143ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1144ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 114588e51032320ab92057666e1230cc6548ca163c51Alan Viverette /** 114688e51032320ab92057666e1230cc6548ca163c51Alan Viverette * Returns the difference in degrees between two values along a circle. 114788e51032320ab92057666e1230cc6548ca163c51Alan Viverette * 114888e51032320ab92057666e1230cc6548ca163c51Alan Viverette * @param first value in the range [0,max] 114988e51032320ab92057666e1230cc6548ca163c51Alan Viverette * @param second value in the range [0,max] 115088e51032320ab92057666e1230cc6548ca163c51Alan Viverette * @param max the maximum value along the circle 115188e51032320ab92057666e1230cc6548ca163c51Alan Viverette * @return the difference in between the two values 115288e51032320ab92057666e1230cc6548ca163c51Alan Viverette */ 115388e51032320ab92057666e1230cc6548ca163c51Alan Viverette private int getCircularDiff(int first, int second, int max) { 115488e51032320ab92057666e1230cc6548ca163c51Alan Viverette final int diff = Math.abs(first - second); 115588e51032320ab92057666e1230cc6548ca163c51Alan Viverette final int midpoint = max / 2; 115688e51032320ab92057666e1230cc6548ca163c51Alan Viverette return (diff > midpoint) ? (max - diff) : diff; 115788e51032320ab92057666e1230cc6548ca163c51Alan Viverette } 115888e51032320ab92057666e1230cc6548ca163c51Alan Viverette 1159ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @Override 1160ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette protected void getVisibleVirtualViews(IntArray virtualViewIds) { 1161eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (mShowHours) { 1162ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int min = mIs24HourMode ? 0 : 1; 1163ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int max = mIs24HourMode ? 23 : 12; 1164ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette for (int i = min; i <= max ; i++) { 1165ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette virtualViewIds.add(makeId(TYPE_HOUR, i)); 1166eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1167eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 1168ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int current = getCurrentMinute(); 1169fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette for (int i = 0; i < MINUTES_IN_CIRCLE; i += MINUTE_INCREMENT) { 1170ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette virtualViewIds.add(makeId(TYPE_MINUTE, i)); 1171ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1172ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // If the current minute falls between two increments, 1173ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // insert an extra node for it. 1174ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (current > i && current < i + MINUTE_INCREMENT) { 1175ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette virtualViewIds.add(makeId(TYPE_MINUTE, current)); 1176ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1177eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1178eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1179eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1180eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 1181ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @Override 1182ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) { 1183ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette event.setClassName(getClass().getName()); 1184eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 1185ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int type = getTypeFromId(virtualViewId); 1186ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int value = getValueFromId(virtualViewId); 1187ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final CharSequence description = getVirtualViewDescription(type, value); 1188ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette event.setContentDescription(description); 1189ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1190ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1191ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @Override 1192ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) { 1193ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette node.setClassName(getClass().getName()); 1194ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette node.addAction(AccessibilityAction.ACTION_CLICK); 1195ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1196ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int type = getTypeFromId(virtualViewId); 1197ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int value = getValueFromId(virtualViewId); 1198ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final CharSequence description = getVirtualViewDescription(type, value); 1199ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette node.setContentDescription(description); 1200ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1201ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette getBoundsForVirtualView(virtualViewId, mTempRect); 1202ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette node.setBoundsInParent(mTempRect); 1203ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1204ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final boolean selected = isVirtualViewSelected(type, value); 1205ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette node.setSelected(selected); 12063fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette 12073fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette final int nextId = getVirtualViewIdAfter(type, value); 12083fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette if (nextId != INVALID_ID) { 12093fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette node.setTraversalBefore(RadialTimePickerView.this, nextId); 12103fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette } 12113fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette } 12123fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette 12133fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette private int getVirtualViewIdAfter(int type, int value) { 12143fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette if (type == TYPE_HOUR) { 12153fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette final int nextValue = value + 1; 12163fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette final int max = mIs24HourMode ? 23 : 12; 12173fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette if (nextValue <= max) { 12183fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette return makeId(type, nextValue); 12193fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette } 12203fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette } else if (type == TYPE_MINUTE) { 12213fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette final int current = getCurrentMinute(); 12223fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette final int snapValue = value - (value % MINUTE_INCREMENT); 12233fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette final int nextValue = snapValue + MINUTE_INCREMENT; 12243fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette if (value < current && nextValue > current) { 12253fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette // The current value is between two snap values. 12263fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette return makeId(type, current); 1227fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette } else if (nextValue < MINUTES_IN_CIRCLE) { 12283fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette return makeId(type, nextValue); 12293fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette } 12303fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette } 12313fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette return INVALID_ID; 1232ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1233ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1234ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @Override 1235ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette protected boolean onPerformActionForVirtualView(int virtualViewId, int action, 1236ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette Bundle arguments) { 1237ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (action == AccessibilityNodeInfo.ACTION_CLICK) { 1238ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int type = getTypeFromId(virtualViewId); 1239ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int value = getValueFromId(virtualViewId); 1240ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (type == TYPE_HOUR) { 1241ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int hour = mIs24HourMode ? value : hour12To24(value, mAmOrPm); 1242ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentHour(hour); 1243ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return true; 1244ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else if (type == TYPE_MINUTE) { 1245ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentMinute(value); 1246ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return true; 1247ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1248ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1249ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return false; 1250ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1251ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1252ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int hour12To24(int hour12, int amOrPm) { 1253ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette int hour24 = hour12; 1254ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (hour12 == 12) { 1255ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (amOrPm == AM) { 1256ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette hour24 = 0; 1257ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1258ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else if (amOrPm == PM) { 1259ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette hour24 += 12; 1260ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1261ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return hour24; 1262ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1263ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 12645efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette private int hour24To12(int hour24) { 12655efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette if (hour24 == 0) { 12665efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette return 12; 12675efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette } else if (hour24 > 12) { 12685efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette return hour24 - 12; 12695efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette } else { 12705efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette return hour24; 12715efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette } 12725efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette } 12735efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette 1274ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private void getBoundsForVirtualView(int virtualViewId, Rect bounds) { 1275ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final float radius; 1276ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int type = getTypeFromId(virtualViewId); 1277ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int value = getValueFromId(virtualViewId); 1278ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final float centerRadius; 1279ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final float degrees; 1280ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (type == TYPE_HOUR) { 128188e51032320ab92057666e1230cc6548ca163c51Alan Viverette final boolean innerCircle = getInnerCircleForHour(value); 1282ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (innerCircle) { 1283adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette centerRadius = mCircleRadius - mTextInset[HOURS_INNER]; 1284adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette radius = mSelectorRadius; 1285ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1286adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette centerRadius = mCircleRadius - mTextInset[HOURS]; 1287adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette radius = mSelectorRadius; 1288ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1289ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1290ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette degrees = getDegreesForHour(value); 1291ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else if (type == TYPE_MINUTE) { 1292adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette centerRadius = mCircleRadius - mTextInset[MINUTES]; 1293ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette degrees = getDegreesForMinute(value); 1294adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette radius = mSelectorRadius; 1295ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1296ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // This should never happen. 1297ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette centerRadius = 0; 1298ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette degrees = 0; 1299ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette radius = 0; 1300ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1301ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1302ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final double radians = Math.toRadians(degrees); 1303ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final float xCenter = mXCenter + centerRadius * (float) Math.sin(radians); 1304ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final float yCenter = mYCenter - centerRadius * (float) Math.cos(radians); 1305ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1306ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette bounds.set((int) (xCenter - radius), (int) (yCenter - radius), 1307ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette (int) (xCenter + radius), (int) (yCenter + radius)); 1308ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1309ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1310ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private CharSequence getVirtualViewDescription(int type, int value) { 1311ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final CharSequence description; 1312ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (type == TYPE_HOUR || type == TYPE_MINUTE) { 1313ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette description = Integer.toString(value); 1314ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1315ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette description = null; 1316ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1317ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return description; 1318ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1319ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1320ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private boolean isVirtualViewSelected(int type, int value) { 1321ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final boolean selected; 1322ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (type == TYPE_HOUR) { 1323ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette selected = getCurrentHour() == value; 1324ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else if (type == TYPE_MINUTE) { 1325ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette selected = getCurrentMinute() == value; 1326ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1327ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette selected = false; 1328ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1329ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return selected; 1330ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1331ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1332ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int makeId(int type, int value) { 1333ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return type << SHIFT_TYPE | value << SHIFT_VALUE; 1334ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1335ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1336ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int getTypeFromId(int id) { 1337ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return id >>> SHIFT_TYPE & MASK_TYPE; 1338ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1339ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1340ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int getValueFromId(int id) { 1341ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return id >>> SHIFT_VALUE & MASK_VALUE; 1342ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1343eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1344eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio} 1345