Loading core/java/android/widget/RadialTimePickerView.java +95 −92 Original line number Original line Diff line number Diff line Loading @@ -79,8 +79,10 @@ public class RadialTimePickerView extends View { // Transparent alpha level // Transparent alpha level private static final int ALPHA_TRANSPARENT = 0; private static final int ALPHA_TRANSPARENT = 0; private static final int DEGREES_FOR_ONE_HOUR = 30; private static final int HOURS_IN_DAY = 24; private static final int DEGREES_FOR_ONE_MINUTE = 6; private static final int MINUTES_IN_HOUR = 60; private static final int DEGREES_FOR_ONE_HOUR = 360 / HOURS_IN_DAY; private static final int DEGREES_FOR_ONE_MINUTE = 360 / MINUTES_IN_HOUR; private static final int[] HOURS_NUMBERS = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; private static final int[] HOURS_NUMBERS = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; private static final int[] HOURS_NUMBERS_24 = {0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; private static final int[] HOURS_NUMBERS_24 = {0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; Loading Loading @@ -140,8 +142,7 @@ public class RadialTimePickerView extends View { private final float[] mInnerTextX = new float[12]; private final float[] mInnerTextX = new float[12]; private final float[] mInnerTextY = new float[12]; private final float[] mInnerTextY = new float[12]; private final int[] mLineLength = new int[3]; private final int[] mSelectionDegrees = new int[2]; private final int[] mSelectionDegrees = new int[3]; private final ArrayList<Animator> mHoursToMinutesAnims = new ArrayList<>(); private final ArrayList<Animator> mHoursToMinutesAnims = new ArrayList<>(); private final ArrayList<Animator> mMinuteToHoursAnims = new ArrayList<>(); private final ArrayList<Animator> mMinuteToHoursAnims = new ArrayList<>(); Loading @@ -168,13 +169,13 @@ public class RadialTimePickerView extends View { private int mYCenter; private int mYCenter; private int mCircleRadius; private int mCircleRadius; private int mMinHypotenuseForInnerNumber; private int mMinDistForInnerNumber; private int mMaxHypotenuseForOuterNumber; private int mMaxDistForOuterNumber; private int mHalfwayHypotenusePoint; private int mHalfwayDist; private String[] mOuterTextHours; private String[] mOuterTextHours; private String[] mInnerTextHours; private String[] mInnerTextHours; private String[] mOuterTextMinutes; private String[] mMinutesText; private AnimatorSet mTransition; private AnimatorSet mTransition; private int mAmOrPm; private int mAmOrPm; Loading Loading @@ -462,11 +463,10 @@ public class RadialTimePickerView extends View { private void setCurrentHourInternal(int hour, boolean callback, boolean autoAdvance) { 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; // 0 is 12 AM (midnight) and 12 is 12 PM (noon). // 0 is 12 AM (midnight) and 12 is 12 PM (noon). final int amOrPm = (hour == 0 || (hour % 24) < 12) ? AM : PM; final int amOrPm = (hour == 0 || (hour % 24) < 12) ? AM : PM; final boolean isOnInnerCircle = mIs24HourMode && hour >= 1 && hour <= 12; final boolean isOnInnerCircle = getInnerCircleForHour(hour); if (mAmOrPm != amOrPm || mIsOnInnerCircle != isOnInnerCircle) { if (mAmOrPm != amOrPm || mIsOnInnerCircle != isOnInnerCircle) { mAmOrPm = amOrPm; mAmOrPm = amOrPm; mIsOnInnerCircle = isOnInnerCircle; mIsOnInnerCircle = isOnInnerCircle; Loading @@ -488,8 +488,7 @@ public class RadialTimePickerView extends View { * @return the current hour between 0 and 23 (inclusive) * @return the current hour between 0 and 23 (inclusive) */ */ public int getCurrentHour() { public int getCurrentHour() { return getHourForDegrees( return getHourForDegrees(mSelectionDegrees[HOURS], mIsOnInnerCircle); mSelectionDegrees[mIsOnInnerCircle ? HOURS_INNER : HOURS], mIsOnInnerCircle); } } private int getHourForDegrees(int degrees, boolean innerCircle) { private int getHourForDegrees(int degrees, boolean innerCircle) { Loading @@ -497,11 +496,11 @@ public class RadialTimePickerView extends View { 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 (innerCircle && hour == 0) { if (!innerCircle && hour == 0) { // Inner circle is 1 through 12. // Outer circle is 1 through 12. hour = 12; hour = 12; } else if (!innerCircle && hour != 0) { } else if (innerCircle && hour != 0) { // Outer circle is 13 through 23 and 0. // Inner circle is 13 through 23 and 0. hour += 12; hour += 12; } } } else if (mAmOrPm == PM) { } else if (mAmOrPm == PM) { Loading @@ -510,6 +509,9 @@ public class RadialTimePickerView extends View { return hour; return hour; } } /** * @param hour the hour in 24-hour time or 12-hour time */ private int getDegreesForHour(int hour) { private int getDegreesForHour(int hour) { // Convert to be 0-11. // Convert to be 0-11. if (mIs24HourMode) { if (mIs24HourMode) { Loading @@ -522,12 +524,19 @@ public class RadialTimePickerView extends View { return hour * DEGREES_FOR_ONE_HOUR; return hour * DEGREES_FOR_ONE_HOUR; } } /** * @param hour the hour in 24-hour time or 12-hour time */ private boolean getInnerCircleForHour(int hour) { return mIs24HourMode && (hour == 0 || hour > 12); } public void setCurrentMinute(int minute) { public void setCurrentMinute(int minute) { setCurrentMinuteInternal(minute, true); setCurrentMinuteInternal(minute, true); } } private void setCurrentMinuteInternal(int minute, boolean callback) { private void setCurrentMinuteInternal(int minute, boolean callback) { mSelectionDegrees[MINUTES] = (minute % 60) * DEGREES_FOR_ONE_MINUTE; mSelectionDegrees[MINUTES] = (minute % MINUTES_IN_HOUR) * DEGREES_FOR_ONE_MINUTE; invalidate(); invalidate(); Loading Loading @@ -572,6 +581,7 @@ public class RadialTimePickerView extends View { initData(); initData(); invalidate(); invalidate(); mTouchHelper.invalidateRoot(); } } public void showMinutes(boolean animate) { public void showMinutes(boolean animate) { Loading @@ -587,6 +597,7 @@ public class RadialTimePickerView extends View { initData(); initData(); invalidate(); invalidate(); mTouchHelper.invalidateRoot(); } } private void initHoursAndMinutesText() { private void initHoursAndMinutesText() { Loading @@ -608,7 +619,7 @@ public class RadialTimePickerView extends View { mInnerTextHours = mHours12Texts; mInnerTextHours = mHours12Texts; } } mOuterTextMinutes = mMinutesTexts; mMinutesText = mMinutesTexts; final int hoursAlpha = mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT; final int hoursAlpha = mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT; mAlpha[HOURS].setValue(hoursAlpha); mAlpha[HOURS].setValue(hoursAlpha); Loading @@ -627,9 +638,9 @@ public class RadialTimePickerView extends View { mYCenter = getHeight() / 2; mYCenter = getHeight() / 2; mCircleRadius = Math.min(mXCenter, mYCenter); mCircleRadius = Math.min(mXCenter, mYCenter); mMinHypotenuseForInnerNumber = mCircleRadius - mTextInset[HOURS_INNER] - mSelectorRadius; mMinDistForInnerNumber = mCircleRadius - mTextInset[HOURS_INNER] - mSelectorRadius; mMaxHypotenuseForOuterNumber = mCircleRadius - mTextInset[HOURS] + mSelectorRadius; mMaxDistForOuterNumber = mCircleRadius - mTextInset[HOURS] + mSelectorRadius; mHalfwayHypotenusePoint = mCircleRadius - (mTextInset[HOURS] + mTextInset[HOURS_INNER]) / 2; mHalfwayDist = mCircleRadius - (mTextInset[HOURS] + mTextInset[HOURS_INNER]) / 2; calculatePositionsHours(); calculatePositionsHours(); calculatePositionsMinutes(); calculatePositionsMinutes(); Loading Loading @@ -674,6 +685,7 @@ public class RadialTimePickerView extends View { private void drawMinutes(Canvas canvas, float alphaMod) { private void drawMinutes(Canvas canvas, float alphaMod) { final int minutesAlpha = (int) (mAlpha[MINUTES].getValue() * alphaMod + 0.5f); final int minutesAlpha = (int) (mAlpha[MINUTES].getValue() * alphaMod + 0.5f); if (minutesAlpha > 0) { if (minutesAlpha > 0) { // Draw the minute selector under the elements. drawSelector(canvas, MINUTES, mSelectorPath, alphaMod); drawSelector(canvas, MINUTES, mSelectorPath, alphaMod); // Exclude the selector region, then draw minutes with no // Exclude the selector region, then draw minutes with no Loading @@ -681,7 +693,7 @@ public class RadialTimePickerView extends View { canvas.save(Canvas.CLIP_SAVE_FLAG); canvas.save(Canvas.CLIP_SAVE_FLAG); canvas.clipPath(mSelectorPath, Region.Op.DIFFERENCE); canvas.clipPath(mSelectorPath, Region.Op.DIFFERENCE); drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES], drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES], mOuterTextMinutes, mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], mMinutesText, mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], minutesAlpha, false, 0, false); minutesAlpha, false, 0, false); canvas.restore(); canvas.restore(); Loading @@ -690,7 +702,7 @@ public class RadialTimePickerView extends View { canvas.save(Canvas.CLIP_SAVE_FLAG); canvas.save(Canvas.CLIP_SAVE_FLAG); canvas.clipPath(mSelectorPath, Region.Op.INTERSECT); canvas.clipPath(mSelectorPath, Region.Op.INTERSECT); drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES], drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES], mOuterTextMinutes, mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], mMinutesText, mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], minutesAlpha, true, mSelectionDegrees[MINUTES], true); minutesAlpha, true, mSelectionDegrees[MINUTES], true); canvas.restore(); canvas.restore(); } } Loading Loading @@ -718,7 +730,7 @@ public class RadialTimePickerView extends View { // Calculate the current radius at which to place the selection circle. // Calculate the current radius at which to place the selection circle. final int selRadius = mSelectorRadius; final int selRadius = mSelectorRadius; final int selLength = mCircleRadius - mTextInset[index]; final int selLength = mCircleRadius - mTextInset[index]; final double selAngleRad = Math.toRadians(mSelectionDegrees[index]); final double selAngleRad = Math.toRadians(mSelectionDegrees[index % 2]); final float selCenterX = mXCenter + selLength * (float) Math.sin(selAngleRad); final float selCenterX = mXCenter + selLength * (float) Math.sin(selAngleRad); final float selCenterY = mYCenter - selLength * (float) Math.cos(selAngleRad); final float selCenterY = mYCenter - selLength * (float) Math.cos(selAngleRad); Loading @@ -734,10 +746,10 @@ public class RadialTimePickerView extends View { } } // Draw the dot if we're between two items. // Draw the dot if we're between two items. final boolean shouldDrawDot = mSelectionDegrees[index] % 30 != 0; final boolean shouldDrawDot = mSelectionDegrees[index % 2] % 30 != 0; if (shouldDrawDot) { if (shouldDrawDot) { final Paint dotPaint = mPaintSelector[index % 2][SELECTOR_DOT]; final Paint dotPaint = mPaintSelector[index % 2][SELECTOR_DOT]; dotPaint.setColor(color); dotPaint.setColor(mSelectorDotColor); canvas.drawCircle(selCenterX, selCenterY, mSelectorDotRadius, dotPaint); canvas.drawCircle(selCenterX, selCenterY, mSelectorDotRadius, dotPaint); } } Loading Loading @@ -898,56 +910,43 @@ public class RadialTimePickerView extends View { } } private int getDegreesFromXY(float x, float y, boolean constrainOutside) { private int getDegreesFromXY(float x, float y, boolean constrainOutside) { final double hypotenuse = Math.sqrt( // Ensure the point is inside the touchable area. (y - mYCenter) * (y - mYCenter) + (x - mXCenter) * (x - mXCenter)); final int innerBound; final int outerBound; // Basic check if we're outside the range of the disk if (constrainOutside && hypotenuse > mCircleRadius) { return -1; } // Check if (mIs24HourMode && mShowHours) { if (mIs24HourMode && mShowHours) { if (hypotenuse >= mMinHypotenuseForInnerNumber innerBound = mMinDistForInnerNumber; && hypotenuse <= mHalfwayHypotenusePoint) { outerBound = mMaxDistForOuterNumber; mIsOnInnerCircle = true; } else if ((hypotenuse <= mMaxHypotenuseForOuterNumber || !constrainOutside) && hypotenuse >= mHalfwayHypotenusePoint) { mIsOnInnerCircle = false; } else { } else { return -1; final int index = mShowHours ? HOURS : MINUTES; final int center = mCircleRadius - mTextInset[index]; innerBound = center - mSelectorRadius; outerBound = center + mSelectorRadius; } } } else { final int index = (mShowHours) ? HOURS : MINUTES; final double dX = x - mXCenter; final float length = (mCircleRadius - mTextInset[index]); final double dY = y - mYCenter; final int distanceToNumber = (int) (hypotenuse - length); final double distFromCenter = Math.sqrt(dX * dX + dY * dY); final int maxAllowedDistance = mTextInset[index]; if (distFromCenter < innerBound || constrainOutside && distFromCenter > outerBound) { if (distanceToNumber < -maxAllowedDistance || (constrainOutside && distanceToNumber > maxAllowedDistance)) { return -1; return -1; } } } final float opposite = Math.abs(y - mYCenter); int degrees = (int) (Math.toDegrees(Math.asin(opposite / hypotenuse)) + 0.5); // Now we have to translate to the correct quadrant. // Convert to degrees. final boolean rightSide = (x > mXCenter); final int degrees = (int) (Math.toDegrees(Math.atan2(dY, dX) + Math.PI / 2) + 0.5); final boolean topSide = (y < mYCenter); if (degrees < 0) { if (rightSide) { return degrees + 360; if (topSide) { degrees = 90 - degrees; } else { } else { degrees = 90 + degrees; return degrees; } } } else { if (topSide) { degrees = 270 + degrees; } else { degrees = 270 - degrees; } } private boolean getInnerCircleFromXY(float x, float y) { if (mIs24HourMode && mShowHours) { final double dX = x - mXCenter; final double dY = y - mYCenter; final double distFromCenter = Math.sqrt(dX * dX + dY * dY); return distFromCenter <= mHalfwayDist; } } return degrees; return false; } } boolean mChangedDuringTouch = false; boolean mChangedDuringTouch = false; Loading Loading @@ -987,34 +986,28 @@ public class RadialTimePickerView extends View { private boolean handleTouchInput( private boolean handleTouchInput( float x, float y, boolean forceSelection, boolean autoAdvance) { float x, float y, boolean forceSelection, boolean autoAdvance) { // Calling getDegreesFromXY has side effects, so cache final boolean isOnInnerCircle = getInnerCircleFromXY(x, y); // whether we used to be on the inner circle. final boolean wasOnInnerCircle = mIsOnInnerCircle; final int degrees = getDegreesFromXY(x, y, false); final int degrees = getDegreesFromXY(x, y, false); if (degrees == -1) { if (degrees == -1) { return false; return false; } } final int[] selectionDegrees = mSelectionDegrees; final int type; final int type; final int newValue; final int newValue; final boolean valueChanged; final boolean valueChanged; if (mShowHours) { if (mShowHours) { final int snapDegrees = snapOnly30s(degrees, 0) % 360; final int snapDegrees = snapOnly30s(degrees, 0) % 360; valueChanged = selectionDegrees[HOURS] != snapDegrees valueChanged = mIsOnInnerCircle != isOnInnerCircle || selectionDegrees[HOURS_INNER] != snapDegrees || mSelectionDegrees[HOURS] != snapDegrees; || wasOnInnerCircle != mIsOnInnerCircle; mIsOnInnerCircle = isOnInnerCircle; mSelectionDegrees[HOURS] = snapDegrees; selectionDegrees[HOURS] = snapDegrees; selectionDegrees[HOURS_INNER] = snapDegrees; type = HOURS; type = HOURS; newValue = getCurrentHour(); newValue = getCurrentHour(); } else { } else { final int snapDegrees = snapPrefer30s(degrees) % 360; final int snapDegrees = snapPrefer30s(degrees) % 360; valueChanged = selectionDegrees[MINUTES] != snapDegrees; valueChanged = mSelectionDegrees[MINUTES] != snapDegrees; mSelectionDegrees[MINUTES] = snapDegrees; selectionDegrees[MINUTES] = snapDegrees; type = MINUTES; type = MINUTES; newValue = getCurrentMinute(); newValue = getCurrentMinute(); } } Loading Loading @@ -1132,17 +1125,11 @@ public class RadialTimePickerView extends View { @Override @Override protected int getVirtualViewAt(float x, float y) { protected int getVirtualViewAt(float x, float y) { final int id; final int id; // Calling getDegreesXY() has side-effects, so we need to cache the // current inner circle value and restore after the call. final boolean wasOnInnerCircle = mIsOnInnerCircle; final int degrees = getDegreesFromXY(x, y, true); final int degrees = getDegreesFromXY(x, y, true); final boolean isOnInnerCircle = mIsOnInnerCircle; mIsOnInnerCircle = wasOnInnerCircle; if (degrees != -1) { if (degrees != -1) { final int snapDegrees = snapOnly30s(degrees, 0) % 360; final int snapDegrees = snapOnly30s(degrees, 0) % 360; if (mShowHours) { if (mShowHours) { final boolean isOnInnerCircle = getInnerCircleFromXY(x, y); final int hour24 = getHourForDegrees(snapDegrees, isOnInnerCircle); final int hour24 = getHourForDegrees(snapDegrees, isOnInnerCircle); final int hour = mIs24HourMode ? hour24 : hour24To12(hour24); final int hour = mIs24HourMode ? hour24 : hour24To12(hour24); id = makeId(TYPE_HOUR, hour); id = makeId(TYPE_HOUR, hour); Loading @@ -1153,8 +1140,10 @@ public class RadialTimePickerView extends View { // If the touched minute is closer to the current minute // If the touched minute is closer to the current minute // than it is to the snapped minute, return current. // than it is to the snapped minute, return current. final int currentOffset = getCircularDiff(current, touched, MINUTES_IN_HOUR); final int snappedOffset = getCircularDiff(snapped, touched, MINUTES_IN_HOUR); final int minute; final int minute; if (Math.abs(current - touched) < Math.abs(snapped - touched)) { if (currentOffset < snappedOffset) { minute = current; minute = current; } else { } else { minute = snapped; minute = snapped; Loading @@ -1168,6 +1157,20 @@ public class RadialTimePickerView extends View { return id; return id; } } /** * Returns the difference in degrees between two values along a circle. * * @param first value in the range [0,max] * @param second value in the range [0,max] * @param max the maximum value along the circle * @return the difference in between the two values */ private int getCircularDiff(int first, int second, int max) { final int diff = Math.abs(first - second); final int midpoint = max / 2; return (diff > midpoint) ? (max - diff) : diff; } @Override @Override protected void getVisibleVirtualViews(IntArray virtualViewIds) { protected void getVisibleVirtualViews(IntArray virtualViewIds) { if (mShowHours) { if (mShowHours) { Loading @@ -1178,7 +1181,7 @@ public class RadialTimePickerView extends View { } } } else { } else { final int current = getCurrentMinute(); final int current = getCurrentMinute(); for (int i = 0; i < 60; i += MINUTE_INCREMENT) { for (int i = 0; i < MINUTES_IN_HOUR; i += MINUTE_INCREMENT) { virtualViewIds.add(makeId(TYPE_MINUTE, i)); virtualViewIds.add(makeId(TYPE_MINUTE, i)); // If the current minute falls between two increments, // If the current minute falls between two increments, Loading Loading @@ -1236,7 +1239,7 @@ public class RadialTimePickerView extends View { if (value < current && nextValue > current) { if (value < current && nextValue > current) { // The current value is between two snap values. // The current value is between two snap values. return makeId(type, current); return makeId(type, current); } else if (nextValue < 60) { } else if (nextValue < MINUTES_IN_HOUR) { return makeId(type, nextValue); return makeId(type, nextValue); } } } } Loading Loading @@ -1290,7 +1293,7 @@ public class RadialTimePickerView extends View { final float centerRadius; final float centerRadius; final float degrees; final float degrees; if (type == TYPE_HOUR) { if (type == TYPE_HOUR) { final boolean innerCircle = mIs24HourMode && value > 0 && value <= 12; final boolean innerCircle = getInnerCircleForHour(value); if (innerCircle) { if (innerCircle) { centerRadius = mCircleRadius - mTextInset[HOURS_INNER]; centerRadius = mCircleRadius - mTextInset[HOURS_INNER]; radius = mSelectorRadius; radius = mSelectorRadius; Loading Loading
core/java/android/widget/RadialTimePickerView.java +95 −92 Original line number Original line Diff line number Diff line Loading @@ -79,8 +79,10 @@ public class RadialTimePickerView extends View { // Transparent alpha level // Transparent alpha level private static final int ALPHA_TRANSPARENT = 0; private static final int ALPHA_TRANSPARENT = 0; private static final int DEGREES_FOR_ONE_HOUR = 30; private static final int HOURS_IN_DAY = 24; private static final int DEGREES_FOR_ONE_MINUTE = 6; private static final int MINUTES_IN_HOUR = 60; private static final int DEGREES_FOR_ONE_HOUR = 360 / HOURS_IN_DAY; private static final int DEGREES_FOR_ONE_MINUTE = 360 / MINUTES_IN_HOUR; private static final int[] HOURS_NUMBERS = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; private static final int[] HOURS_NUMBERS = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; private static final int[] HOURS_NUMBERS_24 = {0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; private static final int[] HOURS_NUMBERS_24 = {0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}; Loading Loading @@ -140,8 +142,7 @@ public class RadialTimePickerView extends View { private final float[] mInnerTextX = new float[12]; private final float[] mInnerTextX = new float[12]; private final float[] mInnerTextY = new float[12]; private final float[] mInnerTextY = new float[12]; private final int[] mLineLength = new int[3]; private final int[] mSelectionDegrees = new int[2]; private final int[] mSelectionDegrees = new int[3]; private final ArrayList<Animator> mHoursToMinutesAnims = new ArrayList<>(); private final ArrayList<Animator> mHoursToMinutesAnims = new ArrayList<>(); private final ArrayList<Animator> mMinuteToHoursAnims = new ArrayList<>(); private final ArrayList<Animator> mMinuteToHoursAnims = new ArrayList<>(); Loading @@ -168,13 +169,13 @@ public class RadialTimePickerView extends View { private int mYCenter; private int mYCenter; private int mCircleRadius; private int mCircleRadius; private int mMinHypotenuseForInnerNumber; private int mMinDistForInnerNumber; private int mMaxHypotenuseForOuterNumber; private int mMaxDistForOuterNumber; private int mHalfwayHypotenusePoint; private int mHalfwayDist; private String[] mOuterTextHours; private String[] mOuterTextHours; private String[] mInnerTextHours; private String[] mInnerTextHours; private String[] mOuterTextMinutes; private String[] mMinutesText; private AnimatorSet mTransition; private AnimatorSet mTransition; private int mAmOrPm; private int mAmOrPm; Loading Loading @@ -462,11 +463,10 @@ public class RadialTimePickerView extends View { private void setCurrentHourInternal(int hour, boolean callback, boolean autoAdvance) { 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; // 0 is 12 AM (midnight) and 12 is 12 PM (noon). // 0 is 12 AM (midnight) and 12 is 12 PM (noon). final int amOrPm = (hour == 0 || (hour % 24) < 12) ? AM : PM; final int amOrPm = (hour == 0 || (hour % 24) < 12) ? AM : PM; final boolean isOnInnerCircle = mIs24HourMode && hour >= 1 && hour <= 12; final boolean isOnInnerCircle = getInnerCircleForHour(hour); if (mAmOrPm != amOrPm || mIsOnInnerCircle != isOnInnerCircle) { if (mAmOrPm != amOrPm || mIsOnInnerCircle != isOnInnerCircle) { mAmOrPm = amOrPm; mAmOrPm = amOrPm; mIsOnInnerCircle = isOnInnerCircle; mIsOnInnerCircle = isOnInnerCircle; Loading @@ -488,8 +488,7 @@ public class RadialTimePickerView extends View { * @return the current hour between 0 and 23 (inclusive) * @return the current hour between 0 and 23 (inclusive) */ */ public int getCurrentHour() { public int getCurrentHour() { return getHourForDegrees( return getHourForDegrees(mSelectionDegrees[HOURS], mIsOnInnerCircle); mSelectionDegrees[mIsOnInnerCircle ? HOURS_INNER : HOURS], mIsOnInnerCircle); } } private int getHourForDegrees(int degrees, boolean innerCircle) { private int getHourForDegrees(int degrees, boolean innerCircle) { Loading @@ -497,11 +496,11 @@ public class RadialTimePickerView extends View { 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 (innerCircle && hour == 0) { if (!innerCircle && hour == 0) { // Inner circle is 1 through 12. // Outer circle is 1 through 12. hour = 12; hour = 12; } else if (!innerCircle && hour != 0) { } else if (innerCircle && hour != 0) { // Outer circle is 13 through 23 and 0. // Inner circle is 13 through 23 and 0. hour += 12; hour += 12; } } } else if (mAmOrPm == PM) { } else if (mAmOrPm == PM) { Loading @@ -510,6 +509,9 @@ public class RadialTimePickerView extends View { return hour; return hour; } } /** * @param hour the hour in 24-hour time or 12-hour time */ private int getDegreesForHour(int hour) { private int getDegreesForHour(int hour) { // Convert to be 0-11. // Convert to be 0-11. if (mIs24HourMode) { if (mIs24HourMode) { Loading @@ -522,12 +524,19 @@ public class RadialTimePickerView extends View { return hour * DEGREES_FOR_ONE_HOUR; return hour * DEGREES_FOR_ONE_HOUR; } } /** * @param hour the hour in 24-hour time or 12-hour time */ private boolean getInnerCircleForHour(int hour) { return mIs24HourMode && (hour == 0 || hour > 12); } public void setCurrentMinute(int minute) { public void setCurrentMinute(int minute) { setCurrentMinuteInternal(minute, true); setCurrentMinuteInternal(minute, true); } } private void setCurrentMinuteInternal(int minute, boolean callback) { private void setCurrentMinuteInternal(int minute, boolean callback) { mSelectionDegrees[MINUTES] = (minute % 60) * DEGREES_FOR_ONE_MINUTE; mSelectionDegrees[MINUTES] = (minute % MINUTES_IN_HOUR) * DEGREES_FOR_ONE_MINUTE; invalidate(); invalidate(); Loading Loading @@ -572,6 +581,7 @@ public class RadialTimePickerView extends View { initData(); initData(); invalidate(); invalidate(); mTouchHelper.invalidateRoot(); } } public void showMinutes(boolean animate) { public void showMinutes(boolean animate) { Loading @@ -587,6 +597,7 @@ public class RadialTimePickerView extends View { initData(); initData(); invalidate(); invalidate(); mTouchHelper.invalidateRoot(); } } private void initHoursAndMinutesText() { private void initHoursAndMinutesText() { Loading @@ -608,7 +619,7 @@ public class RadialTimePickerView extends View { mInnerTextHours = mHours12Texts; mInnerTextHours = mHours12Texts; } } mOuterTextMinutes = mMinutesTexts; mMinutesText = mMinutesTexts; final int hoursAlpha = mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT; final int hoursAlpha = mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT; mAlpha[HOURS].setValue(hoursAlpha); mAlpha[HOURS].setValue(hoursAlpha); Loading @@ -627,9 +638,9 @@ public class RadialTimePickerView extends View { mYCenter = getHeight() / 2; mYCenter = getHeight() / 2; mCircleRadius = Math.min(mXCenter, mYCenter); mCircleRadius = Math.min(mXCenter, mYCenter); mMinHypotenuseForInnerNumber = mCircleRadius - mTextInset[HOURS_INNER] - mSelectorRadius; mMinDistForInnerNumber = mCircleRadius - mTextInset[HOURS_INNER] - mSelectorRadius; mMaxHypotenuseForOuterNumber = mCircleRadius - mTextInset[HOURS] + mSelectorRadius; mMaxDistForOuterNumber = mCircleRadius - mTextInset[HOURS] + mSelectorRadius; mHalfwayHypotenusePoint = mCircleRadius - (mTextInset[HOURS] + mTextInset[HOURS_INNER]) / 2; mHalfwayDist = mCircleRadius - (mTextInset[HOURS] + mTextInset[HOURS_INNER]) / 2; calculatePositionsHours(); calculatePositionsHours(); calculatePositionsMinutes(); calculatePositionsMinutes(); Loading Loading @@ -674,6 +685,7 @@ public class RadialTimePickerView extends View { private void drawMinutes(Canvas canvas, float alphaMod) { private void drawMinutes(Canvas canvas, float alphaMod) { final int minutesAlpha = (int) (mAlpha[MINUTES].getValue() * alphaMod + 0.5f); final int minutesAlpha = (int) (mAlpha[MINUTES].getValue() * alphaMod + 0.5f); if (minutesAlpha > 0) { if (minutesAlpha > 0) { // Draw the minute selector under the elements. drawSelector(canvas, MINUTES, mSelectorPath, alphaMod); drawSelector(canvas, MINUTES, mSelectorPath, alphaMod); // Exclude the selector region, then draw minutes with no // Exclude the selector region, then draw minutes with no Loading @@ -681,7 +693,7 @@ public class RadialTimePickerView extends View { canvas.save(Canvas.CLIP_SAVE_FLAG); canvas.save(Canvas.CLIP_SAVE_FLAG); canvas.clipPath(mSelectorPath, Region.Op.DIFFERENCE); canvas.clipPath(mSelectorPath, Region.Op.DIFFERENCE); drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES], drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES], mOuterTextMinutes, mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], mMinutesText, mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], minutesAlpha, false, 0, false); minutesAlpha, false, 0, false); canvas.restore(); canvas.restore(); Loading @@ -690,7 +702,7 @@ public class RadialTimePickerView extends View { canvas.save(Canvas.CLIP_SAVE_FLAG); canvas.save(Canvas.CLIP_SAVE_FLAG); canvas.clipPath(mSelectorPath, Region.Op.INTERSECT); canvas.clipPath(mSelectorPath, Region.Op.INTERSECT); drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES], drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES], mOuterTextMinutes, mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], mMinutesText, mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], minutesAlpha, true, mSelectionDegrees[MINUTES], true); minutesAlpha, true, mSelectionDegrees[MINUTES], true); canvas.restore(); canvas.restore(); } } Loading Loading @@ -718,7 +730,7 @@ public class RadialTimePickerView extends View { // Calculate the current radius at which to place the selection circle. // Calculate the current radius at which to place the selection circle. final int selRadius = mSelectorRadius; final int selRadius = mSelectorRadius; final int selLength = mCircleRadius - mTextInset[index]; final int selLength = mCircleRadius - mTextInset[index]; final double selAngleRad = Math.toRadians(mSelectionDegrees[index]); final double selAngleRad = Math.toRadians(mSelectionDegrees[index % 2]); final float selCenterX = mXCenter + selLength * (float) Math.sin(selAngleRad); final float selCenterX = mXCenter + selLength * (float) Math.sin(selAngleRad); final float selCenterY = mYCenter - selLength * (float) Math.cos(selAngleRad); final float selCenterY = mYCenter - selLength * (float) Math.cos(selAngleRad); Loading @@ -734,10 +746,10 @@ public class RadialTimePickerView extends View { } } // Draw the dot if we're between two items. // Draw the dot if we're between two items. final boolean shouldDrawDot = mSelectionDegrees[index] % 30 != 0; final boolean shouldDrawDot = mSelectionDegrees[index % 2] % 30 != 0; if (shouldDrawDot) { if (shouldDrawDot) { final Paint dotPaint = mPaintSelector[index % 2][SELECTOR_DOT]; final Paint dotPaint = mPaintSelector[index % 2][SELECTOR_DOT]; dotPaint.setColor(color); dotPaint.setColor(mSelectorDotColor); canvas.drawCircle(selCenterX, selCenterY, mSelectorDotRadius, dotPaint); canvas.drawCircle(selCenterX, selCenterY, mSelectorDotRadius, dotPaint); } } Loading Loading @@ -898,56 +910,43 @@ public class RadialTimePickerView extends View { } } private int getDegreesFromXY(float x, float y, boolean constrainOutside) { private int getDegreesFromXY(float x, float y, boolean constrainOutside) { final double hypotenuse = Math.sqrt( // Ensure the point is inside the touchable area. (y - mYCenter) * (y - mYCenter) + (x - mXCenter) * (x - mXCenter)); final int innerBound; final int outerBound; // Basic check if we're outside the range of the disk if (constrainOutside && hypotenuse > mCircleRadius) { return -1; } // Check if (mIs24HourMode && mShowHours) { if (mIs24HourMode && mShowHours) { if (hypotenuse >= mMinHypotenuseForInnerNumber innerBound = mMinDistForInnerNumber; && hypotenuse <= mHalfwayHypotenusePoint) { outerBound = mMaxDistForOuterNumber; mIsOnInnerCircle = true; } else if ((hypotenuse <= mMaxHypotenuseForOuterNumber || !constrainOutside) && hypotenuse >= mHalfwayHypotenusePoint) { mIsOnInnerCircle = false; } else { } else { return -1; final int index = mShowHours ? HOURS : MINUTES; final int center = mCircleRadius - mTextInset[index]; innerBound = center - mSelectorRadius; outerBound = center + mSelectorRadius; } } } else { final int index = (mShowHours) ? HOURS : MINUTES; final double dX = x - mXCenter; final float length = (mCircleRadius - mTextInset[index]); final double dY = y - mYCenter; final int distanceToNumber = (int) (hypotenuse - length); final double distFromCenter = Math.sqrt(dX * dX + dY * dY); final int maxAllowedDistance = mTextInset[index]; if (distFromCenter < innerBound || constrainOutside && distFromCenter > outerBound) { if (distanceToNumber < -maxAllowedDistance || (constrainOutside && distanceToNumber > maxAllowedDistance)) { return -1; return -1; } } } final float opposite = Math.abs(y - mYCenter); int degrees = (int) (Math.toDegrees(Math.asin(opposite / hypotenuse)) + 0.5); // Now we have to translate to the correct quadrant. // Convert to degrees. final boolean rightSide = (x > mXCenter); final int degrees = (int) (Math.toDegrees(Math.atan2(dY, dX) + Math.PI / 2) + 0.5); final boolean topSide = (y < mYCenter); if (degrees < 0) { if (rightSide) { return degrees + 360; if (topSide) { degrees = 90 - degrees; } else { } else { degrees = 90 + degrees; return degrees; } } } else { if (topSide) { degrees = 270 + degrees; } else { degrees = 270 - degrees; } } private boolean getInnerCircleFromXY(float x, float y) { if (mIs24HourMode && mShowHours) { final double dX = x - mXCenter; final double dY = y - mYCenter; final double distFromCenter = Math.sqrt(dX * dX + dY * dY); return distFromCenter <= mHalfwayDist; } } return degrees; return false; } } boolean mChangedDuringTouch = false; boolean mChangedDuringTouch = false; Loading Loading @@ -987,34 +986,28 @@ public class RadialTimePickerView extends View { private boolean handleTouchInput( private boolean handleTouchInput( float x, float y, boolean forceSelection, boolean autoAdvance) { float x, float y, boolean forceSelection, boolean autoAdvance) { // Calling getDegreesFromXY has side effects, so cache final boolean isOnInnerCircle = getInnerCircleFromXY(x, y); // whether we used to be on the inner circle. final boolean wasOnInnerCircle = mIsOnInnerCircle; final int degrees = getDegreesFromXY(x, y, false); final int degrees = getDegreesFromXY(x, y, false); if (degrees == -1) { if (degrees == -1) { return false; return false; } } final int[] selectionDegrees = mSelectionDegrees; final int type; final int type; final int newValue; final int newValue; final boolean valueChanged; final boolean valueChanged; if (mShowHours) { if (mShowHours) { final int snapDegrees = snapOnly30s(degrees, 0) % 360; final int snapDegrees = snapOnly30s(degrees, 0) % 360; valueChanged = selectionDegrees[HOURS] != snapDegrees valueChanged = mIsOnInnerCircle != isOnInnerCircle || selectionDegrees[HOURS_INNER] != snapDegrees || mSelectionDegrees[HOURS] != snapDegrees; || wasOnInnerCircle != mIsOnInnerCircle; mIsOnInnerCircle = isOnInnerCircle; mSelectionDegrees[HOURS] = snapDegrees; selectionDegrees[HOURS] = snapDegrees; selectionDegrees[HOURS_INNER] = snapDegrees; type = HOURS; type = HOURS; newValue = getCurrentHour(); newValue = getCurrentHour(); } else { } else { final int snapDegrees = snapPrefer30s(degrees) % 360; final int snapDegrees = snapPrefer30s(degrees) % 360; valueChanged = selectionDegrees[MINUTES] != snapDegrees; valueChanged = mSelectionDegrees[MINUTES] != snapDegrees; mSelectionDegrees[MINUTES] = snapDegrees; selectionDegrees[MINUTES] = snapDegrees; type = MINUTES; type = MINUTES; newValue = getCurrentMinute(); newValue = getCurrentMinute(); } } Loading Loading @@ -1132,17 +1125,11 @@ public class RadialTimePickerView extends View { @Override @Override protected int getVirtualViewAt(float x, float y) { protected int getVirtualViewAt(float x, float y) { final int id; final int id; // Calling getDegreesXY() has side-effects, so we need to cache the // current inner circle value and restore after the call. final boolean wasOnInnerCircle = mIsOnInnerCircle; final int degrees = getDegreesFromXY(x, y, true); final int degrees = getDegreesFromXY(x, y, true); final boolean isOnInnerCircle = mIsOnInnerCircle; mIsOnInnerCircle = wasOnInnerCircle; if (degrees != -1) { if (degrees != -1) { final int snapDegrees = snapOnly30s(degrees, 0) % 360; final int snapDegrees = snapOnly30s(degrees, 0) % 360; if (mShowHours) { if (mShowHours) { final boolean isOnInnerCircle = getInnerCircleFromXY(x, y); final int hour24 = getHourForDegrees(snapDegrees, isOnInnerCircle); final int hour24 = getHourForDegrees(snapDegrees, isOnInnerCircle); final int hour = mIs24HourMode ? hour24 : hour24To12(hour24); final int hour = mIs24HourMode ? hour24 : hour24To12(hour24); id = makeId(TYPE_HOUR, hour); id = makeId(TYPE_HOUR, hour); Loading @@ -1153,8 +1140,10 @@ public class RadialTimePickerView extends View { // If the touched minute is closer to the current minute // If the touched minute is closer to the current minute // than it is to the snapped minute, return current. // than it is to the snapped minute, return current. final int currentOffset = getCircularDiff(current, touched, MINUTES_IN_HOUR); final int snappedOffset = getCircularDiff(snapped, touched, MINUTES_IN_HOUR); final int minute; final int minute; if (Math.abs(current - touched) < Math.abs(snapped - touched)) { if (currentOffset < snappedOffset) { minute = current; minute = current; } else { } else { minute = snapped; minute = snapped; Loading @@ -1168,6 +1157,20 @@ public class RadialTimePickerView extends View { return id; return id; } } /** * Returns the difference in degrees between two values along a circle. * * @param first value in the range [0,max] * @param second value in the range [0,max] * @param max the maximum value along the circle * @return the difference in between the two values */ private int getCircularDiff(int first, int second, int max) { final int diff = Math.abs(first - second); final int midpoint = max / 2; return (diff > midpoint) ? (max - diff) : diff; } @Override @Override protected void getVisibleVirtualViews(IntArray virtualViewIds) { protected void getVisibleVirtualViews(IntArray virtualViewIds) { if (mShowHours) { if (mShowHours) { Loading @@ -1178,7 +1181,7 @@ public class RadialTimePickerView extends View { } } } else { } else { final int current = getCurrentMinute(); final int current = getCurrentMinute(); for (int i = 0; i < 60; i += MINUTE_INCREMENT) { for (int i = 0; i < MINUTES_IN_HOUR; i += MINUTE_INCREMENT) { virtualViewIds.add(makeId(TYPE_MINUTE, i)); virtualViewIds.add(makeId(TYPE_MINUTE, i)); // If the current minute falls between two increments, // If the current minute falls between two increments, Loading Loading @@ -1236,7 +1239,7 @@ public class RadialTimePickerView extends View { if (value < current && nextValue > current) { if (value < current && nextValue > current) { // The current value is between two snap values. // The current value is between two snap values. return makeId(type, current); return makeId(type, current); } else if (nextValue < 60) { } else if (nextValue < MINUTES_IN_HOUR) { return makeId(type, nextValue); return makeId(type, nextValue); } } } } Loading Loading @@ -1290,7 +1293,7 @@ public class RadialTimePickerView extends View { final float centerRadius; final float centerRadius; final float degrees; final float degrees; if (type == TYPE_HOUR) { if (type == TYPE_HOUR) { final boolean innerCircle = mIs24HourMode && value > 0 && value <= 12; final boolean innerCircle = getInnerCircleForHour(value); if (innerCircle) { if (innerCircle) { centerRadius = mCircleRadius - mTextInset[HOURS_INNER]; centerRadius = mCircleRadius - mTextInset[HOURS_INNER]; radius = mSelectorRadius; radius = mSelectorRadius; Loading