Skip to content

Commit 232dd3f

Browse files
committed
Polish the NumberPicker, TimePicker, and DatePicker based on UX request.
1. Now the spinners in the time and data picker a wider therefore easier to interact with, i.e. harder to miss accidentall. 2. Removed the scroll distance cut off user to distinguish between fling and change by one. 3. Added visual feedback when the areas the serve as virtual buttons in number picker are poked. 4. Removed the coeffcient that was making drap not to be one-to-one with the scrolled distance. 5. Added some margin at the top and bottom of the spinners in date and time pickers. bug:6321432 Change-Id: I311c1733d1951b0563209401faa830ca70ec87cb
1 parent 1bc1b8a commit 232dd3f

File tree

5 files changed

+160
-94
lines changed

5 files changed

+160
-94
lines changed

core/java/android/widget/NumberPicker.java

Lines changed: 135 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -132,16 +132,6 @@ public class NumberPicker extends LinearLayout {
132132
*/
133133
private static final int UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE = 48;
134134

135-
/**
136-
* The default unscaled minimal distance for a swipe to be considered a fling.
137-
*/
138-
private static final int UNSCALED_DEFAULT_MIN_FLING_DISTANCE = 150;
139-
140-
/**
141-
* Coefficient for adjusting touch scroll distance.
142-
*/
143-
private static final float TOUCH_SCROLL_DECELERATION_COEFFICIENT = 2.0f;
144-
145135
/**
146136
* The resource id for the default layout.
147137
*/
@@ -232,11 +222,6 @@ public String format(int value) {
232222
*/
233223
private final int mTextSize;
234224

235-
/**
236-
* The minimal distance for a swipe to be considered a fling.
237-
*/
238-
private final int mMinFlingDistance;
239-
240225
/**
241226
* The height of the gap between text elements if the selector wheel.
242227
*/
@@ -297,6 +282,11 @@ public String format(int value) {
297282
*/
298283
private final Paint mSelectorWheelPaint;
299284

285+
/**
286+
* The {@link Drawable} for pressed virtual (increment/decrement) buttons.
287+
*/
288+
private final Drawable mVirtualButtonPressedDrawable;
289+
300290
/**
301291
* The height of a selector element (text + gap).
302292
*/
@@ -434,11 +424,26 @@ public String format(int value) {
434424
*/
435425
private int mLastHoveredChildVirtualViewId;
436426

427+
/**
428+
* Whether the increment virtual button is pressed.
429+
*/
430+
private boolean mIncrementVirtualButtonPressed;
431+
432+
/**
433+
* Whether the decrement virtual button is pressed.
434+
*/
435+
private boolean mDecrementVirtualButtonPressed;
436+
437437
/**
438438
* Provider to report to clients the semantic structure of this widget.
439439
*/
440440
private AccessibilityNodeProviderImpl mAccessibilityNodeProvider;
441441

442+
/**
443+
* Helper class for managing pressed state of the virtual buttons.
444+
*/
445+
private final PressedStateHelper mPressedStateHelper;
446+
442447
/**
443448
* Interface to listen for changes of the current value.
444449
*/
@@ -553,12 +558,6 @@ public NumberPicker(Context context, AttributeSet attrs, int defStyle) {
553558
mSelectionDividersDistance = attributesArray.getDimensionPixelSize(
554559
R.styleable.NumberPicker_selectionDividersDistance, defSelectionDividerDistance);
555560

556-
final int defMinFlingDistance = (int) TypedValue.applyDimension(
557-
TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_MIN_FLING_DISTANCE,
558-
getResources().getDisplayMetrics());
559-
mMinFlingDistance = attributesArray.getDimensionPixelSize(
560-
R.styleable.NumberPicker_minFlingDistance, defMinFlingDistance);
561-
562561
mMinHeight = attributesArray.getDimensionPixelSize(
563562
R.styleable.NumberPicker_internalMinHeight, SIZE_UNSPECIFIED);
564563

@@ -581,8 +580,13 @@ public NumberPicker(Context context, AttributeSet attrs, int defStyle) {
581580

582581
mComputeMaxWidth = (mMaxWidth == SIZE_UNSPECIFIED);
583582

583+
mVirtualButtonPressedDrawable = attributesArray.getDrawable(
584+
R.styleable.NumberPicker_virtualButtonPressedDrawable);
585+
584586
attributesArray.recycle();
585587

588+
mPressedStateHelper = new PressedStateHelper();
589+
586590
// By default Linearlayout that we extend is not drawn. This is
587591
// its draw() method is not called but dispatchDraw() is called
588592
// directly (see ViewGroup.drawChild()). However, this class uses
@@ -776,7 +780,19 @@ public boolean onInterceptTouchEvent(MotionEvent event) {
776780
mLastDownEventTime = event.getEventTime();
777781
mIngonreMoveEvents = false;
778782
mShowSoftInputOnTap = false;
779-
// Make sure we wupport flinging inside scrollables.
783+
// Handle pressed state before any state change.
784+
if (mLastDownEventY < mTopSelectionDividerTop) {
785+
if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
786+
mPressedStateHelper.buttonPressDelayed(
787+
PressedStateHelper.BUTTON_DECREMENT);
788+
}
789+
} else if (mLastDownEventY > mBottomSelectionDividerBottom) {
790+
if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
791+
mPressedStateHelper.buttonPressDelayed(
792+
PressedStateHelper.BUTTON_INCREMENT);
793+
}
794+
}
795+
// Make sure we support flinging inside scrollables.
780796
getParent().requestDisallowInterceptTouchEvent(true);
781797
if (!mFlingScroller.isFinished()) {
782798
mFlingScroller.forceFinished(true);
@@ -826,8 +842,7 @@ public boolean onTouchEvent(MotionEvent event) {
826842
onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
827843
}
828844
} else {
829-
int deltaMoveY = (int) ((currentMoveY - mLastDownOrMoveEventY)
830-
/ TOUCH_SCROLL_DECELERATION_COEFFICIENT);
845+
int deltaMoveY = (int) ((currentMoveY - mLastDownOrMoveEventY));
831846
scrollBy(0, deltaMoveY);
832847
invalidate();
833848
}
@@ -836,23 +851,12 @@ public boolean onTouchEvent(MotionEvent event) {
836851
case MotionEvent.ACTION_UP: {
837852
removeBeginSoftInputCommand();
838853
removeChangeCurrentByOneFromLongPress();
854+
mPressedStateHelper.cancel();
839855
VelocityTracker velocityTracker = mVelocityTracker;
840856
velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
841857
int initialVelocity = (int) velocityTracker.getYVelocity();
842858
if (Math.abs(initialVelocity) > mMinimumFlingVelocity) {
843-
int deltaMove = (int) (event.getY() - mLastDownEventY);
844-
int absDeltaMoveY = Math.abs(deltaMove);
845-
if (absDeltaMoveY > mMinFlingDistance) {
846-
fling(initialVelocity);
847-
} else {
848-
final int normalizedDeltaMove =
849-
(int) (absDeltaMoveY / TOUCH_SCROLL_DECELERATION_COEFFICIENT);
850-
if (normalizedDeltaMove < mSelectorElementHeight) {
851-
snapToNextValue(deltaMove < 0);
852-
} else {
853-
snapToClosestValue();
854-
}
855-
}
859+
fling(initialVelocity);
856860
onScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
857861
} else {
858862
int eventY = (int) event.getY();
@@ -867,8 +871,12 @@ public boolean onTouchEvent(MotionEvent event) {
867871
- SELECTOR_MIDDLE_ITEM_INDEX;
868872
if (selectorIndexOffset > 0) {
869873
changeValueByOne(true);
874+
mPressedStateHelper.buttonTapped(
875+
PressedStateHelper.BUTTON_INCREMENT);
870876
} else if (selectorIndexOffset < 0) {
871877
changeValueByOne(false);
878+
mPressedStateHelper.buttonTapped(
879+
PressedStateHelper.BUTTON_DECREMENT);
872880
}
873881
}
874882
} else {
@@ -1356,6 +1364,22 @@ protected void onDraw(Canvas canvas) {
13561364
float x = (mRight - mLeft) / 2;
13571365
float y = mCurrentScrollOffset;
13581366

1367+
// draw the virtual buttons pressed state if needed
1368+
if (mVirtualButtonPressedDrawable != null
1369+
&& mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
1370+
if (mDecrementVirtualButtonPressed) {
1371+
mVirtualButtonPressedDrawable.setState(PRESSED_STATE_SET);
1372+
mVirtualButtonPressedDrawable.setBounds(0, 0, mRight, mTopSelectionDividerTop);
1373+
mVirtualButtonPressedDrawable.draw(canvas);
1374+
}
1375+
if (mIncrementVirtualButtonPressed) {
1376+
mVirtualButtonPressedDrawable.setState(PRESSED_STATE_SET);
1377+
mVirtualButtonPressedDrawable.setBounds(0, mBottomSelectionDividerBottom, mRight,
1378+
mBottom);
1379+
mVirtualButtonPressedDrawable.draw(canvas);
1380+
}
1381+
}
1382+
13591383
// draw the selector wheel
13601384
int[] selectorIndices = mSelectorIndices;
13611385
for (int i = 0; i < selectorIndices.length; i++) {
@@ -1465,15 +1489,15 @@ private int resolveSizeAndStateRespectingMinSize(
14651489
*/
14661490
private void initializeSelectorWheelIndices() {
14671491
mSelectorIndexToStringCache.clear();
1468-
int[] selectorIdices = mSelectorIndices;
1492+
int[] selectorIndices = mSelectorIndices;
14691493
int current = getValue();
14701494
for (int i = 0; i < mSelectorIndices.length; i++) {
14711495
int selectorIndex = current + (i - SELECTOR_MIDDLE_ITEM_INDEX);
14721496
if (mWrapSelectorWheel) {
14731497
selectorIndex = getWrappedSelectorIndex(selectorIndex);
14741498
}
1475-
mSelectorIndices[i] = selectorIndex;
1476-
ensureCachedScrollSelectorValue(mSelectorIndices[i]);
1499+
selectorIndices[i] = selectorIndex;
1500+
ensureCachedScrollSelectorValue(selectorIndices[i]);
14771501
}
14781502
}
14791503

@@ -1775,6 +1799,7 @@ private void removeAllCallbacks() {
17751799
if (mBeginSoftInputOnLongPressCommand != null) {
17761800
removeCallbacks(mBeginSoftInputOnLongPressCommand);
17771801
}
1802+
mPressedStateHelper.cancel();
17781803
}
17791804

17801805
/**
@@ -1910,39 +1935,80 @@ private boolean ensureScrollWheelAdjusted() {
19101935
return false;
19111936
}
19121937

1913-
private void snapToNextValue(boolean increment) {
1914-
int deltaY = mCurrentScrollOffset - mInitialScrollOffset;
1915-
int amountToScroll = 0;
1916-
if (deltaY != 0) {
1917-
mPreviousScrollerY = 0;
1918-
if (deltaY > 0) {
1919-
if (increment) {
1920-
amountToScroll = - deltaY;
1921-
} else {
1922-
amountToScroll = mSelectorElementHeight - deltaY;
1923-
}
1924-
} else {
1925-
if (increment) {
1926-
amountToScroll = - mSelectorElementHeight - deltaY;
1927-
} else {
1928-
amountToScroll = - deltaY;
1929-
}
1938+
class PressedStateHelper implements Runnable {
1939+
public static final int BUTTON_INCREMENT = 1;
1940+
public static final int BUTTON_DECREMENT = 2;
1941+
1942+
private final int MODE_PRESS = 1;
1943+
private final int MODE_TAPPED = 2;
1944+
1945+
private int mManagedButton;
1946+
private int mMode;
1947+
1948+
public void cancel() {
1949+
mMode = 0;
1950+
mManagedButton = 0;
1951+
NumberPicker.this.removeCallbacks(this);
1952+
if (mIncrementVirtualButtonPressed) {
1953+
mIncrementVirtualButtonPressed = false;
1954+
invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
1955+
}
1956+
mDecrementVirtualButtonPressed = false;
1957+
if (mDecrementVirtualButtonPressed) {
1958+
invalidate(0, 0, mRight, mTopSelectionDividerTop);
19301959
}
1931-
mFlingScroller.startScroll(0, 0, 0, amountToScroll, SNAP_SCROLL_DURATION);
1932-
invalidate();
19331960
}
1934-
}
19351961

1936-
private void snapToClosestValue() {
1937-
// adjust to the closest value
1938-
int deltaY = mInitialScrollOffset - mCurrentScrollOffset;
1939-
if (deltaY != 0) {
1940-
mPreviousScrollerY = 0;
1941-
if (Math.abs(deltaY) > mSelectorElementHeight / 2) {
1942-
deltaY += (deltaY > 0) ? -mSelectorElementHeight : mSelectorElementHeight;
1962+
public void buttonPressDelayed(int button) {
1963+
cancel();
1964+
mMode = MODE_PRESS;
1965+
mManagedButton = button;
1966+
NumberPicker.this.postDelayed(this, ViewConfiguration.getTapTimeout());
1967+
}
1968+
1969+
public void buttonTapped(int button) {
1970+
cancel();
1971+
mMode = MODE_TAPPED;
1972+
mManagedButton = button;
1973+
NumberPicker.this.post(this);
1974+
}
1975+
1976+
@Override
1977+
public void run() {
1978+
switch (mMode) {
1979+
case MODE_PRESS: {
1980+
switch (mManagedButton) {
1981+
case BUTTON_INCREMENT: {
1982+
mIncrementVirtualButtonPressed = true;
1983+
invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
1984+
} break;
1985+
case BUTTON_DECREMENT: {
1986+
mDecrementVirtualButtonPressed = true;
1987+
invalidate(0, 0, mRight, mTopSelectionDividerTop);
1988+
}
1989+
}
1990+
} break;
1991+
case MODE_TAPPED: {
1992+
switch (mManagedButton) {
1993+
case BUTTON_INCREMENT: {
1994+
if (!mIncrementVirtualButtonPressed) {
1995+
NumberPicker.this.postDelayed(this,
1996+
ViewConfiguration.getPressedStateDuration());
1997+
}
1998+
mIncrementVirtualButtonPressed ^= true;
1999+
invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
2000+
} break;
2001+
case BUTTON_DECREMENT: {
2002+
if (!mDecrementVirtualButtonPressed) {
2003+
NumberPicker.this.postDelayed(this,
2004+
ViewConfiguration.getPressedStateDuration());
2005+
}
2006+
mDecrementVirtualButtonPressed ^= true;
2007+
invalidate(0, 0, mRight, mTopSelectionDividerTop);
2008+
}
2009+
}
2010+
} break;
19432011
}
1944-
mFlingScroller.startScroll(0, 0, 0, deltaY, SNAP_SCROLL_DURATION);
1945-
invalidate();
19462012
}
19472013
}
19482014

core/res/res/layout/date_picker_holo.xml

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@
4141
android:id="@+id/month"
4242
android:layout_width="wrap_content"
4343
android:layout_height="wrap_content"
44-
android:layout_marginTop="10dip"
45-
android:layout_marginBottom="10dip"
46-
android:layout_marginLeft="16dip"
47-
android:layout_marginRight="16dip"
44+
android:layout_marginTop="16dip"
45+
android:layout_marginBottom="16dip"
46+
android:layout_marginLeft="8dip"
47+
android:layout_marginRight="8dip"
4848
android:focusable="true"
4949
android:focusableInTouchMode="true"
5050
/>
@@ -54,10 +54,10 @@
5454
android:id="@+id/day"
5555
android:layout_width="wrap_content"
5656
android:layout_height="wrap_content"
57-
android:layout_marginTop="10dip"
58-
android:layout_marginBottom="10dip"
59-
android:layout_marginLeft="16dip"
60-
android:layout_marginRight="16dip"
57+
android:layout_marginTop="16dip"
58+
android:layout_marginBottom="16dip"
59+
android:layout_marginLeft="8dip"
60+
android:layout_marginRight="8dip"
6161
android:focusable="true"
6262
android:focusableInTouchMode="true"
6363
/>
@@ -67,9 +67,9 @@
6767
android:id="@+id/year"
6868
android:layout_width="wrap_content"
6969
android:layout_height="wrap_content"
70-
android:layout_marginTop="10dip"
71-
android:layout_marginBottom="10dip"
72-
android:layout_marginLeft="16dip"
70+
android:layout_marginTop="16dip"
71+
android:layout_marginBottom="16dip"
72+
android:layout_marginLeft="8dip"
7373
android:layout_marginRight="16dip"
7474
android:focusable="true"
7575
android:focusableInTouchMode="true"

0 commit comments

Comments
 (0)