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
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index 320c650..5ab99dc 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -203,6 +203,11 @@
private final EditText mInputText;
/**
+ * The min height of this widget.
+ */
+ private final int mMinHeight;
+
+ /**
* The max height of this widget.
*/
private final int mMaxHeight;
@@ -210,7 +215,17 @@
/**
* The max width of this widget.
*/
- private final int mMaxWidth;
+ private final int mMinWidth;
+
+ /**
+ * The max width of this widget.
+ */
+ private int mMaxWidth;
+
+ /**
+ * Flag whether to compute the max width.
+ */
+ private final boolean mComputeMaxWidth;
/**
* The height of the text.
@@ -527,8 +542,19 @@
getResources().getDisplayMetrics());
mSelectionDividerHeight = attributesArray.getDimensionPixelSize(
R.styleable.NumberPicker_selectionDividerHeight, defSelectionDividerHeight);
- mMaxHeight = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_maxHeight, 0);
- mMaxWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_maxWidth, 0);
+ mMinHeight = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_minHeight, 0);
+ mMaxHeight = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_maxHeight,
+ Integer.MAX_VALUE);
+ if (mMinHeight > mMaxHeight) {
+ throw new IllegalArgumentException("minHeight > maxHeight");
+ }
+ mMinWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_minWidth, 0);
+ mMaxWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_maxWidth,
+ Integer.MAX_VALUE);
+ if (mMinWidth > mMaxWidth) {
+ throw new IllegalArgumentException("minWidth > maxWidth");
+ }
+ mComputeMaxWidth = (mMaxWidth == Integer.MAX_VALUE);
attributesArray.recycle();
mShowInputControlsAnimimationDuration = getResources().getInteger(
@@ -677,37 +703,33 @@
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- if (mMaxHeight <= 0 && mMaxWidth <= 0) {
- super.onLayout(changed, left, top, right, bottom);
- } else {
- final int msrdWdth = getMeasuredWidth();
- final int msrdHght = getMeasuredHeight();
+ final int msrdWdth = getMeasuredWidth();
+ final int msrdHght = getMeasuredHeight();
- // Increment button at the top.
- final int inctBtnMsrdWdth = mIncrementButton.getMeasuredWidth();
- final int incrBtnLeft = (msrdWdth - inctBtnMsrdWdth) / 2;
- final int incrBtnTop = 0;
- final int incrBtnRight = incrBtnLeft + inctBtnMsrdWdth;
- final int incrBtnBottom = incrBtnTop + mIncrementButton.getMeasuredHeight();
- mIncrementButton.layout(incrBtnLeft, incrBtnTop, incrBtnRight, incrBtnBottom);
+ // Increment button at the top.
+ final int inctBtnMsrdWdth = mIncrementButton.getMeasuredWidth();
+ final int incrBtnLeft = (msrdWdth - inctBtnMsrdWdth) / 2;
+ final int incrBtnTop = 0;
+ final int incrBtnRight = incrBtnLeft + inctBtnMsrdWdth;
+ final int incrBtnBottom = incrBtnTop + mIncrementButton.getMeasuredHeight();
+ mIncrementButton.layout(incrBtnLeft, incrBtnTop, incrBtnRight, incrBtnBottom);
- // Input text centered horizontally.
- final int inptTxtMsrdWdth = mInputText.getMeasuredWidth();
- final int inptTxtMsrdHght = mInputText.getMeasuredHeight();
- final int inptTxtLeft = (msrdWdth - inptTxtMsrdWdth) / 2;
- final int inptTxtTop = (msrdHght - inptTxtMsrdHght) / 2;
- final int inptTxtRight = inptTxtLeft + inptTxtMsrdWdth;
- final int inptTxtBottom = inptTxtTop + inptTxtMsrdHght;
- mInputText.layout(inptTxtLeft, inptTxtTop, inptTxtRight, inptTxtBottom);
+ // Input text centered horizontally.
+ final int inptTxtMsrdWdth = mInputText.getMeasuredWidth();
+ final int inptTxtMsrdHght = mInputText.getMeasuredHeight();
+ final int inptTxtLeft = (msrdWdth - inptTxtMsrdWdth) / 2;
+ final int inptTxtTop = (msrdHght - inptTxtMsrdHght) / 2;
+ final int inptTxtRight = inptTxtLeft + inptTxtMsrdWdth;
+ final int inptTxtBottom = inptTxtTop + inptTxtMsrdHght;
+ mInputText.layout(inptTxtLeft, inptTxtTop, inptTxtRight, inptTxtBottom);
- // Decrement button at the top.
- final int decrBtnMsrdWdth = mIncrementButton.getMeasuredWidth();
- final int decrBtnLeft = (msrdWdth - decrBtnMsrdWdth) / 2;
- final int decrBtnTop = msrdHght - mDecrementButton.getMeasuredHeight();
- final int decrBtnRight = decrBtnLeft + decrBtnMsrdWdth;
- final int decrBtnBottom = msrdHght;
- mDecrementButton.layout(decrBtnLeft, decrBtnTop, decrBtnRight, decrBtnBottom);
- }
+ // Decrement button at the top.
+ final int decrBtnMsrdWdth = mIncrementButton.getMeasuredWidth();
+ final int decrBtnLeft = (msrdWdth - decrBtnMsrdWdth) / 2;
+ final int decrBtnTop = msrdHght - mDecrementButton.getMeasuredHeight();
+ final int decrBtnRight = decrBtnLeft + decrBtnMsrdWdth;
+ final int decrBtnBottom = msrdHght;
+ mDecrementButton.layout(decrBtnLeft, decrBtnTop, decrBtnRight, decrBtnBottom);
if (!mScrollWheelAndFadingEdgesInitialized) {
mScrollWheelAndFadingEdgesInitialized = true;
@@ -719,20 +741,9 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- final int measuredWidth;
- if (mMaxWidth > 0) {
- measuredWidth = getMaxSize(widthMeasureSpec, mMaxWidth);
- } else {
- measuredWidth = getMeasuredWidth();
- }
- final int measuredHeight;
- if (mMaxHeight > 0) {
- measuredHeight = getMaxSize(heightMeasureSpec, mMaxHeight);
- } else {
- measuredHeight = getMeasuredHeight();
- }
- setMeasuredDimension(measuredWidth, measuredHeight);
+ final int newWidthMeasureSpec = makeMeasureSpec(widthMeasureSpec, mMinWidth, mMaxWidth);
+ final int newHeightMeasureSpec = makeMeasureSpec(heightMeasureSpec, mMinHeight, mMaxHeight);
+ super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec);
}
@Override
@@ -1034,6 +1045,49 @@
}
/**
+ * Computes the max width if no such specified as an attribute.
+ */
+ private void tryComputeMaxWidth() {
+ if (!mComputeMaxWidth) {
+ return;
+ }
+ int maxTextWidth = 0;
+ if (mDisplayedValues == null) {
+ float maxDigitWidth = 0;
+ for (int i = 0; i <= 9; i++) {
+ final float digitWidth = mSelectorWheelPaint.measureText(String.valueOf(i));
+ if (digitWidth > maxDigitWidth) {
+ maxDigitWidth = digitWidth;
+ }
+ }
+ int numberOfDigits = 0;
+ int current = mMaxValue;
+ while (current > 0) {
+ numberOfDigits++;
+ current = current / 10;
+ }
+ maxTextWidth = (int) (numberOfDigits * maxDigitWidth);
+ } else {
+ final int valueCount = mDisplayedValues.length;
+ for (int i = 0; i < valueCount; i++) {
+ final float textWidth = mSelectorWheelPaint.measureText(mDisplayedValues[i]);
+ if (textWidth > maxTextWidth) {
+ maxTextWidth = (int) textWidth;
+ }
+ }
+ }
+ maxTextWidth += mInputText.getPaddingLeft() + mInputText.getPaddingRight();
+ if (mMaxWidth != maxTextWidth) {
+ if (maxTextWidth > mMinWidth) {
+ mMaxWidth = maxTextWidth;
+ } else {
+ mMaxWidth = mMinWidth;
+ }
+ invalidate();
+ }
+ }
+
+ /**
* Gets whether the selector wheel wraps when reaching the min/max value.
*
* @return True if the selector wheel wraps.
@@ -1119,6 +1173,7 @@
setWrapSelectorWheel(wrapSelectorWheel);
initializeSelectorWheelIndices();
updateInputTextView();
+ tryComputeMaxWidth();
}
/**
@@ -1150,6 +1205,7 @@
setWrapSelectorWheel(wrapSelectorWheel);
initializeSelectorWheelIndices();
updateInputTextView();
+ tryComputeMaxWidth();
}
/**
@@ -1298,24 +1354,28 @@
}
/**
- * Gets the max value for a size based on the measure spec passed by
- * the parent and the max value for that size.
+ * Makes a measure spec that tries greedily to use the max value.
*
* @param measureSpec The measure spec.
* @param maxValue The max value for the size.
- * @return The max size.
+ * @return A measure spec greedily imposing the max size.
*/
- private int getMaxSize(int measureSpec, int maxValue) {
+ private int makeMeasureSpec(int measureSpec, int minValue, int maxValue) {
+ final int size = MeasureSpec.getSize(measureSpec);
+ if (size < minValue) {
+ throw new IllegalArgumentException("Available space is less than min size: "
+ + size + " < " + minValue);
+ }
final int mode = MeasureSpec.getMode(measureSpec);
switch (mode) {
case MeasureSpec.EXACTLY:
- return MeasureSpec.getSize(measureSpec);
+ return measureSpec;
case MeasureSpec.AT_MOST:
- return Math.min(MeasureSpec.getSize(measureSpec), maxValue);
+ return MeasureSpec.makeMeasureSpec(Math.min(size, maxValue), MeasureSpec.EXACTLY);
case MeasureSpec.UNSPECIFIED:
- return maxValue;
+ return MeasureSpec.makeMeasureSpec(maxValue, MeasureSpec.EXACTLY);
default:
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException("Unknown measure mode: " + mode);
}
}
diff --git a/core/res/res/layout-sw600dp/date_picker_dialog.xml b/core/res/res/layout-sw600dp/date_picker_dialog.xml
index 004d52a..f9b247f 100644
--- a/core/res/res/layout-sw600dp/date_picker_dialog.xml
+++ b/core/res/res/layout-sw600dp/date_picker_dialog.xml
@@ -22,4 +22,6 @@
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:spinnersShown="true"
+ android:calendarViewShown="true"
/>
diff --git a/core/res/res/layout-sw600dp/number_picker.xml b/core/res/res/layout-sw600dp/number_picker.xml
deleted file mode 100644
index 807daf2..0000000
--- a/core/res/res/layout-sw600dp/number_picker.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2008, 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.
-*/
--->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-
- <ImageButton android:id="@+id/increment"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- style="?android:attr/numberPickerUpButtonStyle"
- android:contentDescription="@string/number_picker_increment_button" />
-
- <EditText android:id="@+id/numberpicker_input"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- style="?android:attr/numberPickerInputTextStyle" />
-
- <ImageButton android:id="@+id/decrement"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- style="?android:attr/numberPickerDownButtonStyle"
- android:contentDescription="@string/number_picker_decrement_button" />
-
-</merge>
diff --git a/core/res/res/layout/date_picker.xml b/core/res/res/layout/date_picker.xml
index 1649466..6f0517d 100644
--- a/core/res/res/layout/date_picker.xml
+++ b/core/res/res/layout/date_picker.xml
@@ -40,7 +40,7 @@
<!-- Month -->
<NumberPicker
android:id="@+id/month"
- android:layout_width="80dip"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="1dip"
android:layout_marginRight="1dip"
@@ -51,7 +51,7 @@
<!-- Day -->
<NumberPicker
android:id="@+id/day"
- android:layout_width="80dip"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="1dip"
android:layout_marginRight="1dip"
@@ -62,7 +62,7 @@
<!-- Year -->
<NumberPicker
android:id="@+id/year"
- android:layout_width="95dip"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="1dip"
android:layout_marginRight="1dip"
diff --git a/core/res/res/layout/date_picker_holo.xml b/core/res/res/layout/date_picker_holo.xml
index 8627637..57b5614 100644
--- a/core/res/res/layout/date_picker_holo.xml
+++ b/core/res/res/layout/date_picker_holo.xml
@@ -23,8 +23,8 @@
depending on the date format selected by the user.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="horizontal"
android:gravity="center">
@@ -32,7 +32,6 @@
<LinearLayout android:id="@+id/pickers"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginRight="22dip"
android:layout_weight="1"
android:orientation="horizontal"
android:gravity="center">
@@ -40,10 +39,10 @@
<!-- Month -->
<NumberPicker
android:id="@+id/month"
- android:layout_width="48dip"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginLeft="22dip"
- android:layout_marginRight="22dip"
+ android:layout_marginLeft="16dip"
+ android:layout_marginRight="16dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
@@ -51,10 +50,10 @@
<!-- Day -->
<NumberPicker
android:id="@+id/day"
- android:layout_width="48dip"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginLeft="22dip"
- android:layout_marginRight="22dip"
+ android:layout_marginLeft="16dip"
+ android:layout_marginRight="16dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
@@ -62,10 +61,10 @@
<!-- Year -->
<NumberPicker
android:id="@+id/year"
- android:layout_width="48dip"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginLeft="22dip"
- android:layout_marginRight="22dip"
+ android:layout_marginLeft="16dip"
+ android:layout_marginRight="16dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
diff --git a/core/res/res/layout/time_picker_holo.xml b/core/res/res/layout/time_picker_holo.xml
index ca6fe2d..29c97b7 100644
--- a/core/res/res/layout/time_picker_holo.xml
+++ b/core/res/res/layout/time_picker_holo.xml
@@ -28,10 +28,10 @@
<!-- hour -->
<NumberPicker
android:id="@+id/hour"
- android:layout_width="48dip"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginLeft="22dip"
- android:layout_marginRight="20dip"
+ android:layout_marginLeft="16dip"
+ android:layout_marginRight="14dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
@@ -47,10 +47,10 @@
<!-- minute -->
<NumberPicker
android:id="@+id/minute"
- android:layout_width="48dip"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginLeft="20dip"
- android:layout_marginRight="22dip"
+ android:layout_marginLeft="14dip"
+ android:layout_marginRight="16dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
@@ -58,10 +58,10 @@
<!-- AM / PM -->
<NumberPicker
android:id="@+id/amPm"
- android:layout_width="48dip"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginLeft="22dip"
- android:layout_marginRight="22dip"
+ android:layout_marginLeft="16dip"
+ android:layout_marginRight="16dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 65e11b4..b2f7b2d 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3572,8 +3572,14 @@
<attr name="selectionDivider" format="reference" />
<!-- @hide The height of the selection divider. -->
<attr name="selectionDividerHeight" format="dimension" />
+ <!-- @hide The min height of the NumberPicker. -->
+ <attr name="minHeight" />
<!-- @hide The max height of the NumberPicker. -->
<attr name="maxHeight" />
+ <!-- @hide The min width of the NumberPicker. -->
+ <attr name="minWidth" />
+ <!-- @hide The max width of the NumberPicker. -->
+ <attr name="maxWidth" />
<!-- @hide The max width of the NumberPicker. -->
<attr name="maxWidth" />
</declare-styleable>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index a4bdf23..db02dd7 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1646,8 +1646,8 @@
<item name="android:flingable">true</item>
<item name="android:selectionDivider">@android:drawable/numberpicker_selection_divider</item>
<item name="android:selectionDividerHeight">2dip</item>
- <item name="android:maxHeight">180dip</item>
- <item name="android:maxWidth">56dip</item>
+ <item name="android:minWidth">48dip</item>
+ <item name="android:maxHeight">200dip</item>
</style>
<style name="Widget.Holo.TimePicker" parent="Widget.TimePicker">
@@ -1676,6 +1676,8 @@
<style name="Widget.Holo.EditText.NumberPickerInputText">
<item name="android:paddingTop">13sp</item>
<item name="android:paddingBottom">13sp</item>
+ <item name="android:paddingLeft">2sp</item>
+ <item name="android:paddingRight">2sp</item>
<item name="android:gravity">center</item>
<item name="android:singleLine">true</item>
<item name="android:textSize">18sp</item>