网上有很多优秀的开源时间控件,可以满足我们大部分的需求。
但有时候还是会碰到系统自带的DatePicker
,这个控件默认的月份显示为英文,如JANUARY、FEBRUARY等。
如何将月份由英文改成数字,这是个好玩的问题。
庖丁解牛
DatePicker
并没有提供相关api可以实现我们的需求,所以需要我们自己来分析。
查看DatePicke
的源码,它的布局文件是date_picker.xml
,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="center_horizontal"
android:orientation="horizontal"
android:gravity="center">
<LinearLayout android:id="@+id/pickers"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal"
android:gravity="center">
<!-- Month -->
<NumberPicker
android:id="@+id/month"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="1dip"
android:layout_marginEnd="1dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
<!-- Day -->
<NumberPicker
android:id="@+id/day"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="1dip"
android:layout_marginEnd="1dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
<!-- Year -->
<NumberPicker
android:id="@+id/year"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="1dip"
android:layout_marginEnd="1dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
</LinearLayout>
<!-- calendar view -->
<CalendarView
android:id="@+id/calendar_view"
android:layout_width="245dip"
android:layout_height="280dip"
android:layout_marginStart="44dip"
android:layout_weight="1"
android:focusable="true"
android:focusableInTouchMode="true"
android:visibility="gone"
/>
</LinearLayout>
可以很轻松地发现,月份就是一个id为month的NumberPicker
控件。
在DatePicker
中以month控件为线索,很快发现NumberPicker
下的这个方法:
/**
* Sets the values to be displayed.
*
* @param displayedValues The displayed values.
*/
public void setDisplayedValues(String[] displayedValues) {
...
...
...
}
该方法参数为一个String数组,该数组每一项就是展示的月份数据。
所以如果我们自己调用该方法,传入以数字表示的月份,就达到目的啦!
寻找调用时机
在DatePicker
源码中,直接调用setDisplayedValues
方法的地方有两处,
一处是在构造函数中:
public DatePicker(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
......
OnValueChangeListener onChangeListener = new OnValueChangeListener() {
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
......
updateSpinners();
updateCalendarView();
notifyDateChanged();
}
};
......
// month
mMonthSpinner = (NumberPicker) findViewById(R.id.month);
mMonthSpinner.setMinValue(0);
mMonthSpinner.setMaxValue(mNumberOfMonths - 1);
mMonthSpinner.setDisplayedValues(mShortMonths);
mMonthSpinner.setOnLongPressUpdateInterval(200);
mMonthSpinner.setOnValueChangedListener(onChangeListener);
mMonthSpinnerInput =(EditText)mMonthSpinner.findViewById(R.id.numberpicker_input);
......
}
mShortMonths
就是默认的英文月份,就不展开介绍了。
构造函数中在第X行调用了setDisplayedValues
方法,完成了展示月份数据的设置。
同时mMonthSpinner
的值监听器为onChangeListener
,当月份变化时会回调onValueChange
方法,该方法中又会调用updateSpinners
方法,这是另一处直接调用setDisplayedValues
方法的地方:
private void updateSpinners() {
......
// make sure the month names are a zero based array
// with the months in the month spinner
String[] displayedValues = Arrays.copyOfRange(mShortMonths,
mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1);
mMonthSpinner.setDisplayedValues(displayedValues);
......
}
而updateSpinners
方法除了会在上述的监听器中被调用,还要其他方法中也会调用,例如:
/**
* Initialize the state. If the provided values designate an inconsistent
* date the values are normalized before updating the spinners.
*
* @param year The initial year.
* @param monthOfYear The initial month <strong>starting from zero</strong>.
* @param dayOfMonth The initial day of the month.
* @param onDateChangedListener How user is notified date is changed by
* user, can be null.
*/
public void init(int year, int monthOfYear, int dayOfMonth,
OnDateChangedListener onDateChangedListener) {
setDate(year, monthOfYear, dayOfMonth);
updateSpinners();
updateCalendarView();
mOnDateChangedListener = onDateChangedListener;
}
/**
* Sets the maximal date supported by this {@link DatePicker} in
* milliseconds since January 1, 1970 00:00:00 in
* {@link TimeZone#getDefault()} time zone.
*
* @param maxDate The maximal supported date.
*/
public void setMaxDate(long maxDate) {
mTempDate.setTimeInMillis(maxDate);
if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
&& mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
return;
}
mMaxDate.setTimeInMillis(maxDate);
mCalendarView.setMaxDate(maxDate);
if (mCurrentDate.after(mMaxDate)) {
mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
updateCalendarView();
}
updateSpinners();
}
以上所有直接或间接调用setDisplayedValues
方法的地方都会将月份重置为英文显示,所以我们自己调用setDisplayedValues
方法的时机就是在这些方法被调用之后。
也就是说,我们的调用时机在DatePicker
构造函数之后、init()
、setMaxDate()
等方法之后和onDateChanged()
方法之中。
轻松实现
知道了在何时调用何方法后,我们来具体实现这一需求。
首先准备好展示的月份数组:
String months[] = {"1月", "2月", "3月", "4月", "5月", "6月",
"7月", "8月", "9月", "10月", "11月", "12月"};
假设mDatePicker
就是目标DatePicker
控件,因为DatePicker
没有提供可以获取到月份控件的api,所以需要自己想办法获取到月份控件,再调用它的setDisplayedValues
方法。
分析之前的date_picker.xml
文件,可以写出下面这行代码:
((NumberPicker) ((ViewGroup) ((ViewGroup) mDatePicker.getChildAt(0)).getChildAt(0)).getChildAt(0)).setDisplayedValues(months);
通过调用这行代码,就实现了替换掉英文月份的目标!