Merge "Add support for Explore by Touch to RadialTimePickerView" into lmp-mr1-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
1ac52667ee
162
core/java/android/util/IntArray.java
Normal file
162
core/java/android/util/IntArray.java
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.util;
|
||||||
|
|
||||||
|
import com.android.internal.util.ArrayUtils;
|
||||||
|
|
||||||
|
import libcore.util.EmptyArray;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a growing array of int primitives.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public class IntArray implements Cloneable {
|
||||||
|
private static final int MIN_CAPACITY_INCREMENT = 12;
|
||||||
|
|
||||||
|
private int[] mValues;
|
||||||
|
private int mSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an empty IntArray with the default initial capacity.
|
||||||
|
*/
|
||||||
|
public IntArray() {
|
||||||
|
this(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an empty IntArray with the specified initial capacity.
|
||||||
|
*/
|
||||||
|
public IntArray(int initialCapacity) {
|
||||||
|
if (initialCapacity == 0) {
|
||||||
|
mValues = EmptyArray.INT;
|
||||||
|
} else {
|
||||||
|
mValues = ArrayUtils.newUnpaddedIntArray(initialCapacity);
|
||||||
|
}
|
||||||
|
mSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends the specified value to the end of this array.
|
||||||
|
*/
|
||||||
|
public void add(int value) {
|
||||||
|
add(mSize, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a value at the specified position in this array.
|
||||||
|
*
|
||||||
|
* @throws IndexOutOfBoundsException when index < 0 || index > size()
|
||||||
|
*/
|
||||||
|
public void add(int index, int value) {
|
||||||
|
if (index < 0 || index > mSize) {
|
||||||
|
throw new IndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureCapacity(1);
|
||||||
|
|
||||||
|
if (mSize - index != 0) {
|
||||||
|
System.arraycopy(mValues, index, mValues, index + 1, mSize - index);
|
||||||
|
}
|
||||||
|
|
||||||
|
mValues[index] = value;
|
||||||
|
mSize++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the values in the specified array to this array.
|
||||||
|
*/
|
||||||
|
public void addAll(IntArray values) {
|
||||||
|
final int count = values.mSize;
|
||||||
|
ensureCapacity(count);
|
||||||
|
|
||||||
|
System.arraycopy(values.mValues, 0, mValues, mSize, count);
|
||||||
|
mSize += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures capacity to append at least <code>count</code> values.
|
||||||
|
*/
|
||||||
|
private void ensureCapacity(int count) {
|
||||||
|
final int currentSize = mSize;
|
||||||
|
final int minCapacity = currentSize + count;
|
||||||
|
if (minCapacity >= mValues.length) {
|
||||||
|
final int targetCap = currentSize + (currentSize < (MIN_CAPACITY_INCREMENT / 2) ?
|
||||||
|
MIN_CAPACITY_INCREMENT : currentSize >> 1);
|
||||||
|
final int newCapacity = targetCap > minCapacity ? targetCap : minCapacity;
|
||||||
|
final int[] newValues = ArrayUtils.newUnpaddedIntArray(newCapacity);
|
||||||
|
System.arraycopy(mValues, 0, newValues, 0, currentSize);
|
||||||
|
mValues = newValues;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all values from this array.
|
||||||
|
*/
|
||||||
|
public void clear() {
|
||||||
|
mSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IntArray clone() throws CloneNotSupportedException {
|
||||||
|
final IntArray clone = (IntArray) super.clone();
|
||||||
|
clone.mValues = mValues.clone();
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at the specified position in this array.
|
||||||
|
*/
|
||||||
|
public int get(int index) {
|
||||||
|
if (index >= mSize) {
|
||||||
|
throw new ArrayIndexOutOfBoundsException(mSize, index);
|
||||||
|
}
|
||||||
|
return mValues[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the index of the first occurrence of the specified value in this
|
||||||
|
* array, or -1 if this array does not contain the value.
|
||||||
|
*/
|
||||||
|
public int indexOf(int value) {
|
||||||
|
final int n = mSize;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (mValues[i] == value) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the value at the specified index from this array.
|
||||||
|
*/
|
||||||
|
public void remove(int index) {
|
||||||
|
if (index >= mSize) {
|
||||||
|
throw new ArrayIndexOutOfBoundsException(mSize, index);
|
||||||
|
}
|
||||||
|
System.arraycopy(mValues, index + 1, mValues, index, mSize - index - 1);
|
||||||
|
mSize--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of values in this array.
|
||||||
|
*/
|
||||||
|
public int size() {
|
||||||
|
return mSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,13 +28,13 @@ import android.content.res.TypedArray;
|
|||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.graphics.RectF;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.format.DateUtils;
|
|
||||||
import android.text.format.Time;
|
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.util.IntArray;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.MathUtils;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.HapticFeedbackConstants;
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
@@ -42,8 +42,10 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.accessibility.AccessibilityEvent;
|
import android.view.accessibility.AccessibilityEvent;
|
||||||
import android.view.accessibility.AccessibilityNodeInfo;
|
import android.view.accessibility.AccessibilityNodeInfo;
|
||||||
|
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
|
||||||
|
|
||||||
import com.android.internal.R;
|
import com.android.internal.R;
|
||||||
|
import com.android.internal.widget.ExploreByTouchHelper;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
@@ -97,6 +99,9 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
|
|||||||
|
|
||||||
private static int[] sSnapPrefer30sMap = new int[361];
|
private static int[] sSnapPrefer30sMap = new int[361];
|
||||||
|
|
||||||
|
private final InvalidateUpdateListener mInvalidateUpdateListener =
|
||||||
|
new InvalidateUpdateListener();
|
||||||
|
|
||||||
private final String[] mHours12Texts = new String[12];
|
private final String[] mHours12Texts = new String[12];
|
||||||
private final String[] mOuterHours24Texts = new String[12];
|
private final String[] mOuterHours24Texts = new String[12];
|
||||||
private final String[] mInnerHours24Texts = new String[12];
|
private final String[] mInnerHours24Texts = new String[12];
|
||||||
@@ -115,7 +120,39 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
|
|||||||
private final Paint mPaintBackground = new Paint();
|
private final Paint mPaintBackground = new Paint();
|
||||||
private final Paint mPaintDebug = new Paint();
|
private final Paint mPaintDebug = new Paint();
|
||||||
|
|
||||||
private Typeface mTypeface;
|
private final Typeface mTypeface;
|
||||||
|
|
||||||
|
private final float[] mCircleRadius = new float[3];
|
||||||
|
|
||||||
|
private final float[] mTextSize = new float[2];
|
||||||
|
|
||||||
|
private final float[][] mTextGridHeights = new float[2][7];
|
||||||
|
private final float[][] mTextGridWidths = new float[2][7];
|
||||||
|
|
||||||
|
private final float[] mInnerTextGridHeights = new float[7];
|
||||||
|
private final float[] mInnerTextGridWidths = new float[7];
|
||||||
|
|
||||||
|
private final float[] mCircleRadiusMultiplier = new float[2];
|
||||||
|
private final float[] mNumbersRadiusMultiplier = new float[3];
|
||||||
|
|
||||||
|
private final float[] mTextSizeMultiplier = new float[3];
|
||||||
|
|
||||||
|
private final float[] mAnimationRadiusMultiplier = new float[3];
|
||||||
|
|
||||||
|
private final float mTransitionMidRadiusMultiplier;
|
||||||
|
private final float mTransitionEndRadiusMultiplier;
|
||||||
|
|
||||||
|
private final int[] mLineLength = new int[3];
|
||||||
|
private final int[] mSelectionRadius = new int[3];
|
||||||
|
private final float mSelectionRadiusMultiplier;
|
||||||
|
private final int[] mSelectionDegrees = new int[3];
|
||||||
|
|
||||||
|
private final ArrayList<Animator> mHoursToMinutesAnims = new ArrayList<Animator>();
|
||||||
|
private final ArrayList<Animator> mMinuteToHoursAnims = new ArrayList<Animator>();
|
||||||
|
|
||||||
|
private final RadialPickerTouchHelper mTouchHelper;
|
||||||
|
|
||||||
|
private float mInnerTextSize;
|
||||||
|
|
||||||
private boolean mIs24HourMode;
|
private boolean mIs24HourMode;
|
||||||
private boolean mShowHours;
|
private boolean mShowHours;
|
||||||
@@ -129,52 +166,21 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
|
|||||||
private int mXCenter;
|
private int mXCenter;
|
||||||
private int mYCenter;
|
private int mYCenter;
|
||||||
|
|
||||||
private float[] mCircleRadius = new float[3];
|
|
||||||
|
|
||||||
private int mMinHypotenuseForInnerNumber;
|
private int mMinHypotenuseForInnerNumber;
|
||||||
private int mMaxHypotenuseForOuterNumber;
|
private int mMaxHypotenuseForOuterNumber;
|
||||||
private int mHalfwayHypotenusePoint;
|
private int mHalfwayHypotenusePoint;
|
||||||
|
|
||||||
private float[] mTextSize = new float[2];
|
|
||||||
private float mInnerTextSize;
|
|
||||||
|
|
||||||
private float[][] mTextGridHeights = new float[2][7];
|
|
||||||
private float[][] mTextGridWidths = new float[2][7];
|
|
||||||
|
|
||||||
private float[] mInnerTextGridHeights = new float[7];
|
|
||||||
private float[] mInnerTextGridWidths = new float[7];
|
|
||||||
|
|
||||||
private String[] mOuterTextHours;
|
private String[] mOuterTextHours;
|
||||||
private String[] mInnerTextHours;
|
private String[] mInnerTextHours;
|
||||||
private String[] mOuterTextMinutes;
|
private String[] mOuterTextMinutes;
|
||||||
|
|
||||||
private float[] mCircleRadiusMultiplier = new float[2];
|
|
||||||
private float[] mNumbersRadiusMultiplier = new float[3];
|
|
||||||
|
|
||||||
private float[] mTextSizeMultiplier = new float[3];
|
|
||||||
|
|
||||||
private float[] mAnimationRadiusMultiplier = new float[3];
|
|
||||||
|
|
||||||
private float mTransitionMidRadiusMultiplier;
|
|
||||||
private float mTransitionEndRadiusMultiplier;
|
|
||||||
|
|
||||||
private AnimatorSet mTransition;
|
private AnimatorSet mTransition;
|
||||||
private InvalidateUpdateListener mInvalidateUpdateListener = new InvalidateUpdateListener();
|
|
||||||
|
|
||||||
private int[] mLineLength = new int[3];
|
|
||||||
private int[] mSelectionRadius = new int[3];
|
|
||||||
private float mSelectionRadiusMultiplier;
|
|
||||||
private int[] mSelectionDegrees = new int[3];
|
|
||||||
|
|
||||||
private int mAmOrPm;
|
private int mAmOrPm;
|
||||||
private int mDisabledAlpha;
|
private int mDisabledAlpha;
|
||||||
|
|
||||||
private RectF mRectF = new RectF();
|
|
||||||
private boolean mInputEnabled = true;
|
|
||||||
private OnValueSelectedListener mListener;
|
private OnValueSelectedListener mListener;
|
||||||
|
|
||||||
private final ArrayList<Animator> mHoursToMinutesAnims = new ArrayList<Animator>();
|
private boolean mInputEnabled = true;
|
||||||
private final ArrayList<Animator> mMinuteToHoursAnims = new ArrayList<Animator>();
|
|
||||||
|
|
||||||
public interface OnValueSelectedListener {
|
public interface OnValueSelectedListener {
|
||||||
void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance);
|
void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance);
|
||||||
@@ -282,11 +288,21 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
|
|||||||
return degrees;
|
return degrees;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public RadialTimePickerView(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
public RadialTimePickerView(Context context, AttributeSet attrs) {
|
public RadialTimePickerView(Context context, AttributeSet attrs) {
|
||||||
this(context, attrs, R.attr.timePickerStyle);
|
this(context, attrs, R.attr.timePickerStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RadialTimePickerView(Context context, AttributeSet attrs, int defStyle) {
|
public RadialTimePickerView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
this(context, attrs, defStyleAttr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RadialTimePickerView(
|
||||||
|
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
|
|
||||||
// Pull disabled alpha from theme.
|
// Pull disabled alpha from theme.
|
||||||
@@ -297,7 +313,7 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
|
|||||||
// process style attributes
|
// process style attributes
|
||||||
final Resources res = getResources();
|
final Resources res = getResources();
|
||||||
final TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TimePicker,
|
final TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TimePicker,
|
||||||
defStyle, 0);
|
defStyleAttr, defStyleRes);
|
||||||
|
|
||||||
mTypeface = Typeface.create("sans-serif", Typeface.NORMAL);
|
mTypeface = Typeface.create("sans-serif", Typeface.NORMAL);
|
||||||
|
|
||||||
@@ -382,6 +398,14 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
|
|||||||
mIs24HourMode = false;
|
mIs24HourMode = false;
|
||||||
mAmOrPm = AM;
|
mAmOrPm = AM;
|
||||||
|
|
||||||
|
// Set up accessibility components.
|
||||||
|
mTouchHelper = new RadialPickerTouchHelper();
|
||||||
|
setAccessibilityDelegate(mTouchHelper);
|
||||||
|
|
||||||
|
if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
|
||||||
|
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
|
||||||
|
}
|
||||||
|
|
||||||
initHoursAndMinutesText();
|
initHoursAndMinutesText();
|
||||||
initData();
|
initData();
|
||||||
|
|
||||||
@@ -406,8 +430,8 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
|
|||||||
final int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
|
final int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
|
||||||
final int currentMinute = calendar.get(Calendar.MINUTE);
|
final int currentMinute = calendar.get(Calendar.MINUTE);
|
||||||
|
|
||||||
setCurrentHour(currentHour);
|
setCurrentHourInternal(currentHour, false, false);
|
||||||
setCurrentMinute(currentMinute);
|
setCurrentMinuteInternal(currentMinute, false);
|
||||||
|
|
||||||
setHapticFeedbackEnabled(true);
|
setHapticFeedbackEnabled(true);
|
||||||
}
|
}
|
||||||
@@ -429,8 +453,9 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
|
|||||||
|
|
||||||
public void initialize(int hour, int minute, boolean is24HourMode) {
|
public void initialize(int hour, int minute, boolean is24HourMode) {
|
||||||
mIs24HourMode = is24HourMode;
|
mIs24HourMode = is24HourMode;
|
||||||
setCurrentHour(hour);
|
|
||||||
setCurrentMinute(minute);
|
setCurrentHourInternal(hour, false, false);
|
||||||
|
setCurrentMinuteInternal(minute, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCurrentItemShowing(int item, boolean animate) {
|
public void setCurrentItemShowing(int item, boolean animate) {
|
||||||
@@ -460,17 +485,39 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
|
|||||||
* @param hour the current hour between 0 and 23 (inclusive)
|
* @param hour the current hour between 0 and 23 (inclusive)
|
||||||
*/
|
*/
|
||||||
public void setCurrentHour(int hour) {
|
public void setCurrentHour(int hour) {
|
||||||
|
setCurrentHourInternal(hour, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current hour.
|
||||||
|
*
|
||||||
|
* @param hour The current hour
|
||||||
|
* @param callback Whether the value listener should be invoked
|
||||||
|
* @param autoAdvance Whether the listener should auto-advance to the next
|
||||||
|
* selection mode, e.g. hour to minutes
|
||||||
|
*/
|
||||||
|
private void setCurrentHourInternal(int hour, boolean callback, boolean autoAdvance) {
|
||||||
final int degrees = (hour % 12) * DEGREES_FOR_ONE_HOUR;
|
final int degrees = (hour % 12) * DEGREES_FOR_ONE_HOUR;
|
||||||
mSelectionDegrees[HOURS] = degrees;
|
mSelectionDegrees[HOURS] = degrees;
|
||||||
mSelectionDegrees[HOURS_INNER] = degrees;
|
mSelectionDegrees[HOURS_INNER] = degrees;
|
||||||
|
|
||||||
// 0 is 12 AM (midnight) and 12 is 12 PM (noon).
|
// 0 is 12 AM (midnight) and 12 is 12 PM (noon).
|
||||||
mAmOrPm = (hour == 0 || (hour % 24) < 12) ? AM : PM;
|
final int amOrPm = (hour == 0 || (hour % 24) < 12) ? AM : PM;
|
||||||
mIsOnInnerCircle = mIs24HourMode && hour >= 1 && hour <= 12;
|
final boolean isOnInnerCircle = mIs24HourMode && hour >= 1 && hour <= 12;
|
||||||
|
if (mAmOrPm != amOrPm || mIsOnInnerCircle != isOnInnerCircle) {
|
||||||
|
mAmOrPm = amOrPm;
|
||||||
|
mIsOnInnerCircle = isOnInnerCircle;
|
||||||
|
|
||||||
initData();
|
initData();
|
||||||
updateLayoutData();
|
updateLayoutData();
|
||||||
|
mTouchHelper.invalidateRoot();
|
||||||
|
}
|
||||||
|
|
||||||
invalidate();
|
invalidate();
|
||||||
|
|
||||||
|
if (callback && mListener != null) {
|
||||||
|
mListener.onValueSelected(HOURS, hour, autoAdvance);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -479,15 +526,19 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
|
|||||||
* @return the current hour between 0 and 23 (inclusive)
|
* @return the current hour between 0 and 23 (inclusive)
|
||||||
*/
|
*/
|
||||||
public int getCurrentHour() {
|
public int getCurrentHour() {
|
||||||
int hour = (mSelectionDegrees[mIsOnInnerCircle ?
|
return getHourForDegrees(
|
||||||
HOURS_INNER : HOURS] / DEGREES_FOR_ONE_HOUR) % 12;
|
mSelectionDegrees[mIsOnInnerCircle ? HOURS_INNER : HOURS], mIsOnInnerCircle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getHourForDegrees(int degrees, boolean innerCircle) {
|
||||||
|
int hour = (degrees / DEGREES_FOR_ONE_HOUR) % 12;
|
||||||
if (mIs24HourMode) {
|
if (mIs24HourMode) {
|
||||||
// Convert the 12-hour value into 24-hour time based on where the
|
// Convert the 12-hour value into 24-hour time based on where the
|
||||||
// selector is positioned.
|
// selector is positioned.
|
||||||
if (mIsOnInnerCircle && hour == 0) {
|
if (innerCircle && hour == 0) {
|
||||||
// Inner circle is 1 through 12.
|
// Inner circle is 1 through 12.
|
||||||
hour = 12;
|
hour = 12;
|
||||||
} else if (!mIsOnInnerCircle && hour != 0) {
|
} else if (!innerCircle && hour != 0) {
|
||||||
// Outer circle is 13 through 23 and 0.
|
// Outer circle is 13 through 23 and 0.
|
||||||
hour += 12;
|
hour += 12;
|
||||||
}
|
}
|
||||||
@@ -497,19 +548,49 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
|
|||||||
return hour;
|
return hour;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getDegreesForHour(int hour) {
|
||||||
|
// Convert to be 0-11.
|
||||||
|
if (mIs24HourMode) {
|
||||||
|
if (hour >= 12) {
|
||||||
|
hour -= 12;
|
||||||
|
}
|
||||||
|
} else if (hour == 12) {
|
||||||
|
hour = 0;
|
||||||
|
}
|
||||||
|
return hour * DEGREES_FOR_ONE_HOUR;
|
||||||
|
}
|
||||||
|
|
||||||
public void setCurrentMinute(int minute) {
|
public void setCurrentMinute(int minute) {
|
||||||
|
setCurrentMinuteInternal(minute, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCurrentMinuteInternal(int minute, boolean callback) {
|
||||||
mSelectionDegrees[MINUTES] = (minute % 60) * DEGREES_FOR_ONE_MINUTE;
|
mSelectionDegrees[MINUTES] = (minute % 60) * DEGREES_FOR_ONE_MINUTE;
|
||||||
|
|
||||||
invalidate();
|
invalidate();
|
||||||
|
|
||||||
|
if (callback && mListener != null) {
|
||||||
|
mListener.onValueSelected(MINUTES, minute, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns minutes in 0-59 range
|
// Returns minutes in 0-59 range
|
||||||
public int getCurrentMinute() {
|
public int getCurrentMinute() {
|
||||||
return (mSelectionDegrees[MINUTES] / DEGREES_FOR_ONE_MINUTE);
|
return getMinuteForDegrees(mSelectionDegrees[MINUTES]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getMinuteForDegrees(int degrees) {
|
||||||
|
return degrees / DEGREES_FOR_ONE_MINUTE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getDegreesForMinute(int minute) {
|
||||||
|
return minute * DEGREES_FOR_ONE_MINUTE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAmOrPm(int val) {
|
public void setAmOrPm(int val) {
|
||||||
mAmOrPm = (val % 2);
|
mAmOrPm = (val % 2);
|
||||||
invalidate();
|
invalidate();
|
||||||
|
mTouchHelper.invalidateRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAmOrPm() {
|
public int getAmOrPm() {
|
||||||
@@ -648,6 +729,8 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
|
|||||||
mSelectionRadius[HOURS] = (int) (mCircleRadius[HOURS] * mSelectionRadiusMultiplier);
|
mSelectionRadius[HOURS] = (int) (mCircleRadius[HOURS] * mSelectionRadiusMultiplier);
|
||||||
mSelectionRadius[HOURS_INNER] = mSelectionRadius[HOURS];
|
mSelectionRadius[HOURS_INNER] = mSelectionRadius[HOURS];
|
||||||
mSelectionRadius[MINUTES] = (int) (mCircleRadius[MINUTES] * mSelectionRadiusMultiplier);
|
mSelectionRadius[MINUTES] = (int) (mCircleRadius[MINUTES] * mSelectionRadiusMultiplier);
|
||||||
|
|
||||||
|
mTouchHelper.invalidateRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -769,20 +852,17 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
|
|||||||
float top = mYCenter - outerRadius;
|
float top = mYCenter - outerRadius;
|
||||||
float right = mXCenter + outerRadius;
|
float right = mXCenter + outerRadius;
|
||||||
float bottom = mYCenter + outerRadius;
|
float bottom = mYCenter + outerRadius;
|
||||||
mRectF = new RectF(left, top, right, bottom);
|
canvas.drawRect(left, top, right, bottom, mPaintDebug);
|
||||||
canvas.drawRect(mRectF, mPaintDebug);
|
|
||||||
|
|
||||||
// Draw outer rectangle for background
|
// Draw outer rectangle for background
|
||||||
left = mXCenter - mCircleRadius[HOURS];
|
left = mXCenter - mCircleRadius[HOURS];
|
||||||
top = mYCenter - mCircleRadius[HOURS];
|
top = mYCenter - mCircleRadius[HOURS];
|
||||||
right = mXCenter + mCircleRadius[HOURS];
|
right = mXCenter + mCircleRadius[HOURS];
|
||||||
bottom = mYCenter + mCircleRadius[HOURS];
|
bottom = mYCenter + mCircleRadius[HOURS];
|
||||||
mRectF.set(left, top, right, bottom);
|
canvas.drawRect(left, top, right, bottom, mPaintDebug);
|
||||||
canvas.drawRect(mRectF, mPaintDebug);
|
|
||||||
|
|
||||||
// Draw outer view rectangle
|
// Draw outer view rectangle
|
||||||
mRectF.set(0, 0, getWidth(), getHeight());
|
canvas.drawRect(0, 0, getWidth(), getHeight(), mPaintDebug);
|
||||||
canvas.drawRect(mRectF, mPaintDebug);
|
|
||||||
|
|
||||||
// Draw selected time
|
// Draw selected time
|
||||||
final String selected = String.format("%02d:%02d", getCurrentHour(), getCurrentMinute());
|
final String selected = String.format("%02d:%02d", getCurrentHour(), getCurrentMinute());
|
||||||
@@ -896,12 +976,14 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Used for animating the hours by changing their radius
|
// Used for animating the hours by changing their radius
|
||||||
|
@SuppressWarnings("unused")
|
||||||
private void setAnimationRadiusMultiplierHours(float animationRadiusMultiplier) {
|
private void setAnimationRadiusMultiplierHours(float animationRadiusMultiplier) {
|
||||||
mAnimationRadiusMultiplier[HOURS] = animationRadiusMultiplier;
|
mAnimationRadiusMultiplier[HOURS] = animationRadiusMultiplier;
|
||||||
mAnimationRadiusMultiplier[HOURS_INNER] = animationRadiusMultiplier;
|
mAnimationRadiusMultiplier[HOURS_INNER] = animationRadiusMultiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used for animating the minutes by changing their radius
|
// Used for animating the minutes by changing their radius
|
||||||
|
@SuppressWarnings("unused")
|
||||||
private void setAnimationRadiusMultiplierMinutes(float animationRadiusMultiplier) {
|
private void setAnimationRadiusMultiplierMinutes(float animationRadiusMultiplier) {
|
||||||
mAnimationRadiusMultiplier[MINUTES] = animationRadiusMultiplier;
|
mAnimationRadiusMultiplier[MINUTES] = animationRadiusMultiplier;
|
||||||
}
|
}
|
||||||
@@ -1094,21 +1176,25 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final float opposite = Math.abs(y - mYCenter);
|
final float opposite = Math.abs(y - mYCenter);
|
||||||
double degrees = Math.toDegrees(Math.asin(opposite / hypotenuse));
|
int degrees = (int) (Math.toDegrees(Math.asin(opposite / hypotenuse)) + 0.5);
|
||||||
|
|
||||||
// Now we have to translate to the correct quadrant.
|
// Now we have to translate to the correct quadrant.
|
||||||
boolean rightSide = (x > mXCenter);
|
final boolean rightSide = (x > mXCenter);
|
||||||
boolean topSide = (y < mYCenter);
|
final boolean topSide = (y < mYCenter);
|
||||||
if (rightSide && topSide) {
|
if (rightSide) {
|
||||||
|
if (topSide) {
|
||||||
degrees = 90 - degrees;
|
degrees = 90 - degrees;
|
||||||
} else if (rightSide && !topSide) {
|
} else {
|
||||||
degrees = 90 + degrees;
|
degrees = 90 + degrees;
|
||||||
} else if (!rightSide && !topSide) {
|
|
||||||
degrees = 270 - degrees;
|
|
||||||
} else if (!rightSide && topSide) {
|
|
||||||
degrees = 270 + degrees;
|
|
||||||
}
|
}
|
||||||
return (int) degrees;
|
} else {
|
||||||
|
if (topSide) {
|
||||||
|
degrees = 270 + degrees;
|
||||||
|
} else {
|
||||||
|
degrees = 270 - degrees;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return degrees;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1176,109 +1262,277 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Necessary for accessibility, to ensure we support "scrolling" forward and backward
|
|
||||||
* in the circle.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
|
public boolean dispatchHoverEvent(MotionEvent event) {
|
||||||
super.onInitializeAccessibilityNodeInfo(info);
|
// First right-of-refusal goes the touch exploration helper.
|
||||||
|
if (mTouchHelper.dispatchHoverEvent(event)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.dispatchHoverEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInputEnabled(boolean inputEnabled) {
|
||||||
|
mInputEnabled = inputEnabled;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RadialPickerTouchHelper extends ExploreByTouchHelper {
|
||||||
|
private final Rect mTempRect = new Rect();
|
||||||
|
|
||||||
|
private final int TYPE_HOUR = 1;
|
||||||
|
private final int TYPE_MINUTE = 2;
|
||||||
|
|
||||||
|
private final int SHIFT_TYPE = 0;
|
||||||
|
private final int MASK_TYPE = 0xF;
|
||||||
|
|
||||||
|
private final int SHIFT_VALUE = 8;
|
||||||
|
private final int MASK_VALUE = 0xFF;
|
||||||
|
|
||||||
|
/** Increment in which virtual views are exposed for minutes. */
|
||||||
|
private final int MINUTE_INCREMENT = 5;
|
||||||
|
|
||||||
|
public RadialPickerTouchHelper() {
|
||||||
|
super(RadialTimePickerView.this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
|
||||||
|
super.onInitializeAccessibilityNodeInfo(host, info);
|
||||||
|
|
||||||
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
|
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
|
||||||
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
|
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Announce the currently-selected time when launched.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
|
public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
|
||||||
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
|
if (super.performAccessibilityAction(host, action, arguments)) {
|
||||||
// Clear the event's current text so that only the current time will be spoken.
|
|
||||||
event.getText().clear();
|
|
||||||
Time time = new Time();
|
|
||||||
time.hour = getCurrentHour();
|
|
||||||
time.minute = getCurrentMinute();
|
|
||||||
long millis = time.normalize(true);
|
|
||||||
int flags = DateUtils.FORMAT_SHOW_TIME;
|
|
||||||
if (mIs24HourMode) {
|
|
||||||
flags |= DateUtils.FORMAT_24HOUR;
|
|
||||||
}
|
|
||||||
String timeString = DateUtils.formatDateTime(getContext(), millis, flags);
|
|
||||||
event.getText().add(timeString);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return super.dispatchPopulateAccessibilityEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When scroll forward/backward events are received, jump the time to the higher/lower
|
|
||||||
* discrete, visible value on the circle.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean performAccessibilityAction(int action, Bundle arguments) {
|
|
||||||
if (super.performAccessibilityAction(action, arguments)) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int changeMultiplier = 0;
|
switch (action) {
|
||||||
if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) {
|
case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
|
||||||
changeMultiplier = 1;
|
adjustPicker(1);
|
||||||
} else if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
|
return true;
|
||||||
changeMultiplier = -1;
|
case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
|
||||||
}
|
adjustPicker(-1);
|
||||||
if (changeMultiplier != 0) {
|
|
||||||
int value;
|
|
||||||
final int stepSize;
|
|
||||||
if (mShowHours) {
|
|
||||||
stepSize = DEGREES_FOR_ONE_HOUR;
|
|
||||||
value = getCurrentHour() % 12;
|
|
||||||
} else {
|
|
||||||
stepSize = DEGREES_FOR_ONE_MINUTE;
|
|
||||||
value = getCurrentMinute();
|
|
||||||
}
|
|
||||||
|
|
||||||
int degrees = value * stepSize;
|
|
||||||
degrees = snapOnly30s(degrees, changeMultiplier);
|
|
||||||
value = degrees / stepSize;
|
|
||||||
final int maxValue;
|
|
||||||
int minValue = 0;
|
|
||||||
if (mShowHours) {
|
|
||||||
if (mIs24HourMode) {
|
|
||||||
maxValue = 23;
|
|
||||||
} else {
|
|
||||||
maxValue = 12;
|
|
||||||
minValue = 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
maxValue = 55;
|
|
||||||
}
|
|
||||||
if (value > maxValue) {
|
|
||||||
// If we scrolled forward past the highest number, wrap around to the lowest.
|
|
||||||
value = minValue;
|
|
||||||
} else if (value < minValue) {
|
|
||||||
// If we scrolled backward past the lowest number, wrap around to the highest.
|
|
||||||
value = maxValue;
|
|
||||||
}
|
|
||||||
if (mShowHours) {
|
|
||||||
setCurrentHour(value);
|
|
||||||
if (mListener != null) {
|
|
||||||
mListener.onValueSelected(HOURS, value, false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setCurrentMinute(value);
|
|
||||||
if (mListener != null) {
|
|
||||||
mListener.onValueSelected(MINUTES, value, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setInputEnabled(boolean inputEnabled) {
|
private void adjustPicker(int step) {
|
||||||
mInputEnabled = inputEnabled;
|
final int stepSize;
|
||||||
invalidate();
|
final int initialValue;
|
||||||
|
final int maxValue;
|
||||||
|
final int minValue;
|
||||||
|
if (mShowHours) {
|
||||||
|
stepSize = DEGREES_FOR_ONE_HOUR;
|
||||||
|
initialValue = getCurrentHour() % 12;
|
||||||
|
|
||||||
|
if (mIs24HourMode) {
|
||||||
|
maxValue = 23;
|
||||||
|
minValue = 0;
|
||||||
|
} else {
|
||||||
|
maxValue = 12;
|
||||||
|
minValue = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stepSize = DEGREES_FOR_ONE_MINUTE;
|
||||||
|
initialValue = getCurrentMinute();
|
||||||
|
|
||||||
|
maxValue = 55;
|
||||||
|
minValue = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int steppedValue = snapOnly30s(initialValue * stepSize, step) / stepSize;
|
||||||
|
final int clampedValue = MathUtils.constrain(steppedValue, minValue, maxValue);
|
||||||
|
if (mShowHours) {
|
||||||
|
setCurrentHour(clampedValue);
|
||||||
|
} else {
|
||||||
|
setCurrentMinute(clampedValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getVirtualViewAt(float x, float y) {
|
||||||
|
final int id;
|
||||||
|
final int degrees = getDegreesFromXY(x, y);
|
||||||
|
if (degrees != -1) {
|
||||||
|
final int snapDegrees = snapOnly30s(degrees, 0) % 360;
|
||||||
|
if (mShowHours) {
|
||||||
|
final int hour = getHourForDegrees(snapDegrees, mIsOnInnerCircle);
|
||||||
|
id = makeId(TYPE_HOUR, hour);
|
||||||
|
} else {
|
||||||
|
final int current = getCurrentMinute();
|
||||||
|
final int touched = getMinuteForDegrees(degrees);
|
||||||
|
final int snapped = getMinuteForDegrees(snapDegrees);
|
||||||
|
|
||||||
|
// If the touched minute is closer to the current minute
|
||||||
|
// than it is to the snapped minute, return current.
|
||||||
|
final int minute;
|
||||||
|
if (Math.abs(current - touched) < Math.abs(snapped - touched)) {
|
||||||
|
minute = current;
|
||||||
|
} else {
|
||||||
|
minute = snapped;
|
||||||
|
}
|
||||||
|
id = makeId(TYPE_MINUTE, minute);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
id = INVALID_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void getVisibleVirtualViews(IntArray virtualViewIds) {
|
||||||
|
if (mShowHours) {
|
||||||
|
final int min = mIs24HourMode ? 0 : 1;
|
||||||
|
final int max = mIs24HourMode ? 23 : 12;
|
||||||
|
for (int i = min; i <= max ; i++) {
|
||||||
|
virtualViewIds.add(makeId(TYPE_HOUR, i));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final int current = getCurrentMinute();
|
||||||
|
for (int i = 0; i < 60; i += MINUTE_INCREMENT) {
|
||||||
|
virtualViewIds.add(makeId(TYPE_MINUTE, i));
|
||||||
|
|
||||||
|
// If the current minute falls between two increments,
|
||||||
|
// insert an extra node for it.
|
||||||
|
if (current > i && current < i + MINUTE_INCREMENT) {
|
||||||
|
virtualViewIds.add(makeId(TYPE_MINUTE, current));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
|
||||||
|
event.setClassName(getClass().getName());
|
||||||
|
|
||||||
|
final int type = getTypeFromId(virtualViewId);
|
||||||
|
final int value = getValueFromId(virtualViewId);
|
||||||
|
final CharSequence description = getVirtualViewDescription(type, value);
|
||||||
|
event.setContentDescription(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) {
|
||||||
|
node.setClassName(getClass().getName());
|
||||||
|
node.addAction(AccessibilityAction.ACTION_CLICK);
|
||||||
|
|
||||||
|
final int type = getTypeFromId(virtualViewId);
|
||||||
|
final int value = getValueFromId(virtualViewId);
|
||||||
|
final CharSequence description = getVirtualViewDescription(type, value);
|
||||||
|
node.setContentDescription(description);
|
||||||
|
|
||||||
|
getBoundsForVirtualView(virtualViewId, mTempRect);
|
||||||
|
node.setBoundsInParent(mTempRect);
|
||||||
|
|
||||||
|
final boolean selected = isVirtualViewSelected(type, value);
|
||||||
|
node.setSelected(selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
|
||||||
|
Bundle arguments) {
|
||||||
|
if (action == AccessibilityNodeInfo.ACTION_CLICK) {
|
||||||
|
final int type = getTypeFromId(virtualViewId);
|
||||||
|
final int value = getValueFromId(virtualViewId);
|
||||||
|
if (type == TYPE_HOUR) {
|
||||||
|
final int hour = mIs24HourMode ? value : hour12To24(value, mAmOrPm);
|
||||||
|
setCurrentHour(hour);
|
||||||
|
return true;
|
||||||
|
} else if (type == TYPE_MINUTE) {
|
||||||
|
setCurrentMinute(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int hour12To24(int hour12, int amOrPm) {
|
||||||
|
int hour24 = hour12;
|
||||||
|
if (hour12 == 12) {
|
||||||
|
if (amOrPm == AM) {
|
||||||
|
hour24 = 0;
|
||||||
|
}
|
||||||
|
} else if (amOrPm == PM) {
|
||||||
|
hour24 += 12;
|
||||||
|
}
|
||||||
|
return hour24;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getBoundsForVirtualView(int virtualViewId, Rect bounds) {
|
||||||
|
final float radius;
|
||||||
|
final int type = getTypeFromId(virtualViewId);
|
||||||
|
final int value = getValueFromId(virtualViewId);
|
||||||
|
final float centerRadius;
|
||||||
|
final float degrees;
|
||||||
|
if (type == TYPE_HOUR) {
|
||||||
|
final boolean innerCircle = mIs24HourMode && value > 0 && value <= 12;
|
||||||
|
if (innerCircle) {
|
||||||
|
centerRadius = mCircleRadius[HOURS_INNER] * mNumbersRadiusMultiplier[HOURS_INNER];
|
||||||
|
radius = mSelectionRadius[HOURS_INNER];
|
||||||
|
} else {
|
||||||
|
centerRadius = mCircleRadius[HOURS] * mNumbersRadiusMultiplier[HOURS];
|
||||||
|
radius = mSelectionRadius[HOURS];
|
||||||
|
}
|
||||||
|
|
||||||
|
degrees = getDegreesForHour(value);
|
||||||
|
} else if (type == TYPE_MINUTE) {
|
||||||
|
centerRadius = mCircleRadius[MINUTES] * mNumbersRadiusMultiplier[MINUTES];
|
||||||
|
degrees = getDegreesForMinute(value);
|
||||||
|
radius = mSelectionRadius[MINUTES];
|
||||||
|
} else {
|
||||||
|
// This should never happen.
|
||||||
|
centerRadius = 0;
|
||||||
|
degrees = 0;
|
||||||
|
radius = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
final double radians = Math.toRadians(degrees);
|
||||||
|
final float xCenter = mXCenter + centerRadius * (float) Math.sin(radians);
|
||||||
|
final float yCenter = mYCenter - centerRadius * (float) Math.cos(radians);
|
||||||
|
|
||||||
|
bounds.set((int) (xCenter - radius), (int) (yCenter - radius),
|
||||||
|
(int) (xCenter + radius), (int) (yCenter + radius));
|
||||||
|
}
|
||||||
|
|
||||||
|
private CharSequence getVirtualViewDescription(int type, int value) {
|
||||||
|
final CharSequence description;
|
||||||
|
if (type == TYPE_HOUR || type == TYPE_MINUTE) {
|
||||||
|
description = Integer.toString(value);
|
||||||
|
} else {
|
||||||
|
description = null;
|
||||||
|
}
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isVirtualViewSelected(int type, int value) {
|
||||||
|
final boolean selected;
|
||||||
|
if (type == TYPE_HOUR) {
|
||||||
|
selected = getCurrentHour() == value;
|
||||||
|
} else if (type == TYPE_MINUTE) {
|
||||||
|
selected = getCurrentMinute() == value;
|
||||||
|
} else {
|
||||||
|
selected = false;
|
||||||
|
}
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int makeId(int type, int value) {
|
||||||
|
return type << SHIFT_TYPE | value << SHIFT_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getTypeFromId(int id) {
|
||||||
|
return id >>> SHIFT_TYPE & MASK_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getValueFromId(int id) {
|
||||||
|
return id >>> SHIFT_VALUE & MASK_VALUE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class IntHolder {
|
private static class IntHolder {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import android.text.format.DateFormat;
|
|||||||
import android.text.format.DateUtils;
|
import android.text.format.DateUtils;
|
||||||
import android.text.format.Time;
|
import android.text.format.Time;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.util.IntArray;
|
||||||
import android.util.MathUtils;
|
import android.util.MathUtils;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -610,7 +611,7 @@ class SimpleMonthView extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
|
protected void getVisibleVirtualViews(IntArray virtualViewIds) {
|
||||||
for (int day = 1; day <= mNumCells; day++) {
|
for (int day = 1; day <= mNumCells; day++) {
|
||||||
virtualViewIds.add(day);
|
virtualViewIds.add(day);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -611,15 +611,12 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
|
|||||||
if (mAllowAutoAdvance && autoAdvance) {
|
if (mAllowAutoAdvance && autoAdvance) {
|
||||||
updateHeaderHour(newValue, false);
|
updateHeaderHour(newValue, false);
|
||||||
setCurrentItemShowing(MINUTE_INDEX, true, false);
|
setCurrentItemShowing(MINUTE_INDEX, true, false);
|
||||||
mRadialTimePickerView.announceForAccessibility(newValue + ". " + mSelectMinutes);
|
mDelegator.announceForAccessibility(newValue + ". " + mSelectMinutes);
|
||||||
} else {
|
} else {
|
||||||
updateHeaderHour(newValue, true);
|
updateHeaderHour(newValue, true);
|
||||||
mRadialTimePickerView.setContentDescription(
|
|
||||||
mHourPickerDescription + ": " + newValue);
|
|
||||||
}
|
}
|
||||||
} else if (pickerIndex == MINUTE_INDEX){
|
} else if (pickerIndex == MINUTE_INDEX){
|
||||||
updateHeaderMinute(newValue, true);
|
updateHeaderMinute(newValue, true);
|
||||||
mRadialTimePickerView.setContentDescription(mMinutePickerDescription + ": " + newValue);
|
|
||||||
} else if (pickerIndex == AMPM_INDEX) {
|
} else if (pickerIndex == AMPM_INDEX) {
|
||||||
updateAmPmLabelStates(newValue);
|
updateAmPmLabelStates(newValue);
|
||||||
} else if (pickerIndex == ENABLE_PICKER_INDEX) {
|
} else if (pickerIndex == ENABLE_PICKER_INDEX) {
|
||||||
@@ -744,19 +741,12 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
|
|||||||
mRadialTimePickerView.setCurrentItemShowing(index, animateCircle);
|
mRadialTimePickerView.setCurrentItemShowing(index, animateCircle);
|
||||||
|
|
||||||
if (index == HOUR_INDEX) {
|
if (index == HOUR_INDEX) {
|
||||||
int hours = mRadialTimePickerView.getCurrentHour();
|
|
||||||
if (!mIs24HourView) {
|
|
||||||
hours = hours % 12;
|
|
||||||
}
|
|
||||||
mRadialTimePickerView.setContentDescription(mHourPickerDescription + ": " + hours);
|
|
||||||
if (announce) {
|
if (announce) {
|
||||||
mRadialTimePickerView.announceForAccessibility(mSelectHours);
|
mDelegator.announceForAccessibility(mSelectHours);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int minutes = mRadialTimePickerView.getCurrentMinute();
|
|
||||||
mRadialTimePickerView.setContentDescription(mMinutePickerDescription + ": " + minutes);
|
|
||||||
if (announce) {
|
if (announce) {
|
||||||
mRadialTimePickerView.announceForAccessibility(mSelectMinutes);
|
mDelegator.announceForAccessibility(mSelectMinutes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -789,7 +779,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
|
|||||||
} else {
|
} else {
|
||||||
deletedKeyStr = String.format("%d", getValFromKeyCode(deleted));
|
deletedKeyStr = String.format("%d", getValFromKeyCode(deleted));
|
||||||
}
|
}
|
||||||
mRadialTimePickerView.announceForAccessibility(
|
mDelegator.announceForAccessibility(
|
||||||
String.format(mDeletedKeyFormat, deletedKeyStr));
|
String.format(mDeletedKeyFormat, deletedKeyStr));
|
||||||
updateDisplay(true);
|
updateDisplay(true);
|
||||||
}
|
}
|
||||||
@@ -851,7 +841,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
|
|||||||
}
|
}
|
||||||
|
|
||||||
int val = getValFromKeyCode(keyCode);
|
int val = getValFromKeyCode(keyCode);
|
||||||
mRadialTimePickerView.announceForAccessibility(String.format("%d", val));
|
mDelegator.announceForAccessibility(String.format("%d", val));
|
||||||
// Automatically fill in 0's if AM or PM was legally entered.
|
// Automatically fill in 0's if AM or PM was legally entered.
|
||||||
if (isTypedTimeFullyLegal()) {
|
if (isTypedTimeFullyLegal()) {
|
||||||
if (!mIs24HourView && mTypedTimes.size() <= 3) {
|
if (!mIs24HourView && mTypedTimes.size() <= 3) {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package com.android.internal.widget;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.IntArray;
|
||||||
import android.view.accessibility.*;
|
import android.view.accessibility.*;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -26,11 +27,9 @@ import android.view.ViewParent;
|
|||||||
import android.view.accessibility.AccessibilityEvent;
|
import android.view.accessibility.AccessibilityEvent;
|
||||||
import android.view.accessibility.AccessibilityManager;
|
import android.view.accessibility.AccessibilityManager;
|
||||||
import android.view.accessibility.AccessibilityNodeInfo;
|
import android.view.accessibility.AccessibilityNodeInfo;
|
||||||
|
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
|
||||||
import android.view.accessibility.AccessibilityNodeProvider;
|
import android.view.accessibility.AccessibilityNodeProvider;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ExploreByTouchHelper is a utility class for implementing accessibility
|
* ExploreByTouchHelper is a utility class for implementing accessibility
|
||||||
* support in custom {@link android.view.View}s that represent a collection of View-like
|
* support in custom {@link android.view.View}s that represent a collection of View-like
|
||||||
@@ -58,14 +57,16 @@ public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate {
|
|||||||
private static final Rect INVALID_PARENT_BOUNDS = new Rect(
|
private static final Rect INVALID_PARENT_BOUNDS = new Rect(
|
||||||
Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE);
|
Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE);
|
||||||
|
|
||||||
// Temporary, reusable data structures.
|
// Lazily-created temporary data structures used when creating nodes.
|
||||||
private final Rect mTempScreenRect = new Rect();
|
private Rect mTempScreenRect;
|
||||||
private final Rect mTempParentRect = new Rect();
|
private Rect mTempParentRect;
|
||||||
private final Rect mTempVisibleRect = new Rect();
|
private int[] mTempGlobalRect;
|
||||||
private final int[] mTempGlobalRect = new int[2];
|
|
||||||
|
|
||||||
/** View's context **/
|
/** Lazily-created temporary data structure used to compute visibility. */
|
||||||
private Context mContext;
|
private Rect mTempVisibleRect;
|
||||||
|
|
||||||
|
/** Lazily-created temporary data structure used to obtain child IDs. */
|
||||||
|
private IntArray mTempArray;
|
||||||
|
|
||||||
/** System accessibility manager, used to check state and send events. */
|
/** System accessibility manager, used to check state and send events. */
|
||||||
private final AccessibilityManager mManager;
|
private final AccessibilityManager mManager;
|
||||||
@@ -73,6 +74,9 @@ public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate {
|
|||||||
/** View whose internal structure is exposed through this helper. */
|
/** View whose internal structure is exposed through this helper. */
|
||||||
private final View mView;
|
private final View mView;
|
||||||
|
|
||||||
|
/** Context of the host view. **/
|
||||||
|
private final Context mContext;
|
||||||
|
|
||||||
/** Node provider that handles creating nodes and performing actions. */
|
/** Node provider that handles creating nodes and performing actions. */
|
||||||
private ExploreByTouchNodeProvider mNodeProvider;
|
private ExploreByTouchNodeProvider mNodeProvider;
|
||||||
|
|
||||||
@@ -332,11 +336,17 @@ public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate {
|
|||||||
onInitializeAccessibilityNodeInfo(mView, node);
|
onInitializeAccessibilityNodeInfo(mView, node);
|
||||||
|
|
||||||
// Add the virtual descendants.
|
// Add the virtual descendants.
|
||||||
final LinkedList<Integer> virtualViewIds = new LinkedList<Integer>();
|
if (mTempArray == null) {
|
||||||
|
mTempArray = new IntArray();
|
||||||
|
} else {
|
||||||
|
mTempArray.clear();
|
||||||
|
}
|
||||||
|
final IntArray virtualViewIds = mTempArray;
|
||||||
getVisibleVirtualViews(virtualViewIds);
|
getVisibleVirtualViews(virtualViewIds);
|
||||||
|
|
||||||
for (Integer childVirtualViewId : virtualViewIds) {
|
final int N = virtualViewIds.size();
|
||||||
node.addChild(mView, childVirtualViewId);
|
for (int i = 0; i < N; i++) {
|
||||||
|
node.addChild(mView, virtualViewIds.get(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
@@ -371,6 +381,11 @@ public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate {
|
|||||||
* @return An {@link AccessibilityNodeInfo} for the specified item.
|
* @return An {@link AccessibilityNodeInfo} for the specified item.
|
||||||
*/
|
*/
|
||||||
private AccessibilityNodeInfo createNodeForChild(int virtualViewId) {
|
private AccessibilityNodeInfo createNodeForChild(int virtualViewId) {
|
||||||
|
ensureTempRects();
|
||||||
|
final Rect tempParentRect = mTempParentRect;
|
||||||
|
final int[] tempGlobalRect = mTempGlobalRect;
|
||||||
|
final Rect tempScreenRect = mTempScreenRect;
|
||||||
|
|
||||||
final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
|
final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
|
||||||
|
|
||||||
// Ensure the client has good defaults.
|
// Ensure the client has good defaults.
|
||||||
@@ -387,8 +402,8 @@ public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate {
|
|||||||
+ "populateNodeForVirtualViewId()");
|
+ "populateNodeForVirtualViewId()");
|
||||||
}
|
}
|
||||||
|
|
||||||
node.getBoundsInParent(mTempParentRect);
|
node.getBoundsInParent(tempParentRect);
|
||||||
if (mTempParentRect.equals(INVALID_PARENT_BOUNDS)) {
|
if (tempParentRect.equals(INVALID_PARENT_BOUNDS)) {
|
||||||
throw new RuntimeException("Callbacks must set parent bounds in "
|
throw new RuntimeException("Callbacks must set parent bounds in "
|
||||||
+ "populateNodeForVirtualViewId()");
|
+ "populateNodeForVirtualViewId()");
|
||||||
}
|
}
|
||||||
@@ -411,29 +426,35 @@ public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate {
|
|||||||
// Manage internal accessibility focus state.
|
// Manage internal accessibility focus state.
|
||||||
if (mFocusedVirtualViewId == virtualViewId) {
|
if (mFocusedVirtualViewId == virtualViewId) {
|
||||||
node.setAccessibilityFocused(true);
|
node.setAccessibilityFocused(true);
|
||||||
node.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
|
node.addAction(AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
|
||||||
} else {
|
} else {
|
||||||
node.setAccessibilityFocused(false);
|
node.setAccessibilityFocused(false);
|
||||||
node.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
|
node.addAction(AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the visibility based on the parent bound.
|
// Set the visibility based on the parent bound.
|
||||||
if (intersectVisibleToUser(mTempParentRect)) {
|
if (intersectVisibleToUser(tempParentRect)) {
|
||||||
node.setVisibleToUser(true);
|
node.setVisibleToUser(true);
|
||||||
node.setBoundsInParent(mTempParentRect);
|
node.setBoundsInParent(tempParentRect);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate screen-relative bound.
|
// Calculate screen-relative bound.
|
||||||
mView.getLocationOnScreen(mTempGlobalRect);
|
mView.getLocationOnScreen(tempGlobalRect);
|
||||||
final int offsetX = mTempGlobalRect[0];
|
final int offsetX = tempGlobalRect[0];
|
||||||
final int offsetY = mTempGlobalRect[1];
|
final int offsetY = tempGlobalRect[1];
|
||||||
mTempScreenRect.set(mTempParentRect);
|
tempScreenRect.set(tempParentRect);
|
||||||
mTempScreenRect.offset(offsetX, offsetY);
|
tempScreenRect.offset(offsetX, offsetY);
|
||||||
node.setBoundsInScreen(mTempScreenRect);
|
node.setBoundsInScreen(tempScreenRect);
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ensureTempRects() {
|
||||||
|
mTempGlobalRect = new int[2];
|
||||||
|
mTempParentRect = new Rect();
|
||||||
|
mTempScreenRect = new Rect();
|
||||||
|
}
|
||||||
|
|
||||||
private boolean performAction(int virtualViewId, int action, Bundle arguments) {
|
private boolean performAction(int virtualViewId, int action, Bundle arguments) {
|
||||||
switch (virtualViewId) {
|
switch (virtualViewId) {
|
||||||
case View.NO_ID:
|
case View.NO_ID:
|
||||||
@@ -451,13 +472,13 @@ public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate {
|
|||||||
switch (action) {
|
switch (action) {
|
||||||
case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
|
case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
|
||||||
case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
|
case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
|
||||||
return manageFocusForChild(virtualViewId, action, arguments);
|
return manageFocusForChild(virtualViewId, action);
|
||||||
default:
|
default:
|
||||||
return onPerformActionForVirtualView(virtualViewId, action, arguments);
|
return onPerformActionForVirtualView(virtualViewId, action, arguments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean manageFocusForChild(int virtualViewId, int action, Bundle arguments) {
|
private boolean manageFocusForChild(int virtualViewId, int action) {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
|
case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
|
||||||
return requestAccessibilityFocus(virtualViewId);
|
return requestAccessibilityFocus(virtualViewId);
|
||||||
@@ -503,12 +524,16 @@ public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If no portion of the parent is visible, this view is not visible.
|
// If no portion of the parent is visible, this view is not visible.
|
||||||
if (!mView.getLocalVisibleRect(mTempVisibleRect)) {
|
if (mTempVisibleRect == null) {
|
||||||
|
mTempVisibleRect = new Rect();
|
||||||
|
}
|
||||||
|
final Rect tempVisibleRect = mTempVisibleRect;
|
||||||
|
if (!mView.getLocalVisibleRect(tempVisibleRect)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the view intersects the visible portion of the parent.
|
// Check if the view intersects the visible portion of the parent.
|
||||||
return localRect.intersect(mTempVisibleRect);
|
return localRect.intersect(tempVisibleRect);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -588,7 +613,7 @@ public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate {
|
|||||||
*
|
*
|
||||||
* @param virtualViewIds The list to populate with visible items
|
* @param virtualViewIds The list to populate with visible items
|
||||||
*/
|
*/
|
||||||
protected abstract void getVisibleVirtualViews(List<Integer> virtualViewIds);
|
protected abstract void getVisibleVirtualViews(IntArray virtualViewIds);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populates an {@link AccessibilityEvent} with information about the
|
* Populates an {@link AccessibilityEvent} with information about the
|
||||||
|
|||||||
@@ -56,7 +56,9 @@
|
|||||||
android:paddingStart="@dimen/timepicker_ampm_horizontal_padding"
|
android:paddingStart="@dimen/timepicker_ampm_horizontal_padding"
|
||||||
android:paddingTop="@dimen/timepicker_ampm_vertical_padding"
|
android:paddingTop="@dimen/timepicker_ampm_vertical_padding"
|
||||||
android:paddingEnd="@dimen/timepicker_ampm_horizontal_padding"
|
android:paddingEnd="@dimen/timepicker_ampm_horizontal_padding"
|
||||||
android:paddingBottom="@dimen/timepicker_am_bottom_padding" />
|
android:paddingBottom="@dimen/timepicker_am_bottom_padding"
|
||||||
|
android:lines="1"
|
||||||
|
android:ellipsize="none" />
|
||||||
<CheckedTextView
|
<CheckedTextView
|
||||||
android:id="@+id/pm_label"
|
android:id="@+id/pm_label"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -64,7 +66,9 @@
|
|||||||
android:paddingStart="@dimen/timepicker_ampm_horizontal_padding"
|
android:paddingStart="@dimen/timepicker_ampm_horizontal_padding"
|
||||||
android:paddingTop="@dimen/timepicker_pm_top_padding"
|
android:paddingTop="@dimen/timepicker_pm_top_padding"
|
||||||
android:paddingEnd="@dimen/timepicker_ampm_horizontal_padding"
|
android:paddingEnd="@dimen/timepicker_ampm_horizontal_padding"
|
||||||
android:paddingBottom="@dimen/timepicker_ampm_vertical_padding" />
|
android:paddingBottom="@dimen/timepicker_ampm_vertical_padding"
|
||||||
|
android:lines="1"
|
||||||
|
android:ellipsize="none" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|||||||
Reference in New Issue
Block a user