Skip to content

Commit 0891a89

Browse files
sganovAndroid (Google) Code Review
authored andcommitted
Merge "Polish the NumberPicker, TimePicker, and DatePicker based on UX request." into jb-dev
2 parents 822b72e + 232dd3f commit 0891a89

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)