Skip to content

Commit ec1e06a

Browse files
committed
Updating NumberPicker, TimePicker, DatePicker to fit different screen and font sizes.
1. Now the NumberPicker has minWidth/minHeight that is the lower bound of the correspodning size for which the widget looks well enough to be usable. There is also maxWidth/masHeight that is the upper bound of the corresponding size for which the widget looks best. The picker tries to greedily reach the max dimesions for which it looks best. 2. The NumberPicker was not taking care of the max width of the items is shows numbers/strings mapped to numbers. Now if not explicitly specified the widget computes the maxWidth at which it looks best based on the content it shows. 3. Removed an unnecessary layout for number picker on tablets. 4. Updated the TimePicker/DatePicker to not hard-code width for the number pickers it uses, rahter wrap the content. bug:5417100 Change-Id: I432aa96185961e59a058a2565b15265ba7394818
1 parent 2333a02 commit ec1e06a

File tree

8 files changed

+151
-121
lines changed

8 files changed

+151
-121
lines changed

core/java/android/widget/NumberPicker.java

Lines changed: 116 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,11 @@ public String format(int value) {
202202
*/
203203
private final EditText mInputText;
204204

205+
/**
206+
* The min height of this widget.
207+
*/
208+
private final int mMinHeight;
209+
205210
/**
206211
* The max height of this widget.
207212
*/
@@ -210,7 +215,17 @@ public String format(int value) {
210215
/**
211216
* The max width of this widget.
212217
*/
213-
private final int mMaxWidth;
218+
private final int mMinWidth;
219+
220+
/**
221+
* The max width of this widget.
222+
*/
223+
private int mMaxWidth;
224+
225+
/**
226+
* Flag whether to compute the max width.
227+
*/
228+
private final boolean mComputeMaxWidth;
214229

215230
/**
216231
* The height of the text.
@@ -527,8 +542,19 @@ public NumberPicker(Context context, AttributeSet attrs, int defStyle) {
527542
getResources().getDisplayMetrics());
528543
mSelectionDividerHeight = attributesArray.getDimensionPixelSize(
529544
R.styleable.NumberPicker_selectionDividerHeight, defSelectionDividerHeight);
530-
mMaxHeight = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_maxHeight, 0);
531-
mMaxWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_maxWidth, 0);
545+
mMinHeight = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_minHeight, 0);
546+
mMaxHeight = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_maxHeight,
547+
Integer.MAX_VALUE);
548+
if (mMinHeight > mMaxHeight) {
549+
throw new IllegalArgumentException("minHeight > maxHeight");
550+
}
551+
mMinWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_minWidth, 0);
552+
mMaxWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_maxWidth,
553+
Integer.MAX_VALUE);
554+
if (mMinWidth > mMaxWidth) {
555+
throw new IllegalArgumentException("minWidth > maxWidth");
556+
}
557+
mComputeMaxWidth = (mMaxWidth == Integer.MAX_VALUE);
532558
attributesArray.recycle();
533559

534560
mShowInputControlsAnimimationDuration = getResources().getInteger(
@@ -677,37 +703,33 @@ public void onAnimationCancel(Animator animation) {
677703

678704
@Override
679705
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
680-
if (mMaxHeight <= 0 && mMaxWidth <= 0) {
681-
super.onLayout(changed, left, top, right, bottom);
682-
} else {
683-
final int msrdWdth = getMeasuredWidth();
684-
final int msrdHght = getMeasuredHeight();
685-
686-
// Increment button at the top.
687-
final int inctBtnMsrdWdth = mIncrementButton.getMeasuredWidth();
688-
final int incrBtnLeft = (msrdWdth - inctBtnMsrdWdth) / 2;
689-
final int incrBtnTop = 0;
690-
final int incrBtnRight = incrBtnLeft + inctBtnMsrdWdth;
691-
final int incrBtnBottom = incrBtnTop + mIncrementButton.getMeasuredHeight();
692-
mIncrementButton.layout(incrBtnLeft, incrBtnTop, incrBtnRight, incrBtnBottom);
693-
694-
// Input text centered horizontally.
695-
final int inptTxtMsrdWdth = mInputText.getMeasuredWidth();
696-
final int inptTxtMsrdHght = mInputText.getMeasuredHeight();
697-
final int inptTxtLeft = (msrdWdth - inptTxtMsrdWdth) / 2;
698-
final int inptTxtTop = (msrdHght - inptTxtMsrdHght) / 2;
699-
final int inptTxtRight = inptTxtLeft + inptTxtMsrdWdth;
700-
final int inptTxtBottom = inptTxtTop + inptTxtMsrdHght;
701-
mInputText.layout(inptTxtLeft, inptTxtTop, inptTxtRight, inptTxtBottom);
702-
703-
// Decrement button at the top.
704-
final int decrBtnMsrdWdth = mIncrementButton.getMeasuredWidth();
705-
final int decrBtnLeft = (msrdWdth - decrBtnMsrdWdth) / 2;
706-
final int decrBtnTop = msrdHght - mDecrementButton.getMeasuredHeight();
707-
final int decrBtnRight = decrBtnLeft + decrBtnMsrdWdth;
708-
final int decrBtnBottom = msrdHght;
709-
mDecrementButton.layout(decrBtnLeft, decrBtnTop, decrBtnRight, decrBtnBottom);
710-
}
706+
final int msrdWdth = getMeasuredWidth();
707+
final int msrdHght = getMeasuredHeight();
708+
709+
// Increment button at the top.
710+
final int inctBtnMsrdWdth = mIncrementButton.getMeasuredWidth();
711+
final int incrBtnLeft = (msrdWdth - inctBtnMsrdWdth) / 2;
712+
final int incrBtnTop = 0;
713+
final int incrBtnRight = incrBtnLeft + inctBtnMsrdWdth;
714+
final int incrBtnBottom = incrBtnTop + mIncrementButton.getMeasuredHeight();
715+
mIncrementButton.layout(incrBtnLeft, incrBtnTop, incrBtnRight, incrBtnBottom);
716+
717+
// Input text centered horizontally.
718+
final int inptTxtMsrdWdth = mInputText.getMeasuredWidth();
719+
final int inptTxtMsrdHght = mInputText.getMeasuredHeight();
720+
final int inptTxtLeft = (msrdWdth - inptTxtMsrdWdth) / 2;
721+
final int inptTxtTop = (msrdHght - inptTxtMsrdHght) / 2;
722+
final int inptTxtRight = inptTxtLeft + inptTxtMsrdWdth;
723+
final int inptTxtBottom = inptTxtTop + inptTxtMsrdHght;
724+
mInputText.layout(inptTxtLeft, inptTxtTop, inptTxtRight, inptTxtBottom);
725+
726+
// Decrement button at the top.
727+
final int decrBtnMsrdWdth = mIncrementButton.getMeasuredWidth();
728+
final int decrBtnLeft = (msrdWdth - decrBtnMsrdWdth) / 2;
729+
final int decrBtnTop = msrdHght - mDecrementButton.getMeasuredHeight();
730+
final int decrBtnRight = decrBtnLeft + decrBtnMsrdWdth;
731+
final int decrBtnBottom = msrdHght;
732+
mDecrementButton.layout(decrBtnLeft, decrBtnTop, decrBtnRight, decrBtnBottom);
711733

712734
if (!mScrollWheelAndFadingEdgesInitialized) {
713735
mScrollWheelAndFadingEdgesInitialized = true;
@@ -719,20 +741,9 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto
719741

720742
@Override
721743
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
722-
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
723-
final int measuredWidth;
724-
if (mMaxWidth > 0) {
725-
measuredWidth = getMaxSize(widthMeasureSpec, mMaxWidth);
726-
} else {
727-
measuredWidth = getMeasuredWidth();
728-
}
729-
final int measuredHeight;
730-
if (mMaxHeight > 0) {
731-
measuredHeight = getMaxSize(heightMeasureSpec, mMaxHeight);
732-
} else {
733-
measuredHeight = getMeasuredHeight();
734-
}
735-
setMeasuredDimension(measuredWidth, measuredHeight);
744+
final int newWidthMeasureSpec = makeMeasureSpec(widthMeasureSpec, mMinWidth, mMaxWidth);
745+
final int newHeightMeasureSpec = makeMeasureSpec(heightMeasureSpec, mMinHeight, mMaxHeight);
746+
super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec);
736747
}
737748

738749
@Override
@@ -1033,6 +1044,49 @@ public void setValue(int value) {
10331044
invalidate();
10341045
}
10351046

1047+
/**
1048+
* Computes the max width if no such specified as an attribute.
1049+
*/
1050+
private void tryComputeMaxWidth() {
1051+
if (!mComputeMaxWidth) {
1052+
return;
1053+
}
1054+
int maxTextWidth = 0;
1055+
if (mDisplayedValues == null) {
1056+
float maxDigitWidth = 0;
1057+
for (int i = 0; i <= 9; i++) {
1058+
final float digitWidth = mSelectorWheelPaint.measureText(String.valueOf(i));
1059+
if (digitWidth > maxDigitWidth) {
1060+
maxDigitWidth = digitWidth;
1061+
}
1062+
}
1063+
int numberOfDigits = 0;
1064+
int current = mMaxValue;
1065+
while (current > 0) {
1066+
numberOfDigits++;
1067+
current = current / 10;
1068+
}
1069+
maxTextWidth = (int) (numberOfDigits * maxDigitWidth);
1070+
} else {
1071+
final int valueCount = mDisplayedValues.length;
1072+
for (int i = 0; i < valueCount; i++) {
1073+
final float textWidth = mSelectorWheelPaint.measureText(mDisplayedValues[i]);
1074+
if (textWidth > maxTextWidth) {
1075+
maxTextWidth = (int) textWidth;
1076+
}
1077+
}
1078+
}
1079+
maxTextWidth += mInputText.getPaddingLeft() + mInputText.getPaddingRight();
1080+
if (mMaxWidth != maxTextWidth) {
1081+
if (maxTextWidth > mMinWidth) {
1082+
mMaxWidth = maxTextWidth;
1083+
} else {
1084+
mMaxWidth = mMinWidth;
1085+
}
1086+
invalidate();
1087+
}
1088+
}
1089+
10361090
/**
10371091
* Gets whether the selector wheel wraps when reaching the min/max value.
10381092
*
@@ -1119,6 +1173,7 @@ public void setMinValue(int minValue) {
11191173
setWrapSelectorWheel(wrapSelectorWheel);
11201174
initializeSelectorWheelIndices();
11211175
updateInputTextView();
1176+
tryComputeMaxWidth();
11221177
}
11231178

11241179
/**
@@ -1150,6 +1205,7 @@ public void setMaxValue(int maxValue) {
11501205
setWrapSelectorWheel(wrapSelectorWheel);
11511206
initializeSelectorWheelIndices();
11521207
updateInputTextView();
1208+
tryComputeMaxWidth();
11531209
}
11541210

11551211
/**
@@ -1298,24 +1354,28 @@ public void sendAccessibilityEvent(int eventType) {
12981354
}
12991355

13001356
/**
1301-
* Gets the max value for a size based on the measure spec passed by
1302-
* the parent and the max value for that size.
1357+
* Makes a measure spec that tries greedily to use the max value.
13031358
*
13041359
* @param measureSpec The measure spec.
13051360
* @param maxValue The max value for the size.
1306-
* @return The max size.
1361+
* @return A measure spec greedily imposing the max size.
13071362
*/
1308-
private int getMaxSize(int measureSpec, int maxValue) {
1363+
private int makeMeasureSpec(int measureSpec, int minValue, int maxValue) {
1364+
final int size = MeasureSpec.getSize(measureSpec);
1365+
if (size < minValue) {
1366+
throw new IllegalArgumentException("Available space is less than min size: "
1367+
+ size + " < " + minValue);
1368+
}
13091369
final int mode = MeasureSpec.getMode(measureSpec);
13101370
switch (mode) {
13111371
case MeasureSpec.EXACTLY:
1312-
return MeasureSpec.getSize(measureSpec);
1372+
return measureSpec;
13131373
case MeasureSpec.AT_MOST:
1314-
return Math.min(MeasureSpec.getSize(measureSpec), maxValue);
1374+
return MeasureSpec.makeMeasureSpec(Math.min(size, maxValue), MeasureSpec.EXACTLY);
13151375
case MeasureSpec.UNSPECIFIED:
1316-
return maxValue;
1376+
return MeasureSpec.makeMeasureSpec(maxValue, MeasureSpec.EXACTLY);
13171377
default:
1318-
throw new IllegalArgumentException();
1378+
throw new IllegalArgumentException("Unknown measure mode: " + mode);
13191379
}
13201380
}
13211381

core/res/res/layout-sw600dp/date_picker_dialog.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,6 @@
2222
android:layout_gravity="center_horizontal"
2323
android:layout_width="wrap_content"
2424
android:layout_height="wrap_content"
25+
android:spinnersShown="true"
26+
android:calendarViewShown="true"
2527
/>

core/res/res/layout-sw600dp/number_picker.xml

Lines changed: 0 additions & 39 deletions
This file was deleted.

core/res/res/layout/date_picker.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
<!-- Month -->
4141
<NumberPicker
4242
android:id="@+id/month"
43-
android:layout_width="80dip"
43+
android:layout_width="wrap_content"
4444
android:layout_height="wrap_content"
4545
android:layout_marginLeft="1dip"
4646
android:layout_marginRight="1dip"
@@ -51,7 +51,7 @@
5151
<!-- Day -->
5252
<NumberPicker
5353
android:id="@+id/day"
54-
android:layout_width="80dip"
54+
android:layout_width="wrap_content"
5555
android:layout_height="wrap_content"
5656
android:layout_marginLeft="1dip"
5757
android:layout_marginRight="1dip"
@@ -62,7 +62,7 @@
6262
<!-- Year -->
6363
<NumberPicker
6464
android:id="@+id/year"
65-
android:layout_width="95dip"
65+
android:layout_width="wrap_content"
6666
android:layout_height="wrap_content"
6767
android:layout_marginLeft="1dip"
6868
android:layout_marginRight="1dip"

core/res/res/layout/date_picker_holo.xml

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,49 +23,48 @@
2323
depending on the date format selected by the user.
2424
-->
2525
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
26-
android:layout_width="fill_parent"
27-
android:layout_height="fill_parent"
26+
android:layout_width="wrap_content"
27+
android:layout_height="wrap_content"
2828
android:layout_gravity="center_horizontal"
2929
android:orientation="horizontal"
3030
android:gravity="center">
3131

3232
<LinearLayout android:id="@+id/pickers"
3333
android:layout_width="wrap_content"
3434
android:layout_height="wrap_content"
35-
android:layout_marginRight="22dip"
3635
android:layout_weight="1"
3736
android:orientation="horizontal"
3837
android:gravity="center">
3938

4039
<!-- Month -->
4140
<NumberPicker
4241
android:id="@+id/month"
43-
android:layout_width="48dip"
42+
android:layout_width="wrap_content"
4443
android:layout_height="wrap_content"
45-
android:layout_marginLeft="22dip"
46-
android:layout_marginRight="22dip"
44+
android:layout_marginLeft="16dip"
45+
android:layout_marginRight="16dip"
4746
android:focusable="true"
4847
android:focusableInTouchMode="true"
4948
/>
5049

5150
<!-- Day -->
5251
<NumberPicker
5352
android:id="@+id/day"
54-
android:layout_width="48dip"
53+
android:layout_width="wrap_content"
5554
android:layout_height="wrap_content"
56-
android:layout_marginLeft="22dip"
57-
android:layout_marginRight="22dip"
55+
android:layout_marginLeft="16dip"
56+
android:layout_marginRight="16dip"
5857
android:focusable="true"
5958
android:focusableInTouchMode="true"
6059
/>
6160

6261
<!-- Year -->
6362
<NumberPicker
6463
android:id="@+id/year"
65-
android:layout_width="48dip"
64+
android:layout_width="wrap_content"
6665
android:layout_height="wrap_content"
67-
android:layout_marginLeft="22dip"
68-
android:layout_marginRight="22dip"
66+
android:layout_marginLeft="16dip"
67+
android:layout_marginRight="16dip"
6968
android:focusable="true"
7069
android:focusableInTouchMode="true"
7170
/>

0 commit comments

Comments
 (0)