1eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio/* 2eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Copyright (C) 2013 The Android Open Source Project 3eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * 4eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Licensed under the Apache License, Version 2.0 (the "License"); 5eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * you may not use this file except in compliance with the License. 6eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * You may obtain a copy of the License at 7eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * 8eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * http://www.apache.org/licenses/LICENSE-2.0 9eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * 10eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Unless required by applicable law or agreed to in writing, software 11eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * distributed under the License is distributed on an "AS IS" BASIS, 12eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * See the License for the specific language governing permissions and 14eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * limitations under the License. 15eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */ 16eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 17eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Megliopackage android.widget; 18eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 19eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.animation.ObjectAnimator; 2066a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viveretteimport android.annotation.IntDef; 21eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.Context; 22ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.content.res.ColorStateList; 23eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.res.Resources; 24eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.content.res.TypedArray; 25eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Canvas; 26eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Color; 27eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Paint; 28ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.graphics.Path; 29ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.graphics.Rect; 30ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.graphics.Region; 31eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.graphics.Typeface; 32eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.os.Bundle; 33eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.util.AttributeSet; 342b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viveretteimport android.util.FloatProperty; 35ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.util.IntArray; 36eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.util.Log; 37ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.util.MathUtils; 38ec9fe1ad663d59829075029508673b2e913538d3Alan Viveretteimport android.util.StateSet; 395134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viveretteimport android.util.TypedValue; 40eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.HapticFeedbackConstants; 41eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.MotionEvent; 4299441c5d7da45c10b729185852be97cbb0bdc8d5Aurimas Liutikasimport android.view.PointerIcon; 43eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.View; 44eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.accessibility.AccessibilityEvent; 45eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport android.view.accessibility.AccessibilityNodeInfo; 46ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 47eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette 4899441c5d7da45c10b729185852be97cbb0bdc8d5Aurimas Liutikasimport com.android.internal.R; 4999441c5d7da45c10b729185852be97cbb0bdc8d5Aurimas Liutikasimport com.android.internal.widget.ExploreByTouchHelper; 5099441c5d7da45c10b729185852be97cbb0bdc8d5Aurimas Liutikas 5166a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viveretteimport java.lang.annotation.Retention; 5266a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viveretteimport java.lang.annotation.RetentionPolicy; 53eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport java.util.Calendar; 54eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglioimport java.util.Locale; 55eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 56eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio/** 57eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * View to show a clock circle picker (with one or two picking circles) 58eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * 59eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * @hide 60eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */ 61ec9fe1ad663d59829075029508673b2e913538d3Alan Viverettepublic class RadialTimePickerView extends View { 62ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private static final String TAG = "RadialTimePickerView"; 63eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 64b0f5461eeca28d6c4be6ba1f826a43a8961f587eAlan Viverette public static final int HOURS = 0; 65b0f5461eeca28d6c4be6ba1f826a43a8961f587eAlan Viverette public static final int MINUTES = 1; 6666a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette 6766a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette /** @hide */ 6866a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette @IntDef({HOURS, MINUTES}) 6966a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette @Retention(RetentionPolicy.SOURCE) 7066a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette @interface PickerType {} 7166a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette 72eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int HOURS_INNER = 2; 73eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 74eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int SELECTOR_CIRCLE = 0; 75eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int SELECTOR_DOT = 1; 76eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int SELECTOR_LINE = 2; 77eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 78eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int AM = 0; 79eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int PM = 1; 80eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 81fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette private static final int HOURS_IN_CIRCLE = 12; 82fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette private static final int MINUTES_IN_CIRCLE = 60; 83fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette private static final int DEGREES_FOR_ONE_HOUR = 360 / HOURS_IN_CIRCLE; 84fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette private static final int DEGREES_FOR_ONE_MINUTE = 360 / MINUTES_IN_CIRCLE; 85eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 86eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int[] HOURS_NUMBERS = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; 87eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int[] HOURS_NUMBERS_24 = {0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; 88eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static final int[] MINUTES_NUMBERS = {0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55}; 89eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 902b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private static final int ANIM_DURATION_NORMAL = 500; 912b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private static final int ANIM_DURATION_TOUCH = 60; 92ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 93ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private static final int[] SNAP_PREFER_30S_MAP = new int[361]; 94ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 95ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private static final int NUM_POSITIONS = 12; 96ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private static final float[] COS_30 = new float[NUM_POSITIONS]; 97ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private static final float[] SIN_30 = new float[NUM_POSITIONS]; 98ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 992b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette /** "Something is wrong" color used when a color attribute is missing. */ 1002b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private static final int MISSING_COLOR = Color.MAGENTA; 1012b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 102ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette static { 103ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // Prepare mapping to snap touchable degrees to selectable degrees. 104ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette preparePrefer30sMap(); 105ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 106ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette final double increment = 2.0 * Math.PI / NUM_POSITIONS; 107ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette double angle = Math.PI / 2.0; 108ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette for (int i = 0; i < NUM_POSITIONS; i++) { 109ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette COS_30[i] = (float) Math.cos(angle); 110ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette SIN_30[i] = (float) Math.sin(angle); 111ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette angle += increment; 112ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 113ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 114eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 1152b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private final FloatProperty<RadialTimePickerView> HOURS_TO_MINUTES = 1162b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette new FloatProperty<RadialTimePickerView>("hoursToMinutes") { 1172b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette @Override 1182b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette public Float get(RadialTimePickerView radialTimePickerView) { 1192b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette return radialTimePickerView.mHoursToMinutes; 1202b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 1212b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 1222b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette @Override 1232b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette public void setValue(RadialTimePickerView object, float value) { 1242b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette object.mHoursToMinutes = value; 1252b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette object.invalidate(); 1262b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 1272b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette }; 128ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 129eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private final String[] mHours12Texts = new String[12]; 130eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private final String[] mOuterHours24Texts = new String[12]; 131eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private final String[] mInnerHours24Texts = new String[12]; 132eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private final String[] mMinutesTexts = new String[12]; 133eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 134eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private final Paint[] mPaint = new Paint[2]; 135eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private final Paint mPaintCenter = new Paint(); 1362b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private final Paint[] mPaintSelector = new Paint[3]; 137eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private final Paint mPaintBackground = new Paint(); 138eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 139ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final Typeface mTypeface; 140ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 141adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private final ColorStateList[] mTextColor = new ColorStateList[3]; 142adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private final int[] mTextSize = new int[3]; 143adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private final int[] mTextInset = new int[3]; 144ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 145ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private final float[][] mOuterTextX = new float[2][12]; 146ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private final float[][] mOuterTextY = new float[2][12]; 147ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 148ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private final float[] mInnerTextX = new float[12]; 149ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private final float[] mInnerTextY = new float[12]; 150ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 15188e51032320ab92057666e1230cc6548ca163c51Alan Viverette private final int[] mSelectionDegrees = new int[2]; 152ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 153ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final RadialPickerTouchHelper mTouchHelper; 154ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 155f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette private final Path mSelectorPath = new Path(); 156f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette 157eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private boolean mIs24HourMode; 158eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private boolean mShowHours; 159d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette 1602b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private ObjectAnimator mHoursToMinutesAnimator; 1612b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private float mHoursToMinutes; 1622b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 163d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette /** 164d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * When in 24-hour mode, indicates that the current hour is between 165d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * 1 and 12 (inclusive). 166d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette */ 167eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private boolean mIsOnInnerCircle; 168eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 169adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private int mSelectorRadius; 17062c79e9a64c3b2cafd5500ed3064977dff7b7da3Alan Viverette private int mSelectorStroke; 171adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private int mSelectorDotRadius; 172adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private int mCenterDotRadius; 173adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette 1742b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private int mSelectorColor; 1752b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private int mSelectorDotColor; 1762b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 177adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private int mXCenter; 178adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private int mYCenter; 179adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private int mCircleRadius; 180eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 18188e51032320ab92057666e1230cc6548ca163c51Alan Viverette private int mMinDistForInnerNumber; 18288e51032320ab92057666e1230cc6548ca163c51Alan Viverette private int mMaxDistForOuterNumber; 18388e51032320ab92057666e1230cc6548ca163c51Alan Viverette private int mHalfwayDist; 184eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 185eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private String[] mOuterTextHours; 186eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private String[] mInnerTextHours; 18788e51032320ab92057666e1230cc6548ca163c51Alan Viverette private String[] mMinutesText; 188eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 189eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private int mAmOrPm; 190adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette 191adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private float mDisabledAlpha; 1925134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette 193eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private OnValueSelectedListener mListener; 194eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 195ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private boolean mInputEnabled = true; 196eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 19766a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette interface OnValueSelectedListener { 19866a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette /** 19966a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * Called when the selected value at a given picker index has changed. 20066a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * 20166a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * @param pickerType the type of value that has changed, one of: 20266a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * <ul> 20366a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * <li>{@link #MINUTES} 20466a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * <li>{@link #HOURS} 20566a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * </ul> 20666a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * @param newValue the new value as minute in hour (0-59) or hour in 20766a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * day (0-23) 20866a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * @param autoAdvance when the picker type is {@link #HOURS}, 20966a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * {@code true} to switch to the {@link #MINUTES} 21066a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * picker or {@code false} to stay on the current 21166a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * picker. No effect when picker type is 21266a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette * {@link #MINUTES}. 21366a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette */ 21466a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette void onValueSelected(@PickerType int pickerType, int newValue, boolean autoAdvance); 215eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 216eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 217eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio /** 218eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Split up the 360 degrees of the circle among the 60 selectable values. Assigns a larger 219eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * selectable area to each of the 12 visible values, such that the ratio of space apportioned 220eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * to a visible value : space apportioned to a non-visible value will be 14 : 4. 221eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * E.g. the output of 30 degrees should have a higher range of input associated with it than 222eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * the output of 24 degrees, because 30 degrees corresponds to a visible number on the clock 223eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * circle (5 on the minutes, 1 or 13 on the hours). 224eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */ 225eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static void preparePrefer30sMap() { 226eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // We'll split up the visible output and the non-visible output such that each visible 227eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // output will correspond to a range of 14 associated input degrees, and each non-visible 228eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // output will correspond to a range of 4 associate input degrees, so visible numbers 229eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // are more than 3 times easier to get than non-visible numbers: 230eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // {354-359,0-7}:0, {8-11}:6, {12-15}:12, {16-19}:18, {20-23}:24, {24-37}:30, etc. 231eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // 232eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // If an output of 30 degrees should correspond to a range of 14 associated degrees, then 233eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // we'll need any input between 24 - 37 to snap to 30. Working out from there, 20-23 should 234eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // snap to 24, while 38-41 should snap to 36. This is somewhat counter-intuitive, that you 235eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // can be touching 36 degrees but have the selection snapped to 30 degrees; however, this 236eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // inconsistency isn't noticeable at such fine-grained degrees, and it affords us the 237eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // ability to aggressively prefer the visible values by a factor of more than 3:1, which 238eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // greatly contributes to the selectability of these values. 239eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 240eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // The first output is 0, and each following output will increment by 6 {0, 6, 12, ...}. 241eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio int snappedOutputDegrees = 0; 242eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Count of how many inputs we've designated to the specified output. 243eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio int count = 1; 244eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // How many input we expect for a specified output. This will be 14 for output divisible 245eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // by 30, and 4 for the remaining output. We'll special case the outputs of 0 and 360, so 246eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // the caller can decide which they need. 247eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio int expectedCount = 8; 248eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Iterate through the input. 249eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio for (int degrees = 0; degrees < 361; degrees++) { 250eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Save the input-output mapping. 251ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette SNAP_PREFER_30S_MAP[degrees] = snappedOutputDegrees; 252eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // If this is the last input for the specified output, calculate the next output and 253eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // the next expected count. 254eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (count == expectedCount) { 255eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio snappedOutputDegrees += 6; 256eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (snappedOutputDegrees == 360) { 257eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio expectedCount = 7; 258eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else if (snappedOutputDegrees % 30 == 0) { 259eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio expectedCount = 14; 260eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 261eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio expectedCount = 4; 262eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 263eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio count = 1; 264eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 265eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio count++; 266eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 267eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 268eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 269eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 270eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio /** 271eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Returns mapping of any input degrees (0 to 360) to one of 60 selectable output degrees, 272eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * where the degrees corresponding to visible numbers (i.e. those divisible by 30) will be 273eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * weighted heavier than the degrees corresponding to non-visible numbers. 274eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * See {@link #preparePrefer30sMap()} documentation for the rationale and generation of the 275eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * mapping. 276eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */ 277eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static int snapPrefer30s(int degrees) { 278ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette if (SNAP_PREFER_30S_MAP == null) { 279eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio return -1; 280eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 281ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette return SNAP_PREFER_30S_MAP[degrees]; 282eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 283eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 284eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio /** 285eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Returns mapping of any input degrees (0 to 360) to one of 12 visible output degrees (all 286eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * multiples of 30), where the input will be "snapped" to the closest visible degrees. 287eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * @param degrees The input degrees 288eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * @param forceHigherOrLower The output may be forced to either the higher or lower step, or may 289eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * be allowed to snap to whichever is closer. Use 1 to force strictly higher, -1 to force 290eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * strictly lower, and 0 to snap to the closer one. 291eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * @return output degrees, will be a multiple of 30 292eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */ 293eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private static int snapOnly30s(int degrees, int forceHigherOrLower) { 294eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio final int stepSize = DEGREES_FOR_ONE_HOUR; 295eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio int floor = (degrees / stepSize) * stepSize; 296eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio final int ceiling = floor + stepSize; 297eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (forceHigherOrLower == 1) { 298eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio degrees = ceiling; 299eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else if (forceHigherOrLower == -1) { 300eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (degrees == floor) { 301eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio floor -= stepSize; 302eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 303eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio degrees = floor; 304eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 305eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if ((degrees - floor) < (ceiling - degrees)) { 306eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio degrees = floor; 307eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 308eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio degrees = ceiling; 309eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 310eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 311eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio return degrees; 312eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 313eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 314ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @SuppressWarnings("unused") 315ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public RadialTimePickerView(Context context) { 316ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette this(context, null); 317ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 318ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 319eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public RadialTimePickerView(Context context, AttributeSet attrs) { 320eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio this(context, attrs, R.attr.timePickerStyle); 321eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 322eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 323ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public RadialTimePickerView(Context context, AttributeSet attrs, int defStyleAttr) { 324ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette this(context, attrs, defStyleAttr, 0); 325ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 326ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 327ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public RadialTimePickerView( 328ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 329eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio super(context, attrs); 330eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 3312b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette applyAttributes(attrs, defStyleAttr, defStyleRes); 3322b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 3335134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette // Pull disabled alpha from theme. 3345134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette final TypedValue outValue = new TypedValue(); 3355134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true); 336adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mDisabledAlpha = outValue.getFloat(); 3375134478151d8aa3d776f8d4f368dbcdbc501a92aAlan Viverette 338eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mTypeface = Typeface.create("sans-serif", Typeface.NORMAL); 339eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 340eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaint[HOURS] = new Paint(); 341eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaint[HOURS].setAntiAlias(true); 342eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaint[HOURS].setTextAlign(Paint.Align.CENTER); 343eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 344eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaint[MINUTES] = new Paint(); 345eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaint[MINUTES].setAntiAlias(true); 346eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaint[MINUTES].setTextAlign(Paint.Align.CENTER); 347eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 348eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaintCenter.setAntiAlias(true); 349ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 3502b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintSelector[SELECTOR_CIRCLE] = new Paint(); 3512b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintSelector[SELECTOR_CIRCLE].setAntiAlias(true); 352eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 3532b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintSelector[SELECTOR_DOT] = new Paint(); 3542b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintSelector[SELECTOR_DOT].setAntiAlias(true); 355eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 3562b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintSelector[SELECTOR_LINE] = new Paint(); 3572b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintSelector[SELECTOR_LINE].setAntiAlias(true); 3582b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintSelector[SELECTOR_LINE].setStrokeWidth(2); 359eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 360eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mPaintBackground.setAntiAlias(true); 361eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 3622b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final Resources res = getResources(); 363adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mSelectorRadius = res.getDimensionPixelSize(R.dimen.timepicker_selector_radius); 36462c79e9a64c3b2cafd5500ed3064977dff7b7da3Alan Viverette mSelectorStroke = res.getDimensionPixelSize(R.dimen.timepicker_selector_stroke); 365adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mSelectorDotRadius = res.getDimensionPixelSize(R.dimen.timepicker_selector_dot_radius); 366adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mCenterDotRadius = res.getDimensionPixelSize(R.dimen.timepicker_center_dot_radius); 367adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette 368adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mTextSize[HOURS] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_normal); 369adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mTextSize[MINUTES] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_normal); 370adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mTextSize[HOURS_INNER] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_inner); 371adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette 372adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mTextInset[HOURS] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_normal); 373adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mTextInset[MINUTES] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_normal); 374adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mTextInset[HOURS_INNER] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_inner); 375eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 376eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mShowHours = true; 3772b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mHoursToMinutes = HOURS; 378eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mIs24HourMode = false; 379eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mAmOrPm = AM; 380eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 381ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // Set up accessibility components. 382ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mTouchHelper = new RadialPickerTouchHelper(); 383ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setAccessibilityDelegate(mTouchHelper); 384ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 385ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 386ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 387ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 388ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 389eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio initHoursAndMinutesText(); 390eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio initData(); 391eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 392eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Initial values 393eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio final Calendar calendar = Calendar.getInstance(Locale.getDefault()); 394eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio final int currentHour = calendar.get(Calendar.HOUR_OF_DAY); 395eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio final int currentMinute = calendar.get(Calendar.MINUTE); 396eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 397ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentHourInternal(currentHour, false, false); 398ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentMinuteInternal(currentMinute, false); 399eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 400eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio setHapticFeedbackEnabled(true); 401eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 402eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 4032b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette void applyAttributes(AttributeSet attrs, int defStyleAttr, int defStyleRes) { 4042b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final Context context = getContext(); 4052b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final TypedArray a = getContext().obtainStyledAttributes(attrs, 4062b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette R.styleable.TimePicker, defStyleAttr, defStyleRes); 4072b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 4082b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final ColorStateList numbersTextColor = a.getColorStateList( 4092b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette R.styleable.TimePicker_numbersTextColor); 4102b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final ColorStateList numbersInnerTextColor = a.getColorStateList( 4112b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette R.styleable.TimePicker_numbersInnerTextColor); 4122b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mTextColor[HOURS] = numbersTextColor == null ? 4132b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette ColorStateList.valueOf(MISSING_COLOR) : numbersTextColor; 4142b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mTextColor[HOURS_INNER] = numbersInnerTextColor == null ? 4152b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette ColorStateList.valueOf(MISSING_COLOR) : numbersInnerTextColor; 4162b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mTextColor[MINUTES] = mTextColor[HOURS]; 4172b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 4182b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // Set up various colors derived from the selector "activated" state. 4192b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final ColorStateList selectorColors = a.getColorStateList( 4202b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette R.styleable.TimePicker_numbersSelectorColor); 4212b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int selectorActivatedColor; 4222b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (selectorColors != null) { 4232b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int[] stateSetEnabledActivated = StateSet.get( 4242b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED); 4252b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette selectorActivatedColor = selectorColors.getColorForState( 4262b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette stateSetEnabledActivated, 0); 4272b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } else { 4282b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette selectorActivatedColor = MISSING_COLOR; 4292b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 4302b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 4312b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintCenter.setColor(selectorActivatedColor); 4322b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 4332b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int[] stateSetActivated = StateSet.get( 4342b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED); 4352b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 4362b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mSelectorColor = selectorActivatedColor; 4372b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mSelectorDotColor = mTextColor[HOURS].getColorForState(stateSetActivated, 0); 4382b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 4392b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mPaintBackground.setColor(a.getColor(R.styleable.TimePicker_numbersBackgroundColor, 4402b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette context.getColor(R.color.timepicker_default_numbers_background_color_material))); 4412b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 4422b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette a.recycle(); 4432b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 4442b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 445eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void initialize(int hour, int minute, boolean is24HourMode) { 446448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette if (mIs24HourMode != is24HourMode) { 447448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette mIs24HourMode = is24HourMode; 448448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette initData(); 449448ff718a0bb92c5b760668c12d2449a00f9aa2aAlan Viverette } 450ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 451ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentHourInternal(hour, false, false); 452ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentMinuteInternal(minute, false); 453eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 454eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 455eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void setCurrentItemShowing(int item, boolean animate) { 456eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio switch (item){ 457eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio case HOURS: 458eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio showHours(animate); 459eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio break; 460eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio case MINUTES: 461eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio showMinutes(animate); 462eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio break; 463eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio default: 464eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio Log.e(TAG, "ClockView does not support showing item " + item); 465eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 466eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 467eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 468eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public int getCurrentItemShowing() { 469eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio return mShowHours ? HOURS : MINUTES; 470eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 471eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 472eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void setOnValueSelectedListener(OnValueSelectedListener listener) { 473eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mListener = listener; 474eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 475eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 476d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette /** 477d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * Sets the current hour in 24-hour time. 478d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * 479d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * @param hour the current hour between 0 and 23 (inclusive) 480d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette */ 481eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void setCurrentHour(int hour) { 482ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentHourInternal(hour, true, false); 483ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 484ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 485ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette /** 486ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette * Sets the current hour. 487ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette * 488ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette * @param hour The current hour 489ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette * @param callback Whether the value listener should be invoked 490ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette * @param autoAdvance Whether the listener should auto-advance to the next 491ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette * selection mode, e.g. hour to minutes 492ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette */ 493ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private void setCurrentHourInternal(int hour, boolean callback, boolean autoAdvance) { 494eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio final int degrees = (hour % 12) * DEGREES_FOR_ONE_HOUR; 495eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mSelectionDegrees[HOURS] = degrees; 496d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette 497d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette // 0 is 12 AM (midnight) and 12 is 12 PM (noon). 498ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int amOrPm = (hour == 0 || (hour % 24) < 12) ? AM : PM; 49988e51032320ab92057666e1230cc6548ca163c51Alan Viverette final boolean isOnInnerCircle = getInnerCircleForHour(hour); 500ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (mAmOrPm != amOrPm || mIsOnInnerCircle != isOnInnerCircle) { 501ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mAmOrPm = amOrPm; 502ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mIsOnInnerCircle = isOnInnerCircle; 503ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 504ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette initData(); 505ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mTouchHelper.invalidateRoot(); 506ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 507d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette 508eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio invalidate(); 509ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 510ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (callback && mListener != null) { 511ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mListener.onValueSelected(HOURS, hour, autoAdvance); 512ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 513eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 514eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 515d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette /** 516d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * Returns the current hour in 24-hour time. 517d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * 518d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette * @return the current hour between 0 and 23 (inclusive) 519d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette */ 520eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public int getCurrentHour() { 52188e51032320ab92057666e1230cc6548ca163c51Alan Viverette return getHourForDegrees(mSelectionDegrees[HOURS], mIsOnInnerCircle); 522ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 523ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 524ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int getHourForDegrees(int degrees, boolean innerCircle) { 525ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette int hour = (degrees / DEGREES_FOR_ONE_HOUR) % 12; 526eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (mIs24HourMode) { 527d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette // Convert the 12-hour value into 24-hour time based on where the 528d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette // selector is positioned. 52988e51032320ab92057666e1230cc6548ca163c51Alan Viverette if (!innerCircle && hour == 0) { 53088e51032320ab92057666e1230cc6548ca163c51Alan Viverette // Outer circle is 1 through 12. 531d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette hour = 12; 53288e51032320ab92057666e1230cc6548ca163c51Alan Viverette } else if (innerCircle && hour != 0) { 53388e51032320ab92057666e1230cc6548ca163c51Alan Viverette // Inner circle is 13 through 23 and 0. 534d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette hour += 12; 535eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 536d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette } else if (mAmOrPm == PM) { 537d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette hour += 12; 538eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 539d735c9b1ed36c7a45afc662c109c2dbf3e205e39Alan Viverette return hour; 540eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 541eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 54288e51032320ab92057666e1230cc6548ca163c51Alan Viverette /** 54388e51032320ab92057666e1230cc6548ca163c51Alan Viverette * @param hour the hour in 24-hour time or 12-hour time 54488e51032320ab92057666e1230cc6548ca163c51Alan Viverette */ 545ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int getDegreesForHour(int hour) { 546ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // Convert to be 0-11. 547ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (mIs24HourMode) { 548ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (hour >= 12) { 549ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette hour -= 12; 550ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 551ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else if (hour == 12) { 552ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette hour = 0; 553ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 554ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return hour * DEGREES_FOR_ONE_HOUR; 555ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 556ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 55788e51032320ab92057666e1230cc6548ca163c51Alan Viverette /** 55888e51032320ab92057666e1230cc6548ca163c51Alan Viverette * @param hour the hour in 24-hour time or 12-hour time 55988e51032320ab92057666e1230cc6548ca163c51Alan Viverette */ 56088e51032320ab92057666e1230cc6548ca163c51Alan Viverette private boolean getInnerCircleForHour(int hour) { 56188e51032320ab92057666e1230cc6548ca163c51Alan Viverette return mIs24HourMode && (hour == 0 || hour > 12); 56288e51032320ab92057666e1230cc6548ca163c51Alan Viverette } 56388e51032320ab92057666e1230cc6548ca163c51Alan Viverette 564eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void setCurrentMinute(int minute) { 565ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentMinuteInternal(minute, true); 566ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 567ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 568ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private void setCurrentMinuteInternal(int minute, boolean callback) { 569fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette mSelectionDegrees[MINUTES] = (minute % MINUTES_IN_CIRCLE) * DEGREES_FOR_ONE_MINUTE; 570ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 571eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio invalidate(); 572ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 573ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (callback && mListener != null) { 574ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mListener.onValueSelected(MINUTES, minute, false); 575ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 576eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 577eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 578eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Returns minutes in 0-59 range 579eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public int getCurrentMinute() { 580ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return getMinuteForDegrees(mSelectionDegrees[MINUTES]); 581ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 582ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 583ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int getMinuteForDegrees(int degrees) { 584ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return degrees / DEGREES_FOR_ONE_MINUTE; 585ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 586ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 587ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int getDegreesForMinute(int minute) { 588ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return minute * DEGREES_FOR_ONE_MINUTE; 589eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 590eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 59130b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette /** 59230b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette * Sets whether the picker is showing AM or PM hours. Has no effect when 59330b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette * in 24-hour mode. 59430b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette * 59530b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette * @param amOrPm {@link #AM} or {@link #PM} 59630b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette * @return {@code true} if the value changed from what was previously set, 59730b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette * or {@code false} otherwise 59830b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette */ 59930b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette public boolean setAmOrPm(int amOrPm) { 60030b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette if (mAmOrPm == amOrPm || mIs24HourMode) { 60130b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette return false; 60230b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette } 60330b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette 60430b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette mAmOrPm = amOrPm; 605eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio invalidate(); 606ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mTouchHelper.invalidateRoot(); 60730b57b6bd222c11f2fcc347a6c794f78c6fa84bdAlan Viverette return true; 608eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 609eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 610eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public int getAmOrPm() { 611eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio return mAmOrPm; 612eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 613eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 614eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void showHours(boolean animate) { 6152b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette showPicker(true, animate); 616eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 617eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 618eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void showMinutes(boolean animate) { 6192b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette showPicker(false, animate); 620eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 621eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 622eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private void initHoursAndMinutesText() { 623eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Initialize the hours and minutes numbers. 624eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio for (int i = 0; i < 12; i++) { 625eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mHours12Texts[i] = String.format("%d", HOURS_NUMBERS[i]); 626f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette mInnerHours24Texts[i] = String.format("%02d", HOURS_NUMBERS_24[i]); 627f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette mOuterHours24Texts[i] = String.format("%d", HOURS_NUMBERS[i]); 628eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mMinutesTexts[i] = String.format("%02d", MINUTES_NUMBERS[i]); 629eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 630eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 631eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 632eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio private void initData() { 633eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (mIs24HourMode) { 634eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mOuterTextHours = mOuterHours24Texts; 635eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mInnerTextHours = mInnerHours24Texts; 636eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 637eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mOuterTextHours = mHours12Texts; 638f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette mInnerTextHours = mHours12Texts; 639eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 640eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 64188e51032320ab92057666e1230cc6548ca163c51Alan Viverette mMinutesText = mMinutesTexts; 642eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 643eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 644eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio @Override 645eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 646ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette if (!changed) { 647ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette return; 648ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 649eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 650eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mXCenter = getWidth() / 2; 651eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio mYCenter = getHeight() / 2; 652ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette mCircleRadius = Math.min(mXCenter, mYCenter); 653eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 65488e51032320ab92057666e1230cc6548ca163c51Alan Viverette mMinDistForInnerNumber = mCircleRadius - mTextInset[HOURS_INNER] - mSelectorRadius; 65588e51032320ab92057666e1230cc6548ca163c51Alan Viverette mMaxDistForOuterNumber = mCircleRadius - mTextInset[HOURS] + mSelectorRadius; 65688e51032320ab92057666e1230cc6548ca163c51Alan Viverette mHalfwayDist = mCircleRadius - (mTextInset[HOURS] + mTextInset[HOURS_INNER]) / 2; 657eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 658ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette calculatePositionsHours(); 659ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette calculatePositionsMinutes(); 660eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 661ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mTouchHelper.invalidateRoot(); 662eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 663eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 664eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio @Override 665eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio public void onDraw(Canvas canvas) { 666adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette final float alphaMod = mInputEnabled ? 1 : mDisabledAlpha; 667eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 668eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio drawCircleBackground(canvas); 6692b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 6702b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final Path selectorPath = mSelectorPath; 6712b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawSelector(canvas, selectorPath); 6722b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawHours(canvas, selectorPath, alphaMod); 6732b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawMinutes(canvas, selectorPath, alphaMod); 674adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette drawCenter(canvas, alphaMod); 675adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette } 676eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 6772b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private void showPicker(boolean hours, boolean animate) { 6782b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (mShowHours == hours) { 6792b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette return; 6802b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 6812b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 6822b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mShowHours = hours; 6832b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 6842b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (animate) { 6852b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette animatePicker(hours, ANIM_DURATION_NORMAL); 68632f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette } else { 68732f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette // If we have a pending or running animator, cancel it. 68832f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette if (mHoursToMinutesAnimator != null && mHoursToMinutesAnimator.isStarted()) { 68932f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette mHoursToMinutesAnimator.cancel(); 69032f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette mHoursToMinutesAnimator = null; 69132f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette } 69232f7dab2823168f24f2dc769adcea8c0a4c79f2fAlan Viverette mHoursToMinutes = hours ? 0.0f : 1.0f; 6932b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 6942b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 6952b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette initData(); 6962b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette invalidate(); 6972b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mTouchHelper.invalidateRoot(); 6982b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 6992b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 7002b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private void animatePicker(boolean hoursToMinutes, long duration) { 7012b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final float target = hoursToMinutes ? HOURS : MINUTES; 7022b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (mHoursToMinutes == target) { 7032b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // If we have a pending or running animator, cancel it. 7042b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (mHoursToMinutesAnimator != null && mHoursToMinutesAnimator.isStarted()) { 7052b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mHoursToMinutesAnimator.cancel(); 7062b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mHoursToMinutesAnimator = null; 7072b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 7082b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 7092b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // We're already showing the correct picker. 7102b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette return; 7112b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 7122b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 7132b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mHoursToMinutesAnimator = ObjectAnimator.ofFloat(this, HOURS_TO_MINUTES, target); 7142b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mHoursToMinutesAnimator.setAutoCancel(true); 7152b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mHoursToMinutesAnimator.setDuration(duration); 7162b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mHoursToMinutesAnimator.start(); 7172b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 7182b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 719adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private void drawCircleBackground(Canvas canvas) { 720adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette canvas.drawCircle(mXCenter, mYCenter, mCircleRadius, mPaintBackground); 721adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette } 722adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette 7232b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private void drawHours(Canvas canvas, Path selectorPath, float alphaMod) { 7242b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int hoursAlpha = (int) (255f * (1f - mHoursToMinutes) * alphaMod + 0.5f); 725ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette if (hoursAlpha > 0) { 7262b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // Exclude the selector region, then draw inner/outer hours with no 7272b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // activated states. 7282b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.save(Canvas.CLIP_SAVE_FLAG); 7292b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.clipPath(selectorPath, Region.Op.DIFFERENCE); 7302b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawHoursClipped(canvas, hoursAlpha, false); 7312b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.restore(); 7322b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 7332b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // Intersect the selector region, then draw minutes with only 7342b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // activated states. 7352b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.save(Canvas.CLIP_SAVE_FLAG); 7362b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.clipPath(selectorPath, Region.Op.INTERSECT); 7372b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawHoursClipped(canvas, hoursAlpha, true); 7382b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.restore(); 739eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 740adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette } 741eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 7422b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private void drawHoursClipped(Canvas canvas, int hoursAlpha, boolean showActivated) { 7432b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // Draw outer hours. 7442b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawTextElements(canvas, mTextSize[HOURS], mTypeface, mTextColor[HOURS], mOuterTextHours, 7452b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mOuterTextX[HOURS], mOuterTextY[HOURS], mPaint[HOURS], hoursAlpha, 7462b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette showActivated && !mIsOnInnerCircle, mSelectionDegrees[HOURS], showActivated); 747ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 7482b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // Draw inner hours (13-00) for 24-hour time. 7492b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (mIs24HourMode && mInnerTextHours != null) { 7502b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawTextElements(canvas, mTextSize[HOURS_INNER], mTypeface, mTextColor[HOURS_INNER], 7512b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mInnerTextHours, mInnerTextX, mInnerTextY, mPaint[HOURS], hoursAlpha, 7522b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette showActivated && mIsOnInnerCircle, mSelectionDegrees[HOURS], showActivated); 7532b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 7542b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 7552b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 7562b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private void drawMinutes(Canvas canvas, Path selectorPath, float alphaMod) { 7572b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int minutesAlpha = (int) (255f * mHoursToMinutes * alphaMod + 0.5f); 7582b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (minutesAlpha > 0) { 759ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // Exclude the selector region, then draw minutes with no 760ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // activated states. 761ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette canvas.save(Canvas.CLIP_SAVE_FLAG); 7622b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.clipPath(selectorPath, Region.Op.DIFFERENCE); 7632b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawMinutesClipped(canvas, minutesAlpha, false); 764ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette canvas.restore(); 765ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 766ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // Intersect the selector region, then draw minutes with only 767ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // activated states. 768ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette canvas.save(Canvas.CLIP_SAVE_FLAG); 7692b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.clipPath(selectorPath, Region.Op.INTERSECT); 7702b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawMinutesClipped(canvas, minutesAlpha, true); 771ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette canvas.restore(); 772ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 773eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 774eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 7752b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private void drawMinutesClipped(Canvas canvas, int minutesAlpha, boolean showActivated) { 7762b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES], mMinutesText, 7772b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], minutesAlpha, 7782b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette showActivated, mSelectionDegrees[MINUTES], showActivated); 7792b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette } 7802b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 781adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private void drawCenter(Canvas canvas, float alphaMod) { 782adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette mPaintCenter.setAlpha((int) (255 * alphaMod + 0.5f)); 783adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette canvas.drawCircle(mXCenter, mYCenter, mCenterDotRadius, mPaintCenter); 784eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 785eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 786eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette private int getMultipliedAlpha(int argb, int alpha) { 787eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette return (int) (Color.alpha(argb) * (alpha / 255.0) + 0.5); 788eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette } 789eb1d3798e37572ca515aad572350f5745adf023dAlan Viverette 7902b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette private void drawSelector(Canvas canvas, Path selectorPath) { 7912b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // Determine the current length, angle, and dot scaling factor. 7922b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int hoursIndex = mIsOnInnerCircle ? HOURS_INNER : HOURS; 7932b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int hoursInset = mTextInset[hoursIndex]; 7942b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int hoursAngleDeg = mSelectionDegrees[hoursIndex % 2]; 7952b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final float hoursDotScale = mSelectionDegrees[hoursIndex % 2] % 30 != 0 ? 1 : 0; 7962b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 7972b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int minutesIndex = MINUTES; 7982b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int minutesInset = mTextInset[minutesIndex]; 7992b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final int minutesAngleDeg = mSelectionDegrees[minutesIndex]; 8002b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final float minutesDotScale = mSelectionDegrees[minutesIndex] % 30 != 0 ? 1 : 0; 801eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 802f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette // Calculate the current radius at which to place the selection circle. 803f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette final int selRadius = mSelectorRadius; 8042b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final float selLength = 8052b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette mCircleRadius - MathUtils.lerp(hoursInset, minutesInset, mHoursToMinutes); 8062b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final double selAngleRad = 8072b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette Math.toRadians(MathUtils.lerpDeg(hoursAngleDeg, minutesAngleDeg, mHoursToMinutes)); 808f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette final float selCenterX = mXCenter + selLength * (float) Math.sin(selAngleRad); 809f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette final float selCenterY = mYCenter - selLength * (float) Math.cos(selAngleRad); 810f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette 811f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette // Draw the selection circle. 8122b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final Paint paint = mPaintSelector[SELECTOR_CIRCLE]; 8132b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette paint.setColor(mSelectorColor); 814f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette canvas.drawCircle(selCenterX, selCenterY, selRadius, paint); 815eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 816ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // If needed, set up the clip path for later. 817ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette if (selectorPath != null) { 818f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette selectorPath.reset(); 819f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette selectorPath.addCircle(selCenterX, selCenterY, selRadius, Path.Direction.CCW); 820ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 821ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 822f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette // Draw the dot if we're between two items. 8232b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final float dotScale = MathUtils.lerp(hoursDotScale, minutesDotScale, mHoursToMinutes); 8242b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette if (dotScale > 0) { 8252b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final Paint dotPaint = mPaintSelector[SELECTOR_DOT]; 82688e51032320ab92057666e1230cc6548ca163c51Alan Viverette dotPaint.setColor(mSelectorDotColor); 8272b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette canvas.drawCircle(selCenterX, selCenterY, mSelectorDotRadius * dotScale, dotPaint); 828eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 829eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 830adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette // Shorten the line to only go from the edge of the center dot to the 831adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette // edge of the selection circle. 832f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette final double sin = Math.sin(selAngleRad); 833f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette final double cos = Math.cos(selAngleRad); 8342b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final float lineLength = selLength - selRadius; 835adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette final int centerX = mXCenter + (int) (mCenterDotRadius * sin); 836adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette final int centerY = mYCenter - (int) (mCenterDotRadius * cos); 837f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette final float linePointX = centerX + (int) (lineLength * sin); 838f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette final float linePointY = centerY - (int) (lineLength * cos); 839ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 840f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette // Draw the line. 8412b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette final Paint linePaint = mPaintSelector[SELECTOR_LINE]; 8422b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette linePaint.setColor(mSelectorColor); 843f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette linePaint.setStrokeWidth(mSelectorStroke); 844f2525f6802219ad5e983128dc2f7b5a89a507d9cAlan Viverette canvas.drawLine(mXCenter, mYCenter, linePointX, linePointY, linePaint); 845eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 846eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 847ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private void calculatePositionsHours() { 848eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Calculate the text positions 849adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette final float numbersRadius = mCircleRadius - mTextInset[HOURS]; 850eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 851eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Calculate the positions for the 12 numbers in the main circle. 852ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette calculatePositions(mPaint[HOURS], numbersRadius, mXCenter, mYCenter, 853ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette mTextSize[HOURS], mOuterTextX[HOURS], mOuterTextY[HOURS]); 854eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 855eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // If we have an inner circle, calculate those positions too. 856eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (mIs24HourMode) { 857adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette final int innerNumbersRadius = mCircleRadius - mTextInset[HOURS_INNER]; 858ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette calculatePositions(mPaint[HOURS], innerNumbersRadius, mXCenter, mYCenter, 859ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette mTextSize[HOURS_INNER], mInnerTextX, mInnerTextY); 860eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 861eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 862eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 863ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private void calculatePositionsMinutes() { 864eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Calculate the text positions 865adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette final float numbersRadius = mCircleRadius - mTextInset[MINUTES]; 866eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 867eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio // Calculate the positions for the 12 numbers in the main circle. 868ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette calculatePositions(mPaint[MINUTES], numbersRadius, mXCenter, mYCenter, 869ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette mTextSize[MINUTES], mOuterTextX[MINUTES], mOuterTextY[MINUTES]); 870eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 871eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 872eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio /** 873eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Using the trigonometric Unit Circle, calculate the positions that the text will need to be 874eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * drawn at based on the specified circle radius. Place the values in the textGridHeights and 875eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * textGridWidths parameters. 876eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */ 877ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private static void calculatePositions(Paint paint, float radius, float xCenter, float yCenter, 878ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette float textSize, float[] x, float[] y) { 879ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // Adjust yCenter to account for the text's baseline. 880eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio paint.setTextSize(textSize); 881eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio yCenter -= (paint.descent() + paint.ascent()) / 2; 882eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 883ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette for (int i = 0; i < NUM_POSITIONS; i++) { 884ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette x[i] = xCenter - radius * COS_30[i]; 885ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette y[i] = yCenter - radius * SIN_30[i]; 886ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 887eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 888eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 889eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio /** 890eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio * Draw the 12 text values at the positions specified by the textGrid parameters. 891eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio */ 892adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette private void drawTextElements(Canvas canvas, float textSize, Typeface typeface, 893adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette ColorStateList textColor, String[] texts, float[] textX, float[] textY, Paint paint, 894adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette int alpha, boolean showActivated, int activatedDegrees, boolean activatedOnly) { 895eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio paint.setTextSize(textSize); 896eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio paint.setTypeface(typeface); 897eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 898ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette // The activated index can touch a range of elements. 899ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette final float activatedIndex = activatedDegrees / (360.0f / NUM_POSITIONS); 900ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette final int activatedFloor = (int) activatedIndex; 901ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette final int activatedCeil = ((int) Math.ceil(activatedIndex)) % NUM_POSITIONS; 902eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 903ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette for (int i = 0; i < 12; i++) { 904ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette final boolean activated = (activatedFloor == i || activatedCeil == i); 905ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette if (activatedOnly && !activated) { 906ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette continue; 907ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 908eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 909ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette final int stateMask = StateSet.VIEW_STATE_ENABLED 910ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette | (showActivated && activated ? StateSet.VIEW_STATE_ACTIVATED : 0); 911adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette final int color = textColor.getColorForState(StateSet.get(stateMask), 0); 912ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette paint.setColor(color); 913ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette paint.setAlpha(getMultipliedAlpha(color, alpha)); 914eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 915ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette canvas.drawText(texts[i], textX[i], textY[i], paint); 916ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette } 917eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 918eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 919ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette private int getDegreesFromXY(float x, float y, boolean constrainOutside) { 92088e51032320ab92057666e1230cc6548ca163c51Alan Viverette // Ensure the point is inside the touchable area. 92188e51032320ab92057666e1230cc6548ca163c51Alan Viverette final int innerBound; 92288e51032320ab92057666e1230cc6548ca163c51Alan Viverette final int outerBound; 92388e51032320ab92057666e1230cc6548ca163c51Alan Viverette if (mIs24HourMode && mShowHours) { 92488e51032320ab92057666e1230cc6548ca163c51Alan Viverette innerBound = mMinDistForInnerNumber; 92588e51032320ab92057666e1230cc6548ca163c51Alan Viverette outerBound = mMaxDistForOuterNumber; 92688e51032320ab92057666e1230cc6548ca163c51Alan Viverette } else { 92788e51032320ab92057666e1230cc6548ca163c51Alan Viverette final int index = mShowHours ? HOURS : MINUTES; 92888e51032320ab92057666e1230cc6548ca163c51Alan Viverette final int center = mCircleRadius - mTextInset[index]; 92988e51032320ab92057666e1230cc6548ca163c51Alan Viverette innerBound = center - mSelectorRadius; 93088e51032320ab92057666e1230cc6548ca163c51Alan Viverette outerBound = center + mSelectorRadius; 93188e51032320ab92057666e1230cc6548ca163c51Alan Viverette } 932eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 93388e51032320ab92057666e1230cc6548ca163c51Alan Viverette final double dX = x - mXCenter; 93488e51032320ab92057666e1230cc6548ca163c51Alan Viverette final double dY = y - mYCenter; 93588e51032320ab92057666e1230cc6548ca163c51Alan Viverette final double distFromCenter = Math.sqrt(dX * dX + dY * dY); 93688e51032320ab92057666e1230cc6548ca163c51Alan Viverette if (distFromCenter < innerBound || constrainOutside && distFromCenter > outerBound) { 937eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio return -1; 938eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 939ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette 94088e51032320ab92057666e1230cc6548ca163c51Alan Viverette // Convert to degrees. 94188e51032320ab92057666e1230cc6548ca163c51Alan Viverette final int degrees = (int) (Math.toDegrees(Math.atan2(dY, dX) + Math.PI / 2) + 0.5); 94288e51032320ab92057666e1230cc6548ca163c51Alan Viverette if (degrees < 0) { 94388e51032320ab92057666e1230cc6548ca163c51Alan Viverette return degrees + 360; 944eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 94588e51032320ab92057666e1230cc6548ca163c51Alan Viverette return degrees; 946eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 94788e51032320ab92057666e1230cc6548ca163c51Alan Viverette } 948eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 94988e51032320ab92057666e1230cc6548ca163c51Alan Viverette private boolean getInnerCircleFromXY(float x, float y) { 95088e51032320ab92057666e1230cc6548ca163c51Alan Viverette if (mIs24HourMode && mShowHours) { 95188e51032320ab92057666e1230cc6548ca163c51Alan Viverette final double dX = x - mXCenter; 95288e51032320ab92057666e1230cc6548ca163c51Alan Viverette final double dY = y - mYCenter; 95388e51032320ab92057666e1230cc6548ca163c51Alan Viverette final double distFromCenter = Math.sqrt(dX * dX + dY * dY); 95488e51032320ab92057666e1230cc6548ca163c51Alan Viverette return distFromCenter <= mHalfwayDist; 955eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 95688e51032320ab92057666e1230cc6548ca163c51Alan Viverette return false; 957eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 958eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 959002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette boolean mChangedDuringTouch = false; 960002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 961eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio @Override 962ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette public boolean onTouchEvent(MotionEvent event) { 963002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette if (!mInputEnabled) { 964eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio return true; 965eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 966eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 967002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette final int action = event.getActionMasked(); 968002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette if (action == MotionEvent.ACTION_MOVE 969002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette || action == MotionEvent.ACTION_UP 970002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette || action == MotionEvent.ACTION_DOWN) { 971002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette boolean forceSelection = false; 972002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette boolean autoAdvance = false; 973002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 974002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette if (action == MotionEvent.ACTION_DOWN) { 975002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette // This is a new event stream, reset whether the value changed. 976002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette mChangedDuringTouch = false; 977002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } else if (action == MotionEvent.ACTION_UP) { 978002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette autoAdvance = true; 979002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 980002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette // If we saw a down/up pair without the value changing, assume 981002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette // this is a single-tap selection and force a change. 982002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette if (!mChangedDuringTouch) { 983002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette forceSelection = true; 984eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 985002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } 986eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 987002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette mChangedDuringTouch |= handleTouchInput( 988002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette event.getX(), event.getY(), forceSelection, autoAdvance); 989eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 990002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 991002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette return true; 992002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } 993002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 994002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette private boolean handleTouchInput( 995002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette float x, float y, boolean forceSelection, boolean autoAdvance) { 99688e51032320ab92057666e1230cc6548ca163c51Alan Viverette final boolean isOnInnerCircle = getInnerCircleFromXY(x, y); 997ec9fe1ad663d59829075029508673b2e913538d3Alan Viverette final int degrees = getDegreesFromXY(x, y, false); 998002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette if (degrees == -1) { 999002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette return false; 1000002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } 1001002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 10022b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette // Ensure we're showing the correct picker. 10032b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette animatePicker(mShowHours, ANIM_DURATION_TOUCH); 10042b4dc1156695ae0a7498bf8fe7cd6b82941026d3Alan Viverette 100566a8562c58507c52d0b9924aa0c7c246b1c723e8Alan Viverette final @PickerType int type; 100611a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette final int newValue; 100711a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette final boolean valueChanged; 1008002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 1009002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette if (mShowHours) { 1010002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette final int snapDegrees = snapOnly30s(degrees, 0) % 360; 101188e51032320ab92057666e1230cc6548ca163c51Alan Viverette valueChanged = mIsOnInnerCircle != isOnInnerCircle 101288e51032320ab92057666e1230cc6548ca163c51Alan Viverette || mSelectionDegrees[HOURS] != snapDegrees; 101388e51032320ab92057666e1230cc6548ca163c51Alan Viverette mIsOnInnerCircle = isOnInnerCircle; 101488e51032320ab92057666e1230cc6548ca163c51Alan Viverette mSelectionDegrees[HOURS] = snapDegrees; 101511a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette type = HOURS; 101611a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette newValue = getCurrentHour(); 1017002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } else { 1018002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette final int snapDegrees = snapPrefer30s(degrees) % 360; 101988e51032320ab92057666e1230cc6548ca163c51Alan Viverette valueChanged = mSelectionDegrees[MINUTES] != snapDegrees; 102088e51032320ab92057666e1230cc6548ca163c51Alan Viverette mSelectionDegrees[MINUTES] = snapDegrees; 102111a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette type = MINUTES; 102211a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette newValue = getCurrentMinute(); 1023002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } 1024002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 102511a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette if (valueChanged || forceSelection || autoAdvance) { 102611a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette // Fire the listener even if we just need to auto-advance. 1027002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette if (mListener != null) { 1028002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette mListener.onValueSelected(type, newValue, autoAdvance); 1029002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } 103011a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette 103111a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette // Only provide feedback if the value actually changed. 103211a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette if (valueChanged || forceSelection) { 103311a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK); 103411a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette invalidate(); 103511a68e18039b6bb2b67c5f7a82fa13d60583b2c3Alan Viverette } 1036002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette return true; 1037002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette } 1038002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette 1039002f918ceae416ba5c382de499ccff12e642c7b5Alan Viverette return false; 1040eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1041eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 1042eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio @Override 1043ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public boolean dispatchHoverEvent(MotionEvent event) { 1044ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // First right-of-refusal goes the touch exploration helper. 1045ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (mTouchHelper.dispatchHoverEvent(event)) { 1046eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio return true; 1047eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1048ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return super.dispatchHoverEvent(event); 1049eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1050eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 1051ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public void setInputEnabled(boolean inputEnabled) { 1052ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette mInputEnabled = inputEnabled; 1053ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette invalidate(); 1054ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1055ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 105647f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev @Override 105747f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { 105847f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev if (!isEnabled()) { 105947f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev return null; 106047f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev } 106147f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev final int degrees = getDegreesFromXY(event.getX(), event.getY(), false); 106247f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev if (degrees != -1) { 106347f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND); 106447f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev } 106547f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev return super.onResolvePointerIcon(event, pointerIndex); 106647f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev } 106747f333afa883111e52abdb72ef7db214efa112daVladislav Kaznacheev 1068ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private class RadialPickerTouchHelper extends ExploreByTouchHelper { 1069ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final Rect mTempRect = new Rect(); 1070ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1071ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final int TYPE_HOUR = 1; 1072ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final int TYPE_MINUTE = 2; 1073ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1074ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final int SHIFT_TYPE = 0; 1075ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final int MASK_TYPE = 0xF; 1076ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1077ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final int SHIFT_VALUE = 8; 1078ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final int MASK_VALUE = 0xFF; 1079ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1080ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette /** Increment in which virtual views are exposed for minutes. */ 1081ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private final int MINUTE_INCREMENT = 5; 1082ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1083ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public RadialPickerTouchHelper() { 1084ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette super(RadialTimePickerView.this); 1085eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1086eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 1087ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @Override 1088ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 1089ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette super.onInitializeAccessibilityNodeInfo(host, info); 1090ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1091ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD); 1092ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD); 1093eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1094ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1095ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @Override 1096ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette public boolean performAccessibilityAction(View host, int action, Bundle arguments) { 1097ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (super.performAccessibilityAction(host, action, arguments)) { 1098ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return true; 1099eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1100eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 1101ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette switch (action) { 1102ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: 1103ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette adjustPicker(1); 1104ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return true; 1105ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: 1106ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette adjustPicker(-1); 1107ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return true; 1108ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1109ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1110ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return false; 1111ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1112ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1113ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private void adjustPicker(int step) { 1114ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int stepSize; 11154a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette final int initialStep; 111667945c11a5e9547f71be91ceb99e7b9ff15a6292Alan Viverette final int maxValue; 1117ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int minValue; 1118eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (mShowHours) { 11194a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette stepSize = 1; 1120ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 11214a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette final int currentHour24 = getCurrentHour(); 1122eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (mIs24HourMode) { 11234a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette initialStep = currentHour24; 1124ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette minValue = 0; 11254a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette maxValue = 23; 1126eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 11274a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette initialStep = hour24To12(currentHour24); 1128eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio minValue = 1; 11294a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette maxValue = 12; 1130eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1131eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 11324a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette stepSize = 5; 11334a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette initialStep = getCurrentMinute() / stepSize; 1134ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette minValue = 0; 11354a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette maxValue = 55; 1136eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1137ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 11384a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette final int nextValue = (initialStep + step) * stepSize; 11394a6bd692d2de2127406e89d3aefeb8a6c950deb6Alan Viverette final int clampedValue = MathUtils.constrain(nextValue, minValue, maxValue); 1140ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (mShowHours) { 1141ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentHour(clampedValue); 1142ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1143ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentMinute(clampedValue); 1144ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1145ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1146ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1147ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @Override 1148ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette protected int getVirtualViewAt(float x, float y) { 1149ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int id; 1150d8c2af55420d869c140278e6643d16cd90cfeb51Alan Viverette final int degrees = getDegreesFromXY(x, y, true); 1151ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (degrees != -1) { 1152ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int snapDegrees = snapOnly30s(degrees, 0) % 360; 1153ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (mShowHours) { 115488e51032320ab92057666e1230cc6548ca163c51Alan Viverette final boolean isOnInnerCircle = getInnerCircleFromXY(x, y); 11555efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette final int hour24 = getHourForDegrees(snapDegrees, isOnInnerCircle); 11565efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette final int hour = mIs24HourMode ? hour24 : hour24To12(hour24); 1157ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette id = makeId(TYPE_HOUR, hour); 1158ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1159ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int current = getCurrentMinute(); 1160ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int touched = getMinuteForDegrees(degrees); 1161ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int snapped = getMinuteForDegrees(snapDegrees); 1162ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1163ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // If the touched minute is closer to the current minute 1164ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // than it is to the snapped minute, return current. 1165fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette final int currentOffset = getCircularDiff(current, touched, MINUTES_IN_CIRCLE); 1166fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette final int snappedOffset = getCircularDiff(snapped, touched, MINUTES_IN_CIRCLE); 1167ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int minute; 116888e51032320ab92057666e1230cc6548ca163c51Alan Viverette if (currentOffset < snappedOffset) { 1169ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette minute = current; 1170ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1171ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette minute = snapped; 1172ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1173ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette id = makeId(TYPE_MINUTE, minute); 1174ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1175ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1176ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette id = INVALID_ID; 1177eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1178ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1179ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return id; 1180ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1181ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 118288e51032320ab92057666e1230cc6548ca163c51Alan Viverette /** 118388e51032320ab92057666e1230cc6548ca163c51Alan Viverette * Returns the difference in degrees between two values along a circle. 118488e51032320ab92057666e1230cc6548ca163c51Alan Viverette * 118588e51032320ab92057666e1230cc6548ca163c51Alan Viverette * @param first value in the range [0,max] 118688e51032320ab92057666e1230cc6548ca163c51Alan Viverette * @param second value in the range [0,max] 118788e51032320ab92057666e1230cc6548ca163c51Alan Viverette * @param max the maximum value along the circle 118888e51032320ab92057666e1230cc6548ca163c51Alan Viverette * @return the difference in between the two values 118988e51032320ab92057666e1230cc6548ca163c51Alan Viverette */ 119088e51032320ab92057666e1230cc6548ca163c51Alan Viverette private int getCircularDiff(int first, int second, int max) { 119188e51032320ab92057666e1230cc6548ca163c51Alan Viverette final int diff = Math.abs(first - second); 119288e51032320ab92057666e1230cc6548ca163c51Alan Viverette final int midpoint = max / 2; 119388e51032320ab92057666e1230cc6548ca163c51Alan Viverette return (diff > midpoint) ? (max - diff) : diff; 119488e51032320ab92057666e1230cc6548ca163c51Alan Viverette } 119588e51032320ab92057666e1230cc6548ca163c51Alan Viverette 1196ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @Override 1197ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette protected void getVisibleVirtualViews(IntArray virtualViewIds) { 1198eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio if (mShowHours) { 1199ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int min = mIs24HourMode ? 0 : 1; 1200ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int max = mIs24HourMode ? 23 : 12; 1201ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette for (int i = min; i <= max ; i++) { 1202ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette virtualViewIds.add(makeId(TYPE_HOUR, i)); 1203eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1204eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } else { 1205ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int current = getCurrentMinute(); 1206fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette for (int i = 0; i < MINUTES_IN_CIRCLE; i += MINUTE_INCREMENT) { 1207ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette virtualViewIds.add(makeId(TYPE_MINUTE, i)); 1208ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1209ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // If the current minute falls between two increments, 1210ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // insert an extra node for it. 1211ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (current > i && current < i + MINUTE_INCREMENT) { 1212ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette virtualViewIds.add(makeId(TYPE_MINUTE, current)); 1213ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1214eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1215eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1216eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1217eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 1218ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @Override 1219ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) { 1220ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette event.setClassName(getClass().getName()); 1221eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio 1222ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int type = getTypeFromId(virtualViewId); 1223ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int value = getValueFromId(virtualViewId); 1224ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final CharSequence description = getVirtualViewDescription(type, value); 1225ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette event.setContentDescription(description); 1226ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1227ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1228ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @Override 1229ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) { 1230ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette node.setClassName(getClass().getName()); 1231ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette node.addAction(AccessibilityAction.ACTION_CLICK); 1232ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1233ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int type = getTypeFromId(virtualViewId); 1234ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int value = getValueFromId(virtualViewId); 1235ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final CharSequence description = getVirtualViewDescription(type, value); 1236ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette node.setContentDescription(description); 1237ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1238ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette getBoundsForVirtualView(virtualViewId, mTempRect); 1239ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette node.setBoundsInParent(mTempRect); 1240ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1241ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final boolean selected = isVirtualViewSelected(type, value); 1242ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette node.setSelected(selected); 12433fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette 12443fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette final int nextId = getVirtualViewIdAfter(type, value); 12453fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette if (nextId != INVALID_ID) { 12463fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette node.setTraversalBefore(RadialTimePickerView.this, nextId); 12473fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette } 12483fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette } 12493fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette 12503fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette private int getVirtualViewIdAfter(int type, int value) { 12513fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette if (type == TYPE_HOUR) { 12523fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette final int nextValue = value + 1; 12533fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette final int max = mIs24HourMode ? 23 : 12; 12543fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette if (nextValue <= max) { 12553fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette return makeId(type, nextValue); 12563fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette } 12573fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette } else if (type == TYPE_MINUTE) { 12583fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette final int current = getCurrentMinute(); 12593fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette final int snapValue = value - (value % MINUTE_INCREMENT); 12603fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette final int nextValue = snapValue + MINUTE_INCREMENT; 12613fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette if (value < current && nextValue > current) { 12623fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette // The current value is between two snap values. 12633fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette return makeId(type, current); 1264fc76a64be7a45c3f51c67b43028ca96484933881Alan Viverette } else if (nextValue < MINUTES_IN_CIRCLE) { 12653fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette return makeId(type, nextValue); 12663fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette } 12673fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette } 12683fc00e3139706c2c90f8e7261eef48086887dc11Alan Viverette return INVALID_ID; 1269ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1270ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1271ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette @Override 1272ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette protected boolean onPerformActionForVirtualView(int virtualViewId, int action, 1273ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette Bundle arguments) { 1274ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (action == AccessibilityNodeInfo.ACTION_CLICK) { 1275ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int type = getTypeFromId(virtualViewId); 1276ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int value = getValueFromId(virtualViewId); 1277ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (type == TYPE_HOUR) { 1278ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int hour = mIs24HourMode ? value : hour12To24(value, mAmOrPm); 1279ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentHour(hour); 1280ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return true; 1281ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else if (type == TYPE_MINUTE) { 1282ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette setCurrentMinute(value); 1283ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return true; 1284ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1285ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1286ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return false; 1287ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1288ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1289ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int hour12To24(int hour12, int amOrPm) { 1290ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette int hour24 = hour12; 1291ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (hour12 == 12) { 1292ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (amOrPm == AM) { 1293ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette hour24 = 0; 1294ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1295ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else if (amOrPm == PM) { 1296ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette hour24 += 12; 1297ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1298ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return hour24; 1299ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1300ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 13015efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette private int hour24To12(int hour24) { 13025efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette if (hour24 == 0) { 13035efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette return 12; 13045efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette } else if (hour24 > 12) { 13055efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette return hour24 - 12; 13065efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette } else { 13075efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette return hour24; 13085efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette } 13095efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette } 13105efe0d199e9a6133af355ace6e4b52b82a65af6eAlan Viverette 1311ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private void getBoundsForVirtualView(int virtualViewId, Rect bounds) { 1312ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final float radius; 1313ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int type = getTypeFromId(virtualViewId); 1314ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final int value = getValueFromId(virtualViewId); 1315ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final float centerRadius; 1316ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final float degrees; 1317ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (type == TYPE_HOUR) { 131888e51032320ab92057666e1230cc6548ca163c51Alan Viverette final boolean innerCircle = getInnerCircleForHour(value); 1319ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (innerCircle) { 1320adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette centerRadius = mCircleRadius - mTextInset[HOURS_INNER]; 1321adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette radius = mSelectorRadius; 1322ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1323adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette centerRadius = mCircleRadius - mTextInset[HOURS]; 1324adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette radius = mSelectorRadius; 1325ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1326ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1327ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette degrees = getDegreesForHour(value); 1328ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else if (type == TYPE_MINUTE) { 1329adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette centerRadius = mCircleRadius - mTextInset[MINUTES]; 1330ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette degrees = getDegreesForMinute(value); 1331adbc95f015aed3c6d67bc05507cafa2148cc5d94Alan Viverette radius = mSelectorRadius; 1332ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1333ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette // This should never happen. 1334ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette centerRadius = 0; 1335ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette degrees = 0; 1336ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette radius = 0; 1337ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1338ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1339ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final double radians = Math.toRadians(degrees); 1340ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final float xCenter = mXCenter + centerRadius * (float) Math.sin(radians); 1341ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final float yCenter = mYCenter - centerRadius * (float) Math.cos(radians); 1342ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1343ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette bounds.set((int) (xCenter - radius), (int) (yCenter - radius), 1344ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette (int) (xCenter + radius), (int) (yCenter + radius)); 1345ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1346ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1347ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private CharSequence getVirtualViewDescription(int type, int value) { 1348ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final CharSequence description; 1349ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (type == TYPE_HOUR || type == TYPE_MINUTE) { 1350ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette description = Integer.toString(value); 1351ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1352ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette description = null; 1353ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1354ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return description; 1355ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1356ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1357ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private boolean isVirtualViewSelected(int type, int value) { 1358ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette final boolean selected; 1359ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette if (type == TYPE_HOUR) { 1360ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette selected = getCurrentHour() == value; 1361ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else if (type == TYPE_MINUTE) { 1362ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette selected = getCurrentMinute() == value; 1363ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } else { 1364ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette selected = false; 1365ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1366ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return selected; 1367ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1368ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1369ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int makeId(int type, int value) { 1370ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return type << SHIFT_TYPE | value << SHIFT_VALUE; 1371ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1372ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1373ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int getTypeFromId(int id) { 1374ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return id >>> SHIFT_TYPE & MASK_TYPE; 1375ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1376ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette 1377ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette private int getValueFromId(int id) { 1378ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette return id >>> SHIFT_VALUE & MASK_VALUE; 1379ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette } 1380eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio } 1381eeff63a5c347f282b5c8c3ae6a0924bf03fbce8fFabrice Di Meglio} 1382