RadialTimePickerView.java revision 66a8562c58507c52d0b9924aa0c7c246b1c723e8
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 1966a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viveretteimport com.android.internal.R; 2066a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viveretteimport com.android.internal.widget.ExploreByTouchHelper; 2166a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette 22eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.animation.ObjectAnimator; 2366a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viveretteimport android.annotation.IntDef; 24eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.Context; 25ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.content.res.ColorStateList; 26eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.res.Resources; 27eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.res.TypedArray; 28eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Canvas; 29eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Color; 30eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Paint; 31ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.graphics.Path; 32ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.graphics.Rect; 33ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.graphics.Region; 34eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Typeface; 35eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.os.Bundle; 36eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.util.AttributeSet; 372b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viveretteimport android.util.FloatProperty; 38ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.util.IntArray; 39eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.util.Log; 40ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.util.MathUtils; 41ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.util.StateSet; 425134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viveretteimport android.util.TypedValue; 43eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.HapticFeedbackConstants; 44eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.MotionEvent; 45eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.View; 46eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.accessibility.AccessibilityEvent; 47eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.accessibility.AccessibilityNodeInfo; 48ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 49eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette 5066a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viveretteimport java.lang.annotation.Retention; 5166a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viveretteimport java.lang.annotation.RetentionPolicy; 52eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport java.util.Calendar; 53eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport java.util.Locale; 54eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 55eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio/** 56eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * View to show a clock circle picker (with one or two picking circles) 57eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * 58eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * @hide 59eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */ 60ec9fe1ad663d59829075029508673b2e913538d3Alan Viverettepublic class RadialTimePickerView extends View { 61ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private static final String TAG = "RadialTimePickerView"; 62eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 63b0f5461eeca28d6c4be6ba1f826a43a8961f587eAlan Viverette public static final int HOURS = 0; 64b0f5461eeca28d6c4be6ba1f826a43a8961f587eAlan Viverette public static final int MINUTES = 1; 6566a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette 6666a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette /** @hide */ 6766a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette @IntDef({HOURS, MINUTES}) 6866a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette @Retention(RetentionPolicy.SOURCE) 6966a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette @interface PickerType {} 7066a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette 71eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int HOURS_INNER = 2; 72eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 73eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int SELECTOR_CIRCLE = 0; 74eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int SELECTOR_DOT = 1; 75eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int SELECTOR_LINE = 2; 76eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 77eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int AM = 0; 78eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int PM = 1; 79eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 80fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette private static final int HOURS_IN_CIRCLE = 12; 81fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette private static final int MINUTES_IN_CIRCLE = 60; 82fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette private static final int DEGREES_FOR_ONE_HOUR = 360 / HOURS_IN_CIRCLE; 83fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette private static final int DEGREES_FOR_ONE_MINUTE = 360 / MINUTES_IN_CIRCLE; 84eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 85eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int[] HOURS_NUMBERS = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; 86eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int[] HOURS_NUMBERS_24 = {0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; 87eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int[] MINUTES_NUMBERS = {0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55}; 88eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 892b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private static final int ANIM_DURATION_NORMAL = 500; 902b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private static final int ANIM_DURATION_TOUCH = 60; 91ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 92ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private static final int[] SNAP_PREFER_30S_MAP = new int[361]; 93ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 94ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private static final int NUM_POSITIONS = 12; 95ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private static final float[] COS_30 = new float[NUM_POSITIONS]; 96ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private static final float[] SIN_30 = new float[NUM_POSITIONS]; 97ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 982b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette /** "Something is wrong" color used when a color attribute is missing. */ 992b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private static final int MISSING_COLOR = Color.MAGENTA; 1002b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 101ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette static { 102ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // Prepare mapping to snap touchable degrees to selectable degrees. 103ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette preparePrefer30sMap(); 104ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 105ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette final double increment = 2.0 * Math.PI / NUM_POSITIONS; 106ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette double angle = Math.PI / 2.0; 107ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette for (int i = 0; i < NUM_POSITIONS; i++) { 108ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette COS_30[i] = (float) Math.cos(angle); 109ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette SIN_30[i] = (float) Math.sin(angle); 110ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette angle += increment; 111ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 112ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 113eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 1142b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private final FloatProperty<RadialTimePickerView> HOURS_TO_MINUTES = 1152b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette new FloatProperty<RadialTimePickerView>("hoursToMinutes") { 1162b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette @Override 1172b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette public Float get(RadialTimePickerView radialTimePickerView) { 1182b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette return radialTimePickerView.mHoursToMinutes; 1192b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 1202b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 1212b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette @Override 1222b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette public void setValue(RadialTimePickerView object, float value) { 1232b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette object.mHoursToMinutes = value; 1242b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette object.invalidate(); 1252b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 1262b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette }; 127ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 128eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private final String[] mHours12Texts = new String[12]; 129eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private final String[] mOuterHours24Texts = new String[12]; 130eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private final String[] mInnerHours24Texts = new String[12]; 131eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private final String[] mMinutesTexts = new String[12]; 132eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 133eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private final Paint[] mPaint = new Paint[2]; 134eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private final Paint mPaintCenter = new Paint(); 1352b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private final Paint[] mPaintSelector = new Paint[3]; 136eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private final Paint mPaintBackground = new Paint(); 137eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 138ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final Typeface mTypeface; 139ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 140adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private final ColorStateList[] mTextColor = new ColorStateList[3]; 141adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private final int[] mTextSize = new int[3]; 142adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private final int[] mTextInset = new int[3]; 143ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 144ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private final float[][] mOuterTextX = new float[2][12]; 145ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private final float[][] mOuterTextY = new float[2][12]; 146ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 147ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private final float[] mInnerTextX = new float[12]; 148ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private final float[] mInnerTextY = new float[12]; 149ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 15088e51032320ab92057666e1230cc6548ca163c51Alan Viverette private final int[] mSelectionDegrees = new int[2]; 151ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 152ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final RadialPickerTouchHelper mTouchHelper; 153ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 154f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette private final Path mSelectorPath = new Path(); 155f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette 156eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private boolean mIs24HourMode; 157eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private boolean mShowHours; 158d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette 1592b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private ObjectAnimator mHoursToMinutesAnimator; 1602b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private float mHoursToMinutes; 1612b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 162d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette /** 163d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * When in 24-hour mode, indicates that the current hour is between 164d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * 1 and 12 (inclusive). 165d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette */ 166eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private boolean mIsOnInnerCircle; 167eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 168adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private int mSelectorRadius; 16962c79e9a64c3b2cafd5500ed3064977dff7b7da3Alan Viverette private int mSelectorStroke; 170adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private int mSelectorDotRadius; 171adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private int mCenterDotRadius; 172adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette 1732b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private int mSelectorColor; 1742b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private int mSelectorDotColor; 1752b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 176adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private int mXCenter; 177adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private int mYCenter; 178adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private int mCircleRadius; 179eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 18088e51032320ab92057666e1230cc6548ca163c51Alan Viverette private int mMinDistForInnerNumber; 18188e51032320ab92057666e1230cc6548ca163c51Alan Viverette private int mMaxDistForOuterNumber; 18288e51032320ab92057666e1230cc6548ca163c51Alan Viverette private int mHalfwayDist; 183eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 184eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private String[] mOuterTextHours; 185eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private String[] mInnerTextHours; 18688e51032320ab92057666e1230cc6548ca163c51Alan Viverette private String[] mMinutesText; 187eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 188eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private int mAmOrPm; 189adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette 190adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private float mDisabledAlpha; 1915134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette 192eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private OnValueSelectedListener mListener; 193eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 194ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private boolean mInputEnabled = true; 195eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 19666a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette interface OnValueSelectedListener { 19766a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette /** 19866a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * Called when the selected value at a given picker index has changed. 19966a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * 20066a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * @param pickerType the type of value that has changed, one of: 20166a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * <ul> 20266a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * <li>{@link #MINUTES} 20366a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * <li>{@link #HOURS} 20466a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * </ul> 20566a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * @param newValue the new value as minute in hour (0-59) or hour in 20666a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * day (0-23) 20766a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * @param autoAdvance when the picker type is {@link #HOURS}, 20866a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * {@code true} to switch to the {@link #MINUTES} 20966a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * picker or {@code false} to stay on the current 21066a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * picker. No effect when picker type is 21166a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * {@link #MINUTES}. 21266a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette */ 21366a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette void onValueSelected(@PickerType int pickerType, int newValue, boolean autoAdvance); 214eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 215eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 216eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio /** 217eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Split up the 360 degrees of the circle among the 60 selectable values. Assigns a larger 218eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * selectable area to each of the 12 visible values, such that the ratio of space apportioned 219eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * to a visible value : space apportioned to a non-visible value will be 14 : 4. 220eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * E.g. the output of 30 degrees should have a higher range of input associated with it than 221eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * the output of 24 degrees, because 30 degrees corresponds to a visible number on the clock 222eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * circle (5 on the minutes, 1 or 13 on the hours). 223eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */ 224eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static void preparePrefer30sMap() { 225eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // We'll split up the visible output and the non-visible output such that each visible 226eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // output will correspond to a range of 14 associated input degrees, and each non-visible 227eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // output will correspond to a range of 4 associate input degrees, so visible numbers 228eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // are more than 3 times easier to get than non-visible numbers: 229eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // {354-359,0-7}:0, {8-11}:6, {12-15}:12, {16-19}:18, {20-23}:24, {24-37}:30, etc. 230eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // 231eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // If an output of 30 degrees should correspond to a range of 14 associated degrees, then 232eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // we'll need any input between 24 - 37 to snap to 30. Working out from there, 20-23 should 233eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // snap to 24, while 38-41 should snap to 36. This is somewhat counter-intuitive, that you 234eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // can be touching 36 degrees but have the selection snapped to 30 degrees; however, this 235eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // inconsistency isn't noticeable at such fine-grained degrees, and it affords us the 236eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // ability to aggressively prefer the visible values by a factor of more than 3:1, which 237eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // greatly contributes to the selectability of these values. 238eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 239eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // The first output is 0, and each following output will increment by 6 {0, 6, 12, ...}. 240eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio int snappedOutputDegrees = 0; 241eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Count of how many inputs we've designated to the specified output. 242eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio int count = 1; 243eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // How many input we expect for a specified output. This will be 14 for output divisible 244eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // by 30, and 4 for the remaining output. We'll special case the outputs of 0 and 360, so 245eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // the caller can decide which they need. 246eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio int expectedCount = 8; 247eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Iterate through the input. 248eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio for (int degrees = 0; degrees < 361; degrees++) { 249eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Save the input-output mapping. 250ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette SNAP_PREFER_30S_MAP[degrees] = snappedOutputDegrees; 251eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // If this is the last input for the specified output, calculate the next output and 252eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // the next expected count. 253eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (count == expectedCount) { 254eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio snappedOutputDegrees += 6; 255eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (snappedOutputDegrees == 360) { 256eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio expectedCount = 7; 257eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else if (snappedOutputDegrees % 30 == 0) { 258eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio expectedCount = 14; 259eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 260eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio expectedCount = 4; 261eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 262eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio count = 1; 263eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 264eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio count++; 265eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 266eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 267eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 268eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 269eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio /** 270eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Returns mapping of any input degrees (0 to 360) to one of 60 selectable output degrees, 271eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * where the degrees corresponding to visible numbers (i.e. those divisible by 30) will be 272eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * weighted heavier than the degrees corresponding to non-visible numbers. 273eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * See {@link #preparePrefer30sMap()} documentation for the rationale and generation of the 274eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * mapping. 275eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */ 276eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static int snapPrefer30s(int degrees) { 277ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette if (SNAP_PREFER_30S_MAP == null) { 278eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio return -1; 279eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 280ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette return SNAP_PREFER_30S_MAP[degrees]; 281eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 282eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 283eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio /** 284eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Returns mapping of any input degrees (0 to 360) to one of 12 visible output degrees (all 285eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * multiples of 30), where the input will be "snapped" to the closest visible degrees. 286eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * @param degrees The input degrees 287eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * @param forceHigherOrLower The output may be forced to either the higher or lower step, or may 288eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * be allowed to snap to whichever is closer. Use 1 to force strictly higher, -1 to force 289eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * strictly lower, and 0 to snap to the closer one. 290eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * @return output degrees, will be a multiple of 30 291eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */ 292eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static int snapOnly30s(int degrees, int forceHigherOrLower) { 293eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio final int stepSize = DEGREES_FOR_ONE_HOUR; 294eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio int floor = (degrees / stepSize) * stepSize; 295eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio final int ceiling = floor + stepSize; 296eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (forceHigherOrLower == 1) { 297eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio degrees = ceiling; 298eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else if (forceHigherOrLower == -1) { 299eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (degrees == floor) { 300eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio floor -= stepSize; 301eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 302eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio degrees = floor; 303eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 304eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if ((degrees - floor) < (ceiling - degrees)) { 305eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio degrees = floor; 306eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 307eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio degrees = ceiling; 308eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 309eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 310eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio return degrees; 311eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 312eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 313ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @SuppressWarnings("unused") 314ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public RadialTimePickerView(Context context) { 315ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette this(context, null); 316ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 317ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 318eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public RadialTimePickerView(Context context, AttributeSet attrs) { 319eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio this(context, attrs, R.attr.timePickerStyle); 320eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 321eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 322ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public RadialTimePickerView(Context context, AttributeSet attrs, int defStyleAttr) { 323ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette this(context, attrs, defStyleAttr, 0); 324ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 325ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 326ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public RadialTimePickerView( 327ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 328eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio super(context, attrs); 329eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 3302b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette applyAttributes(attrs, defStyleAttr, defStyleRes); 3312b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 3325134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette // Pull disabled alpha from theme. 3335134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette final TypedValue outValue = new TypedValue(); 3345134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true); 335adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mDisabledAlpha = outValue.getFloat(); 3365134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette 337eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mTypeface = Typeface.create("sans-serif", Typeface.NORMAL); 338eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 339eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaint[HOURS] = new Paint(); 340eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaint[HOURS].setAntiAlias(true); 341eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaint[HOURS].setTextAlign(Paint.Align.CENTER); 342eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 343eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaint[MINUTES] = new Paint(); 344eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaint[MINUTES].setAntiAlias(true); 345eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaint[MINUTES].setTextAlign(Paint.Align.CENTER); 346eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 347eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaintCenter.setAntiAlias(true); 348ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 3492b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintSelector[SELECTOR_CIRCLE] = new Paint(); 3502b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintSelector[SELECTOR_CIRCLE].setAntiAlias(true); 351eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 3522b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintSelector[SELECTOR_DOT] = new Paint(); 3532b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintSelector[SELECTOR_DOT].setAntiAlias(true); 354eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 3552b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintSelector[SELECTOR_LINE] = new Paint(); 3562b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintSelector[SELECTOR_LINE].setAntiAlias(true); 3572b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintSelector[SELECTOR_LINE].setStrokeWidth(2); 358eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 359eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaintBackground.setAntiAlias(true); 360eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 3612b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final Resources res = getResources(); 362adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mSelectorRadius = res.getDimensionPixelSize(R.dimen.timepicker_selector_radius); 36362c79e9a64c3b2cafd5500ed3064977dff7b7da3Alan Viverette mSelectorStroke = res.getDimensionPixelSize(R.dimen.timepicker_selector_stroke); 364adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mSelectorDotRadius = res.getDimensionPixelSize(R.dimen.timepicker_selector_dot_radius); 365adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mCenterDotRadius = res.getDimensionPixelSize(R.dimen.timepicker_center_dot_radius); 366adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette 367adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mTextSize[HOURS] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_normal); 368adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mTextSize[MINUTES] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_normal); 369adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mTextSize[HOURS_INNER] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_inner); 370adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette 371adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mTextInset[HOURS] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_normal); 372adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mTextInset[MINUTES] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_normal); 373adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mTextInset[HOURS_INNER] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_inner); 374eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 375eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mShowHours = true; 3762b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mHoursToMinutes = HOURS; 377eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mIs24HourMode = false; 378eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mAmOrPm = AM; 379eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 380ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // Set up accessibility components. 381ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mTouchHelper = new RadialPickerTouchHelper(); 382ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setAccessibilityDelegate(mTouchHelper); 383ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 384ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 385ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 386ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 387ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 388eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio initHoursAndMinutesText(); 389eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio initData(); 390eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 391eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Initial values 392eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio final Calendar calendar = Calendar.getInstance(Locale.getDefault()); 393eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio final int currentHour = calendar.get(Calendar.HOUR_OF_DAY); 394eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio final int currentMinute = calendar.get(Calendar.MINUTE); 395eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 396ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentHourInternal(currentHour, false, false); 397ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentMinuteInternal(currentMinute, false); 398eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 399eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio setHapticFeedbackEnabled(true); 400eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 401eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 4022b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette void applyAttributes(AttributeSet attrs, int defStyleAttr, int defStyleRes) { 4032b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final Context context = getContext(); 4042b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final TypedArray a = getContext().obtainStyledAttributes(attrs, 4052b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette R.styleable.TimePicker, defStyleAttr, defStyleRes); 4062b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 4072b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final ColorStateList numbersTextColor = a.getColorStateList( 4082b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette R.styleable.TimePicker_numbersTextColor); 4092b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final ColorStateList numbersInnerTextColor = a.getColorStateList( 4102b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette R.styleable.TimePicker_numbersInnerTextColor); 4112b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mTextColor[HOURS] = numbersTextColor == null ? 4122b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette ColorStateList.valueOf(MISSING_COLOR) : numbersTextColor; 4132b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mTextColor[HOURS_INNER] = numbersInnerTextColor == null ? 4142b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette ColorStateList.valueOf(MISSING_COLOR) : numbersInnerTextColor; 4152b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mTextColor[MINUTES] = mTextColor[HOURS]; 4162b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 4172b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // Set up various colors derived from the selector "activated" state. 4182b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final ColorStateList selectorColors = a.getColorStateList( 4192b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette R.styleable.TimePicker_numbersSelectorColor); 4202b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int selectorActivatedColor; 4212b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (selectorColors != null) { 4222b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int[] stateSetEnabledActivated = StateSet.get( 4232b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED); 4242b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette selectorActivatedColor = selectorColors.getColorForState( 4252b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette stateSetEnabledActivated, 0); 4262b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } else { 4272b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette selectorActivatedColor = MISSING_COLOR; 4282b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 4292b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 4302b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintCenter.setColor(selectorActivatedColor); 4312b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 4322b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int[] stateSetActivated = StateSet.get( 4332b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED); 4342b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 4352b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mSelectorColor = selectorActivatedColor; 4362b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mSelectorDotColor = mTextColor[HOURS].getColorForState(stateSetActivated, 0); 4372b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 4382b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintBackground.setColor(a.getColor(R.styleable.TimePicker_numbersBackgroundColor, 4392b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette context.getColor(R.color.timepicker_default_numbers_background_color_material))); 4402b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 4412b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette a.recycle(); 4422b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 4432b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 444eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void initialize(int hour, int minute, boolean is24HourMode) { 445448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette if (mIs24HourMode != is24HourMode) { 446448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette mIs24HourMode = is24HourMode; 447448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette initData(); 448448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette } 449ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 450ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentHourInternal(hour, false, false); 451ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentMinuteInternal(minute, false); 452eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 453eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 454eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void setCurrentItemShowing(int item, boolean animate) { 455eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio switch (item){ 456eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio case HOURS: 457eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio showHours(animate); 458eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio break; 459eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio case MINUTES: 460eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio showMinutes(animate); 461eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio break; 462eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio default: 463eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio Log.e(TAG, "ClockView does not support showing item " + item); 464eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 465eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 466eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 467eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public int getCurrentItemShowing() { 468eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio return mShowHours ? HOURS : MINUTES; 469eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 470eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 471eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void setOnValueSelectedListener(OnValueSelectedListener listener) { 472eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mListener = listener; 473eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 474eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 475d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette /** 476d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * Sets the current hour in 24-hour time. 477d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * 478d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * @param hour the current hour between 0 and 23 (inclusive) 479d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette */ 480eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void setCurrentHour(int hour) { 481ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentHourInternal(hour, true, false); 482ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 483ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 484ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette /** 485ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette * Sets the current hour. 486ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette * 487ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette * @param hour The current hour 488ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette * @param callback Whether the value listener should be invoked 489ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette * @param autoAdvance Whether the listener should auto-advance to the next 490ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette * selection mode, e.g. hour to minutes 491ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette */ 492ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private void setCurrentHourInternal(int hour, boolean callback, boolean autoAdvance) { 493eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio final int degrees = (hour % 12) * DEGREES_FOR_ONE_HOUR; 494eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mSelectionDegrees[HOURS] = degrees; 495d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette 496d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette // 0 is 12 AM (midnight) and 12 is 12 PM (noon). 497ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int amOrPm = (hour == 0 || (hour % 24) < 12) ? AM : PM; 49888e51032320ab92057666e1230cc6548ca163c51Alan Viverette final boolean isOnInnerCircle = getInnerCircleForHour(hour); 499ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (mAmOrPm != amOrPm || mIsOnInnerCircle != isOnInnerCircle) { 500ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mAmOrPm = amOrPm; 501ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mIsOnInnerCircle = isOnInnerCircle; 502ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 503ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette initData(); 504ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mTouchHelper.invalidateRoot(); 505ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 506d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette 507eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio invalidate(); 508ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 509ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (callback && mListener != null) { 510ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mListener.onValueSelected(HOURS, hour, autoAdvance); 511ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 512eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 513eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 514d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette /** 515d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * Returns the current hour in 24-hour time. 516d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * 517d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * @return the current hour between 0 and 23 (inclusive) 518d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette */ 519eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public int getCurrentHour() { 52088e51032320ab92057666e1230cc6548ca163c51Alan Viverette return getHourForDegrees(mSelectionDegrees[HOURS], mIsOnInnerCircle); 521ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 522ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 523ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int getHourForDegrees(int degrees, boolean innerCircle) { 524ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette int hour = (degrees / DEGREES_FOR_ONE_HOUR) % 12; 525eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (mIs24HourMode) { 526d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette // Convert the 12-hour value into 24-hour time based on where the 527d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette // selector is positioned. 52888e51032320ab92057666e1230cc6548ca163c51Alan Viverette if (!innerCircle && hour == 0) { 52988e51032320ab92057666e1230cc6548ca163c51Alan Viverette // Outer circle is 1 through 12. 530d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette hour = 12; 53188e51032320ab92057666e1230cc6548ca163c51Alan Viverette } else if (innerCircle && hour != 0) { 53288e51032320ab92057666e1230cc6548ca163c51Alan Viverette // Inner circle is 13 through 23 and 0. 533d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette hour += 12; 534eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 535d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette } else if (mAmOrPm == PM) { 536d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette hour += 12; 537eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 538d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette return hour; 539eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 540eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 54188e51032320ab92057666e1230cc6548ca163c51Alan Viverette /** 54288e51032320ab92057666e1230cc6548ca163c51Alan Viverette * @param hour the hour in 24-hour time or 12-hour time 54388e51032320ab92057666e1230cc6548ca163c51Alan Viverette */ 544ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int getDegreesForHour(int hour) { 545ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // Convert to be 0-11. 546ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (mIs24HourMode) { 547ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (hour >= 12) { 548ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette hour -= 12; 549ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 550ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else if (hour == 12) { 551ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette hour = 0; 552ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 553ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return hour * DEGREES_FOR_ONE_HOUR; 554ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 555ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 55688e51032320ab92057666e1230cc6548ca163c51Alan Viverette /** 55788e51032320ab92057666e1230cc6548ca163c51Alan Viverette * @param hour the hour in 24-hour time or 12-hour time 55888e51032320ab92057666e1230cc6548ca163c51Alan Viverette */ 55988e51032320ab92057666e1230cc6548ca163c51Alan Viverette private boolean getInnerCircleForHour(int hour) { 56088e51032320ab92057666e1230cc6548ca163c51Alan Viverette return mIs24HourMode && (hour == 0 || hour > 12); 56188e51032320ab92057666e1230cc6548ca163c51Alan Viverette } 56288e51032320ab92057666e1230cc6548ca163c51Alan Viverette 563eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void setCurrentMinute(int minute) { 564ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentMinuteInternal(minute, true); 565ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 566ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 567ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private void setCurrentMinuteInternal(int minute, boolean callback) { 568fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette mSelectionDegrees[MINUTES] = (minute % MINUTES_IN_CIRCLE) * DEGREES_FOR_ONE_MINUTE; 569ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 570eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio invalidate(); 571ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 572ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (callback && mListener != null) { 573ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mListener.onValueSelected(MINUTES, minute, false); 574ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 575eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 576eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 577eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Returns minutes in 0-59 range 578eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public int getCurrentMinute() { 579ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return getMinuteForDegrees(mSelectionDegrees[MINUTES]); 580ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 581ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 582ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int getMinuteForDegrees(int degrees) { 583ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return degrees / DEGREES_FOR_ONE_MINUTE; 584ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 585ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 586ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int getDegreesForMinute(int minute) { 587ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return minute * DEGREES_FOR_ONE_MINUTE; 588eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 589eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 59030b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette /** 59130b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette * Sets whether the picker is showing AM or PM hours. Has no effect when 59230b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette * in 24-hour mode. 59330b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette * 59430b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette * @param amOrPm {@link #AM} or {@link #PM} 59530b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette * @return {@code true} if the value changed from what was previously set, 59630b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette * or {@code false} otherwise 59730b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette */ 59830b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette public boolean setAmOrPm(int amOrPm) { 59930b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette if (mAmOrPm == amOrPm || mIs24HourMode) { 60030b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette return false; 60130b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette } 60230b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette 60330b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette mAmOrPm = amOrPm; 604eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio invalidate(); 605ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mTouchHelper.invalidateRoot(); 60630b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette return true; 607eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 608eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 609eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public int getAmOrPm() { 610eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio return mAmOrPm; 611eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 612eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 613eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void showHours(boolean animate) { 6142b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette showPicker(true, animate); 615eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 616eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 617eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void showMinutes(boolean animate) { 6182b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette showPicker(false, animate); 619eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 620eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 621eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private void initHoursAndMinutesText() { 622eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Initialize the hours and minutes numbers. 623eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio for (int i = 0; i < 12; i++) { 624eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mHours12Texts[i] = String.format("%d", HOURS_NUMBERS[i]); 625f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette mInnerHours24Texts[i] = String.format("%02d", HOURS_NUMBERS_24[i]); 626f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette mOuterHours24Texts[i] = String.format("%d", HOURS_NUMBERS[i]); 627eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mMinutesTexts[i] = String.format("%02d", MINUTES_NUMBERS[i]); 628eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 629eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 630eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 631eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private void initData() { 632eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (mIs24HourMode) { 633eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mOuterTextHours = mOuterHours24Texts; 634eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mInnerTextHours = mInnerHours24Texts; 635eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 636eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mOuterTextHours = mHours12Texts; 637f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette mInnerTextHours = mHours12Texts; 638eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 639eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 64088e51032320ab92057666e1230cc6548ca163c51Alan Viverette mMinutesText = mMinutesTexts; 641eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 642eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 643eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio @Override 644eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 645ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette if (!changed) { 646ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette return; 647ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 648eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 649eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mXCenter = getWidth() / 2; 650eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mYCenter = getHeight() / 2; 651ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette mCircleRadius = Math.min(mXCenter, mYCenter); 652eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 65388e51032320ab92057666e1230cc6548ca163c51Alan Viverette mMinDistForInnerNumber = mCircleRadius - mTextInset[HOURS_INNER] - mSelectorRadius; 65488e51032320ab92057666e1230cc6548ca163c51Alan Viverette mMaxDistForOuterNumber = mCircleRadius - mTextInset[HOURS] + mSelectorRadius; 65588e51032320ab92057666e1230cc6548ca163c51Alan Viverette mHalfwayDist = mCircleRadius - (mTextInset[HOURS] + mTextInset[HOURS_INNER]) / 2; 656eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 657ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette calculatePositionsHours(); 658ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette calculatePositionsMinutes(); 659eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 660ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mTouchHelper.invalidateRoot(); 661eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 662eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 663eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio @Override 664eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void onDraw(Canvas canvas) { 665adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette final float alphaMod = mInputEnabled ? 1 : mDisabledAlpha; 666eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 667eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio drawCircleBackground(canvas); 6682b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 6692b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final Path selectorPath = mSelectorPath; 6702b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawSelector(canvas, selectorPath); 6712b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawHours(canvas, selectorPath, alphaMod); 6722b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawMinutes(canvas, selectorPath, alphaMod); 673adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette drawCenter(canvas, alphaMod); 674adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette } 675eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 6762b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private void showPicker(boolean hours, boolean animate) { 6772b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (mShowHours == hours) { 6782b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette return; 6792b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 6802b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 6812b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mShowHours = hours; 6822b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 6832b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (animate) { 6842b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette animatePicker(hours, ANIM_DURATION_NORMAL); 68532f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette } else { 68632f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette // If we have a pending or running animator, cancel it. 68732f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette if (mHoursToMinutesAnimator != null && mHoursToMinutesAnimator.isStarted()) { 68832f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette mHoursToMinutesAnimator.cancel(); 68932f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette mHoursToMinutesAnimator = null; 69032f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette } 69132f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette mHoursToMinutes = hours ? 0.0f : 1.0f; 6922b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 6932b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 6942b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette initData(); 6952b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette invalidate(); 6962b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mTouchHelper.invalidateRoot(); 6972b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 6982b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 6992b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private void animatePicker(boolean hoursToMinutes, long duration) { 7002b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final float target = hoursToMinutes ? HOURS : MINUTES; 7012b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (mHoursToMinutes == target) { 7022b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // If we have a pending or running animator, cancel it. 7032b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (mHoursToMinutesAnimator != null && mHoursToMinutesAnimator.isStarted()) { 7042b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mHoursToMinutesAnimator.cancel(); 7052b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mHoursToMinutesAnimator = null; 7062b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 7072b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 7082b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // We're already showing the correct picker. 7092b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette return; 7102b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 7112b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 7122b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mHoursToMinutesAnimator = ObjectAnimator.ofFloat(this, HOURS_TO_MINUTES, target); 7132b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mHoursToMinutesAnimator.setAutoCancel(true); 7142b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mHoursToMinutesAnimator.setDuration(duration); 7152b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mHoursToMinutesAnimator.start(); 7162b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 7172b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 718adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private void drawCircleBackground(Canvas canvas) { 719adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette canvas.drawCircle(mXCenter, mYCenter, mCircleRadius, mPaintBackground); 720adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette } 721adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette 7222b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private void drawHours(Canvas canvas, Path selectorPath, float alphaMod) { 7232b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int hoursAlpha = (int) (255f * (1f - mHoursToMinutes) * alphaMod + 0.5f); 724ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette if (hoursAlpha > 0) { 7252b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // Exclude the selector region, then draw inner/outer hours with no 7262b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // activated states. 7272b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.save(Canvas.CLIP_SAVE_FLAG); 7282b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.clipPath(selectorPath, Region.Op.DIFFERENCE); 7292b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawHoursClipped(canvas, hoursAlpha, false); 7302b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.restore(); 7312b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 7322b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // Intersect the selector region, then draw minutes with only 7332b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // activated states. 7342b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.save(Canvas.CLIP_SAVE_FLAG); 7352b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.clipPath(selectorPath, Region.Op.INTERSECT); 7362b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawHoursClipped(canvas, hoursAlpha, true); 7372b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.restore(); 738eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 739adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette } 740eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 7412b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private void drawHoursClipped(Canvas canvas, int hoursAlpha, boolean showActivated) { 7422b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // Draw outer hours. 7432b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawTextElements(canvas, mTextSize[HOURS], mTypeface, mTextColor[HOURS], mOuterTextHours, 7442b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mOuterTextX[HOURS], mOuterTextY[HOURS], mPaint[HOURS], hoursAlpha, 7452b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette showActivated && !mIsOnInnerCircle, mSelectionDegrees[HOURS], showActivated); 746ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 7472b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // Draw inner hours (13-00) for 24-hour time. 7482b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (mIs24HourMode && mInnerTextHours != null) { 7492b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawTextElements(canvas, mTextSize[HOURS_INNER], mTypeface, mTextColor[HOURS_INNER], 7502b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mInnerTextHours, mInnerTextX, mInnerTextY, mPaint[HOURS], hoursAlpha, 7512b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette showActivated && mIsOnInnerCircle, mSelectionDegrees[HOURS], showActivated); 7522b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 7532b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 7542b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 7552b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private void drawMinutes(Canvas canvas, Path selectorPath, float alphaMod) { 7562b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int minutesAlpha = (int) (255f * mHoursToMinutes * alphaMod + 0.5f); 7572b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (minutesAlpha > 0) { 758ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // Exclude the selector region, then draw minutes with no 759ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // activated states. 760ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette canvas.save(Canvas.CLIP_SAVE_FLAG); 7612b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.clipPath(selectorPath, Region.Op.DIFFERENCE); 7622b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawMinutesClipped(canvas, minutesAlpha, false); 763ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette canvas.restore(); 764ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 765ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // Intersect the selector region, then draw minutes with only 766ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // activated states. 767ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette canvas.save(Canvas.CLIP_SAVE_FLAG); 7682b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.clipPath(selectorPath, Region.Op.INTERSECT); 7692b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawMinutesClipped(canvas, minutesAlpha, true); 770ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette canvas.restore(); 771ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 772eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 773eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 7742b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private void drawMinutesClipped(Canvas canvas, int minutesAlpha, boolean showActivated) { 7752b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES], mMinutesText, 7762b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], minutesAlpha, 7772b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette showActivated, mSelectionDegrees[MINUTES], showActivated); 7782b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 7792b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 780adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private void drawCenter(Canvas canvas, float alphaMod) { 781adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mPaintCenter.setAlpha((int) (255 * alphaMod + 0.5f)); 782adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette canvas.drawCircle(mXCenter, mYCenter, mCenterDotRadius, mPaintCenter); 783eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 784eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 785eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette private int getMultipliedAlpha(int argb, int alpha) { 786eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette return (int) (Color.alpha(argb) * (alpha / 255.0) + 0.5); 787eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette } 788eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette 7892b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private void drawSelector(Canvas canvas, Path selectorPath) { 7902b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // Determine the current length, angle, and dot scaling factor. 7912b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int hoursIndex = mIsOnInnerCircle ? HOURS_INNER : HOURS; 7922b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int hoursInset = mTextInset[hoursIndex]; 7932b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int hoursAngleDeg = mSelectionDegrees[hoursIndex % 2]; 7942b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final float hoursDotScale = mSelectionDegrees[hoursIndex % 2] % 30 != 0 ? 1 : 0; 7952b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 7962b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int minutesIndex = MINUTES; 7972b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int minutesInset = mTextInset[minutesIndex]; 7982b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int minutesAngleDeg = mSelectionDegrees[minutesIndex]; 7992b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final float minutesDotScale = mSelectionDegrees[minutesIndex] % 30 != 0 ? 1 : 0; 800eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 801f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette // Calculate the current radius at which to place the selection circle. 802f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette final int selRadius = mSelectorRadius; 8032b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final float selLength = 8042b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mCircleRadius - MathUtils.lerp(hoursInset, minutesInset, mHoursToMinutes); 8052b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final double selAngleRad = 8062b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette Math.toRadians(MathUtils.lerpDeg(hoursAngleDeg, minutesAngleDeg, mHoursToMinutes)); 807f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette final float selCenterX = mXCenter + selLength * (float) Math.sin(selAngleRad); 808f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette final float selCenterY = mYCenter - selLength * (float) Math.cos(selAngleRad); 809f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette 810f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette // Draw the selection circle. 8112b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final Paint paint = mPaintSelector[SELECTOR_CIRCLE]; 8122b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette paint.setColor(mSelectorColor); 813f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette canvas.drawCircle(selCenterX, selCenterY, selRadius, paint); 814eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 815ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // If needed, set up the clip path for later. 816ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette if (selectorPath != null) { 817f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette selectorPath.reset(); 818f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette selectorPath.addCircle(selCenterX, selCenterY, selRadius, Path.Direction.CCW); 819ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 820ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 821f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette // Draw the dot if we're between two items. 8222b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final float dotScale = MathUtils.lerp(hoursDotScale, minutesDotScale, mHoursToMinutes); 8232b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (dotScale > 0) { 8242b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final Paint dotPaint = mPaintSelector[SELECTOR_DOT]; 82588e51032320ab92057666e1230cc6548ca163c51Alan Viverette dotPaint.setColor(mSelectorDotColor); 8262b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.drawCircle(selCenterX, selCenterY, mSelectorDotRadius * dotScale, dotPaint); 827eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 828eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 829adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette // Shorten the line to only go from the edge of the center dot to the 830adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette // edge of the selection circle. 831f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette final double sin = Math.sin(selAngleRad); 832f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette final double cos = Math.cos(selAngleRad); 8332b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final float lineLength = selLength - selRadius; 834adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette final int centerX = mXCenter + (int) (mCenterDotRadius * sin); 835adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette final int centerY = mYCenter - (int) (mCenterDotRadius * cos); 836f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette final float linePointX = centerX + (int) (lineLength * sin); 837f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette final float linePointY = centerY - (int) (lineLength * cos); 838ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 839f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette // Draw the line. 8402b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final Paint linePaint = mPaintSelector[SELECTOR_LINE]; 8412b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette linePaint.setColor(mSelectorColor); 842f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette linePaint.setStrokeWidth(mSelectorStroke); 843f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette canvas.drawLine(mXCenter, mYCenter, linePointX, linePointY, linePaint); 844eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 845eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 846ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private void calculatePositionsHours() { 847eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Calculate the text positions 848adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette final float numbersRadius = mCircleRadius - mTextInset[HOURS]; 849eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 850eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Calculate the positions for the 12 numbers in the main circle. 851ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette calculatePositions(mPaint[HOURS], numbersRadius, mXCenter, mYCenter, 852ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette mTextSize[HOURS], mOuterTextX[HOURS], mOuterTextY[HOURS]); 853eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 854eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // If we have an inner circle, calculate those positions too. 855eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (mIs24HourMode) { 856adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette final int innerNumbersRadius = mCircleRadius - mTextInset[HOURS_INNER]; 857ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette calculatePositions(mPaint[HOURS], innerNumbersRadius, mXCenter, mYCenter, 858ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette mTextSize[HOURS_INNER], mInnerTextX, mInnerTextY); 859eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 860eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 861eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 862ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private void calculatePositionsMinutes() { 863eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Calculate the text positions 864adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette final float numbersRadius = mCircleRadius - mTextInset[MINUTES]; 865eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 866eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Calculate the positions for the 12 numbers in the main circle. 867ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette calculatePositions(mPaint[MINUTES], numbersRadius, mXCenter, mYCenter, 868ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette mTextSize[MINUTES], mOuterTextX[MINUTES], mOuterTextY[MINUTES]); 869eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 870eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 871eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio /** 872eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Using the trigonometric Unit Circle, calculate the positions that the text will need to be 873eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * drawn at based on the specified circle radius. Place the values in the textGridHeights and 874eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * textGridWidths parameters. 875eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */ 876ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private static void calculatePositions(Paint paint, float radius, float xCenter, float yCenter, 877ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette float textSize, float[] x, float[] y) { 878ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // Adjust yCenter to account for the text's baseline. 879eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio paint.setTextSize(textSize); 880eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio yCenter -= (paint.descent() + paint.ascent()) / 2; 881eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 882ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette for (int i = 0; i < NUM_POSITIONS; i++) { 883ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette x[i] = xCenter - radius * COS_30[i]; 884ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette y[i] = yCenter - radius * SIN_30[i]; 885ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 886eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 887eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 888eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio /** 889eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Draw the 12 text values at the positions specified by the textGrid parameters. 890eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */ 891adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private void drawTextElements(Canvas canvas, float textSize, Typeface typeface, 892adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette ColorStateList textColor, String[] texts, float[] textX, float[] textY, Paint paint, 893adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette int alpha, boolean showActivated, int activatedDegrees, boolean activatedOnly) { 894eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio paint.setTextSize(textSize); 895eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio paint.setTypeface(typeface); 896eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 897ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // The activated index can touch a range of elements. 898ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette final float activatedIndex = activatedDegrees / (360.0f / NUM_POSITIONS); 899ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette final int activatedFloor = (int) activatedIndex; 900ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette final int activatedCeil = ((int) Math.ceil(activatedIndex)) % NUM_POSITIONS; 901eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 902ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette for (int i = 0; i < 12; i++) { 903ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette final boolean activated = (activatedFloor == i || activatedCeil == i); 904ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette if (activatedOnly && !activated) { 905ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette continue; 906ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 907eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 908ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette final int stateMask = StateSet.VIEW_STATE_ENABLED 909ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette | (showActivated && activated ? StateSet.VIEW_STATE_ACTIVATED : 0); 910adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette final int color = textColor.getColorForState(StateSet.get(stateMask), 0); 911ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette paint.setColor(color); 912ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette paint.setAlpha(getMultipliedAlpha(color, alpha)); 913eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 914ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette canvas.drawText(texts[i], textX[i], textY[i], paint); 915ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 916eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 917eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 918ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private int getDegreesFromXY(float x, float y, boolean constrainOutside) { 91988e51032320ab92057666e1230cc6548ca163c51Alan Viverette // Ensure the point is inside the touchable area. 92088e51032320ab92057666e1230cc6548ca163c51Alan Viverette final int innerBound; 92188e51032320ab92057666e1230cc6548ca163c51Alan Viverette final int outerBound; 92288e51032320ab92057666e1230cc6548ca163c51Alan Viverette if (mIs24HourMode && mShowHours) { 92388e51032320ab92057666e1230cc6548ca163c51Alan Viverette innerBound = mMinDistForInnerNumber; 92488e51032320ab92057666e1230cc6548ca163c51Alan Viverette outerBound = mMaxDistForOuterNumber; 92588e51032320ab92057666e1230cc6548ca163c51Alan Viverette } else { 92688e51032320ab92057666e1230cc6548ca163c51Alan Viverette final int index = mShowHours ? HOURS : MINUTES; 92788e51032320ab92057666e1230cc6548ca163c51Alan Viverette final int center = mCircleRadius - mTextInset[index]; 92888e51032320ab92057666e1230cc6548ca163c51Alan Viverette innerBound = center - mSelectorRadius; 92988e51032320ab92057666e1230cc6548ca163c51Alan Viverette outerBound = center + mSelectorRadius; 93088e51032320ab92057666e1230cc6548ca163c51Alan Viverette } 931eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 93288e51032320ab92057666e1230cc6548ca163c51Alan Viverette final double dX = x - mXCenter; 93388e51032320ab92057666e1230cc6548ca163c51Alan Viverette final double dY = y - mYCenter; 93488e51032320ab92057666e1230cc6548ca163c51Alan Viverette final double distFromCenter = Math.sqrt(dX * dX + dY * dY); 93588e51032320ab92057666e1230cc6548ca163c51Alan Viverette if (distFromCenter < innerBound || constrainOutside && distFromCenter > outerBound) { 936eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio return -1; 937eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 938ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 93988e51032320ab92057666e1230cc6548ca163c51Alan Viverette // Convert to degrees. 94088e51032320ab92057666e1230cc6548ca163c51Alan Viverette final int degrees = (int) (Math.toDegrees(Math.atan2(dY, dX) + Math.PI / 2) + 0.5); 94188e51032320ab92057666e1230cc6548ca163c51Alan Viverette if (degrees < 0) { 94288e51032320ab92057666e1230cc6548ca163c51Alan Viverette return degrees + 360; 943eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 94488e51032320ab92057666e1230cc6548ca163c51Alan Viverette return degrees; 945eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 94688e51032320ab92057666e1230cc6548ca163c51Alan Viverette } 947eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 94888e51032320ab92057666e1230cc6548ca163c51Alan Viverette private boolean getInnerCircleFromXY(float x, float y) { 94988e51032320ab92057666e1230cc6548ca163c51Alan Viverette if (mIs24HourMode && mShowHours) { 95088e51032320ab92057666e1230cc6548ca163c51Alan Viverette final double dX = x - mXCenter; 95188e51032320ab92057666e1230cc6548ca163c51Alan Viverette final double dY = y - mYCenter; 95288e51032320ab92057666e1230cc6548ca163c51Alan Viverette final double distFromCenter = Math.sqrt(dX * dX + dY * dY); 95388e51032320ab92057666e1230cc6548ca163c51Alan Viverette return distFromCenter <= mHalfwayDist; 954eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 95588e51032320ab92057666e1230cc6548ca163c51Alan Viverette return false; 956eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 957eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 958002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette boolean mChangedDuringTouch = false; 959002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 960eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio @Override 961ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette public boolean onTouchEvent(MotionEvent event) { 962002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette if (!mInputEnabled) { 963eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio return true; 964eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 965eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 966002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette final int action = event.getActionMasked(); 967002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette if (action == MotionEvent.ACTION_MOVE 968002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette || action == MotionEvent.ACTION_UP 969002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette || action == MotionEvent.ACTION_DOWN) { 970002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette boolean forceSelection = false; 971002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette boolean autoAdvance = false; 972002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 973002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette if (action == MotionEvent.ACTION_DOWN) { 974002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette // This is a new event stream, reset whether the value changed. 975002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette mChangedDuringTouch = false; 976002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } else if (action == MotionEvent.ACTION_UP) { 977002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette autoAdvance = true; 978002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 979002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette // If we saw a down/up pair without the value changing, assume 980002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette // this is a single-tap selection and force a change. 981002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette if (!mChangedDuringTouch) { 982002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette forceSelection = true; 983eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 984002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } 985eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 986002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette mChangedDuringTouch |= handleTouchInput( 987002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette event.getX(), event.getY(), forceSelection, autoAdvance); 988eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 989002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 990002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette return true; 991002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } 992002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 993002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette private boolean handleTouchInput( 994002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette float x, float y, boolean forceSelection, boolean autoAdvance) { 99588e51032320ab92057666e1230cc6548ca163c51Alan Viverette final boolean isOnInnerCircle = getInnerCircleFromXY(x, y); 996ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette final int degrees = getDegreesFromXY(x, y, false); 997002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette if (degrees == -1) { 998002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette return false; 999002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } 1000002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 10012b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // Ensure we're showing the correct picker. 10022b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette animatePicker(mShowHours, ANIM_DURATION_TOUCH); 10032b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 100466a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette final @PickerType int type; 100511a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette final int newValue; 100611a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette final boolean valueChanged; 1007002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 1008002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette if (mShowHours) { 1009002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette final int snapDegrees = snapOnly30s(degrees, 0) % 360; 101088e51032320ab92057666e1230cc6548ca163c51Alan Viverette valueChanged = mIsOnInnerCircle != isOnInnerCircle 101188e51032320ab92057666e1230cc6548ca163c51Alan Viverette || mSelectionDegrees[HOURS] != snapDegrees; 101288e51032320ab92057666e1230cc6548ca163c51Alan Viverette mIsOnInnerCircle = isOnInnerCircle; 101388e51032320ab92057666e1230cc6548ca163c51Alan Viverette mSelectionDegrees[HOURS] = snapDegrees; 101411a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette type = HOURS; 101511a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette newValue = getCurrentHour(); 1016002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } else { 1017002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette final int snapDegrees = snapPrefer30s(degrees) % 360; 101888e51032320ab92057666e1230cc6548ca163c51Alan Viverette valueChanged = mSelectionDegrees[MINUTES] != snapDegrees; 101988e51032320ab92057666e1230cc6548ca163c51Alan Viverette mSelectionDegrees[MINUTES] = snapDegrees; 102011a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette type = MINUTES; 102111a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette newValue = getCurrentMinute(); 1022002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } 1023002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 102411a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette if (valueChanged || forceSelection || autoAdvance) { 102511a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette // Fire the listener even if we just need to auto-advance. 1026002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette if (mListener != null) { 1027002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette mListener.onValueSelected(type, newValue, autoAdvance); 1028002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } 102911a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette 103011a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette // Only provide feedback if the value actually changed. 103111a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette if (valueChanged || forceSelection) { 103211a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK); 103311a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette invalidate(); 103411a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette } 1035002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette return true; 1036002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } 1037002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 1038002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette return false; 1039eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1040eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 1041eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio @Override 1042ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public boolean dispatchHoverEvent(MotionEvent event) { 1043ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // First right-of-refusal goes the touch exploration helper. 1044ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (mTouchHelper.dispatchHoverEvent(event)) { 1045eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio return true; 1046eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1047ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return super.dispatchHoverEvent(event); 1048eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1049eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 1050ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public void setInputEnabled(boolean inputEnabled) { 1051ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mInputEnabled = inputEnabled; 1052ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette invalidate(); 1053ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1054ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1055ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private class RadialPickerTouchHelper extends ExploreByTouchHelper { 1056ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final Rect mTempRect = new Rect(); 1057ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1058ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final int TYPE_HOUR = 1; 1059ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final int TYPE_MINUTE = 2; 1060ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1061ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final int SHIFT_TYPE = 0; 1062ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final int MASK_TYPE = 0xF; 1063ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1064ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final int SHIFT_VALUE = 8; 1065ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final int MASK_VALUE = 0xFF; 1066ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1067ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette /** Increment in which virtual views are exposed for minutes. */ 1068ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final int MINUTE_INCREMENT = 5; 1069ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1070ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public RadialPickerTouchHelper() { 1071ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette super(RadialTimePickerView.this); 1072eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1073eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 1074ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @Override 1075ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 1076ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette super.onInitializeAccessibilityNodeInfo(host, info); 1077ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1078ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD); 1079ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD); 1080eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1081ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1082ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @Override 1083ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public boolean performAccessibilityAction(View host, int action, Bundle arguments) { 1084ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (super.performAccessibilityAction(host, action, arguments)) { 1085ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return true; 1086eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1087eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 1088ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette switch (action) { 1089ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: 1090ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette adjustPicker(1); 1091ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return true; 1092ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: 1093ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette adjustPicker(-1); 1094ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return true; 1095ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1096ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1097ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return false; 1098ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1099ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1100ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private void adjustPicker(int step) { 1101ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int stepSize; 11024a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette final int initialStep; 110367945c11a5e9547f71be91ceb99e7b9ff15a6292Alan Viverette final int maxValue; 1104ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int minValue; 1105eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (mShowHours) { 11064a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette stepSize = 1; 1107ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 11084a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette final int currentHour24 = getCurrentHour(); 1109eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (mIs24HourMode) { 11104a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette initialStep = currentHour24; 1111ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette minValue = 0; 11124a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette maxValue = 23; 1113eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 11144a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette initialStep = hour24To12(currentHour24); 1115eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio minValue = 1; 11164a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette maxValue = 12; 1117eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1118eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 11194a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette stepSize = 5; 11204a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette initialStep = getCurrentMinute() / stepSize; 1121ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette minValue = 0; 11224a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette maxValue = 55; 1123eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1124ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 11254a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette final int nextValue = (initialStep + step) * stepSize; 11264a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette final int clampedValue = MathUtils.constrain(nextValue, minValue, maxValue); 1127ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (mShowHours) { 1128ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentHour(clampedValue); 1129ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1130ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentMinute(clampedValue); 1131ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1132ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1133ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1134ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @Override 1135ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette protected int getVirtualViewAt(float x, float y) { 1136ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int id; 1137d8c2af55420d869c140278e6643d16cd90cfeb51Alan Viverette final int degrees = getDegreesFromXY(x, y, true); 1138ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (degrees != -1) { 1139ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int snapDegrees = snapOnly30s(degrees, 0) % 360; 1140ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (mShowHours) { 114188e51032320ab92057666e1230cc6548ca163c51Alan Viverette final boolean isOnInnerCircle = getInnerCircleFromXY(x, y); 11425efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette final int hour24 = getHourForDegrees(snapDegrees, isOnInnerCircle); 11435efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette final int hour = mIs24HourMode ? hour24 : hour24To12(hour24); 1144ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette id = makeId(TYPE_HOUR, hour); 1145ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1146ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int current = getCurrentMinute(); 1147ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int touched = getMinuteForDegrees(degrees); 1148ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int snapped = getMinuteForDegrees(snapDegrees); 1149ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1150ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // If the touched minute is closer to the current minute 1151ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // than it is to the snapped minute, return current. 1152fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette final int currentOffset = getCircularDiff(current, touched, MINUTES_IN_CIRCLE); 1153fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette final int snappedOffset = getCircularDiff(snapped, touched, MINUTES_IN_CIRCLE); 1154ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int minute; 115588e51032320ab92057666e1230cc6548ca163c51Alan Viverette if (currentOffset < snappedOffset) { 1156ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette minute = current; 1157ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1158ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette minute = snapped; 1159ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1160ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette id = makeId(TYPE_MINUTE, minute); 1161ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1162ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1163ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette id = INVALID_ID; 1164eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1165ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1166ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return id; 1167ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1168ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 116988e51032320ab92057666e1230cc6548ca163c51Alan Viverette /** 117088e51032320ab92057666e1230cc6548ca163c51Alan Viverette * Returns the difference in degrees between two values along a circle. 117188e51032320ab92057666e1230cc6548ca163c51Alan Viverette * 117288e51032320ab92057666e1230cc6548ca163c51Alan Viverette * @param first value in the range [0,max] 117388e51032320ab92057666e1230cc6548ca163c51Alan Viverette * @param second value in the range [0,max] 117488e51032320ab92057666e1230cc6548ca163c51Alan Viverette * @param max the maximum value along the circle 117588e51032320ab92057666e1230cc6548ca163c51Alan Viverette * @return the difference in between the two values 117688e51032320ab92057666e1230cc6548ca163c51Alan Viverette */ 117788e51032320ab92057666e1230cc6548ca163c51Alan Viverette private int getCircularDiff(int first, int second, int max) { 117888e51032320ab92057666e1230cc6548ca163c51Alan Viverette final int diff = Math.abs(first - second); 117988e51032320ab92057666e1230cc6548ca163c51Alan Viverette final int midpoint = max / 2; 118088e51032320ab92057666e1230cc6548ca163c51Alan Viverette return (diff > midpoint) ? (max - diff) : diff; 118188e51032320ab92057666e1230cc6548ca163c51Alan Viverette } 118288e51032320ab92057666e1230cc6548ca163c51Alan Viverette 1183ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @Override 1184ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette protected void getVisibleVirtualViews(IntArray virtualViewIds) { 1185eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (mShowHours) { 1186ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int min = mIs24HourMode ? 0 : 1; 1187ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int max = mIs24HourMode ? 23 : 12; 1188ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette for (int i = min; i <= max ; i++) { 1189ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette virtualViewIds.add(makeId(TYPE_HOUR, i)); 1190eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1191eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 1192ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int current = getCurrentMinute(); 1193fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette for (int i = 0; i < MINUTES_IN_CIRCLE; i += MINUTE_INCREMENT) { 1194ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette virtualViewIds.add(makeId(TYPE_MINUTE, i)); 1195ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1196ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // If the current minute falls between two increments, 1197ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // insert an extra node for it. 1198ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (current > i && current < i + MINUTE_INCREMENT) { 1199ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette virtualViewIds.add(makeId(TYPE_MINUTE, current)); 1200ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1201eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1202eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1203eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1204eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 1205ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @Override 1206ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) { 1207ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette event.setClassName(getClass().getName()); 1208eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 1209ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int type = getTypeFromId(virtualViewId); 1210ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int value = getValueFromId(virtualViewId); 1211ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final CharSequence description = getVirtualViewDescription(type, value); 1212ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette event.setContentDescription(description); 1213ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1214ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1215ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @Override 1216ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) { 1217ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette node.setClassName(getClass().getName()); 1218ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette node.addAction(AccessibilityAction.ACTION_CLICK); 1219ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1220ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int type = getTypeFromId(virtualViewId); 1221ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int value = getValueFromId(virtualViewId); 1222ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final CharSequence description = getVirtualViewDescription(type, value); 1223ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette node.setContentDescription(description); 1224ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1225ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette getBoundsForVirtualView(virtualViewId, mTempRect); 1226ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette node.setBoundsInParent(mTempRect); 1227ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1228ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final boolean selected = isVirtualViewSelected(type, value); 1229ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette node.setSelected(selected); 12303fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette 12313fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette final int nextId = getVirtualViewIdAfter(type, value); 12323fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette if (nextId != INVALID_ID) { 12333fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette node.setTraversalBefore(RadialTimePickerView.this, nextId); 12343fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette } 12353fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette } 12363fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette 12373fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette private int getVirtualViewIdAfter(int type, int value) { 12383fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette if (type == TYPE_HOUR) { 12393fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette final int nextValue = value + 1; 12403fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette final int max = mIs24HourMode ? 23 : 12; 12413fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette if (nextValue <= max) { 12423fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette return makeId(type, nextValue); 12433fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette } 12443fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette } else if (type == TYPE_MINUTE) { 12453fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette final int current = getCurrentMinute(); 12463fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette final int snapValue = value - (value % MINUTE_INCREMENT); 12473fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette final int nextValue = snapValue + MINUTE_INCREMENT; 12483fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette if (value < current && nextValue > current) { 12493fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette // The current value is between two snap values. 12503fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette return makeId(type, current); 1251fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette } else if (nextValue < MINUTES_IN_CIRCLE) { 12523fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette return makeId(type, nextValue); 12533fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette } 12543fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette } 12553fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette return INVALID_ID; 1256ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1257ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1258ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @Override 1259ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette protected boolean onPerformActionForVirtualView(int virtualViewId, int action, 1260ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette Bundle arguments) { 1261ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (action == AccessibilityNodeInfo.ACTION_CLICK) { 1262ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int type = getTypeFromId(virtualViewId); 1263ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int value = getValueFromId(virtualViewId); 1264ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (type == TYPE_HOUR) { 1265ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int hour = mIs24HourMode ? value : hour12To24(value, mAmOrPm); 1266ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentHour(hour); 1267ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return true; 1268ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else if (type == TYPE_MINUTE) { 1269ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentMinute(value); 1270ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return true; 1271ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1272ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1273ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return false; 1274ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1275ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1276ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int hour12To24(int hour12, int amOrPm) { 1277ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette int hour24 = hour12; 1278ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (hour12 == 12) { 1279ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (amOrPm == AM) { 1280ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette hour24 = 0; 1281ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1282ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else if (amOrPm == PM) { 1283ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette hour24 += 12; 1284ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1285ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return hour24; 1286ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1287ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 12885efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette private int hour24To12(int hour24) { 12895efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette if (hour24 == 0) { 12905efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette return 12; 12915efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette } else if (hour24 > 12) { 12925efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette return hour24 - 12; 12935efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette } else { 12945efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette return hour24; 12955efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette } 12965efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette } 12975efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette 1298ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private void getBoundsForVirtualView(int virtualViewId, Rect bounds) { 1299ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final float radius; 1300ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int type = getTypeFromId(virtualViewId); 1301ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int value = getValueFromId(virtualViewId); 1302ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final float centerRadius; 1303ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final float degrees; 1304ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (type == TYPE_HOUR) { 130588e51032320ab92057666e1230cc6548ca163c51Alan Viverette final boolean innerCircle = getInnerCircleForHour(value); 1306ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (innerCircle) { 1307adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette centerRadius = mCircleRadius - mTextInset[HOURS_INNER]; 1308adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette radius = mSelectorRadius; 1309ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1310adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette centerRadius = mCircleRadius - mTextInset[HOURS]; 1311adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette radius = mSelectorRadius; 1312ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1313ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1314ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette degrees = getDegreesForHour(value); 1315ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else if (type == TYPE_MINUTE) { 1316adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette centerRadius = mCircleRadius - mTextInset[MINUTES]; 1317ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette degrees = getDegreesForMinute(value); 1318adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette radius = mSelectorRadius; 1319ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1320ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // This should never happen. 1321ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette centerRadius = 0; 1322ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette degrees = 0; 1323ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette radius = 0; 1324ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1325ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1326ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final double radians = Math.toRadians(degrees); 1327ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final float xCenter = mXCenter + centerRadius * (float) Math.sin(radians); 1328ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final float yCenter = mYCenter - centerRadius * (float) Math.cos(radians); 1329ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1330ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette bounds.set((int) (xCenter - radius), (int) (yCenter - radius), 1331ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette (int) (xCenter + radius), (int) (yCenter + radius)); 1332ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1333ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1334ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private CharSequence getVirtualViewDescription(int type, int value) { 1335ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final CharSequence description; 1336ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (type == TYPE_HOUR || type == TYPE_MINUTE) { 1337ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette description = Integer.toString(value); 1338ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1339ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette description = null; 1340ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1341ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return description; 1342ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1343ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1344ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private boolean isVirtualViewSelected(int type, int value) { 1345ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final boolean selected; 1346ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (type == TYPE_HOUR) { 1347ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette selected = getCurrentHour() == value; 1348ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else if (type == TYPE_MINUTE) { 1349ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette selected = getCurrentMinute() == value; 1350ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1351ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette selected = false; 1352ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1353ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return selected; 1354ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1355ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1356ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int makeId(int type, int value) { 1357ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return type << SHIFT_TYPE | value << SHIFT_VALUE; 1358ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1359ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1360ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int getTypeFromId(int id) { 1361ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return id >>> SHIFT_TYPE & MASK_TYPE; 1362ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1363ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1364ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int getValueFromId(int id) { 1365ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return id >>> SHIFT_VALUE & MASK_VALUE; 1366ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1367eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1368eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio} 1369