项目中需要用到时间设置,想到了系统自带的datepicker控件,因为项目没有触屏需要用物理按键控制datepicker的滚动,并且年月日的顺序需要重排,折腾了好一会功夫,做个记录。
1.datepicker初始化
datePicker = findViewById(R.id.datepicker);
datePicker.setCalendarViewShown(false);
timePicker = findViewById(R.id.timepicker);
timePicker.setIs24HourView(true);
xml文件中设置Theme
android:theme="@android:style/Theme.Holo.Light.NoActionBar.Fullscreen" // 不设置就是显示日历
效果图如下:
2.修改年月日的顺序
我们知道,系统中文时datePicker是年月日(yymmdd)的顺序,系统英文时是月日年(mmddyy)上图,所以源码中datePicker会根据语言不同来进行重排序。
- 源码路径:frameworks/base/core/java/android/widget/DatePicker.java
int layoutResourceId = attributesArray.getResourceId(
R.styleable.DatePicker_legacyLayout, R.layout.date_picker_legacy);
attributesArray.recycle();
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE); // 获取inflater,还没这样用过,以后可以试试
inflater.inflate(layoutResourceId, mDelegator, true);
// 省略部分代码
mSpinners = (LinearLayout) mDelegator.findViewById(R.id.pickers);
找到了datePicker的布局文件 R.layout.date_picker_legacy,看看xml文件
- 源码路径:frameworks/base/core/res/res/layout/date_picker_legacy.xml
<!-- Layout of date picker-->
<!-- Warning: everything within the "pickers" layout is removed and re-ordered
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_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>
可以看到datePicker就是横向排列的三个NumberPicker数字选择器外加一个隐藏的CalendarView,注释也说了会根据日期格式重新排序,现在去找找排序的方法。
- 源码路径:frameworks/base/core/java/android/widget/DatePicker.java
/**
* Reorders the spinners according to the date format that is
* explicitly set by the user and if no such is set fall back
* to the current locale's default format.
*/
private void reorderSpinners() {
mSpinners.removeAllViews();
// We use numeric spinners for year and day, but textual months. Ask icu4c what
// order the user's locale uses for that combination. http://b/7207103.
String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "yyyyMMMdd");
char[] order = ICU.getDateFormatOrder(pattern);
final int spinnerCount = order.length;
for (int i = 0; i < spinnerCount; i++) {
switch (order[i]) {
case 'd':
mSpinners.addView(mDaySpinner);
setImeOptions(mDaySpinner, spinnerCount, i);
break;
case 'M':
mSpinners.addView(mMonthSpinner);
setImeOptions(mMonthSpinner, spinnerCount, i);
break;
case 'y':
mSpinners.addView(mYearSpinner);
setImeOptions(mYearSpinner, spinnerCount, i);
break;
default:
throw new IllegalArgumentException(Arrays.toString(order));
}
}
}
可以看到,它的操作是先remove所有子view,然后根据排序再重新add。
现在我们来重新排序年月日
datePicker = findViewById(R.id.datepicker);
datePicker.setCalendarViewShown(false);
timePicker = findViewById(R.id.timepicker);
timePicker.setIs24HourView(true);
// 可以在任何地方进行使用,但只能获取系统本身的资源。
Resources resources = Resources.getSystem();
// 获取布局LinearLayout
LinearLayout mSpinners = (LinearLayout) findViewById(resources.getIdentifier("pickers", "id", "android"));
if(mSpinners != null){
// 获取年月日numberpicker
yearPicker = (NumberPicker) datePicker.findViewById(resources.getIdentifier("year", "id", "android"));
monthPicker = (NumberPicker) datePicker.findViewById(resources.getIdentifier("month", "id", "android"));
dayPicker = (NumberPicker) datePicker.findViewById(resources.getIdentifier("day", "id", "android"));
/*重新排列datepicker年月日的顺序*/
mSpinners.removeAllViews();
mSpinners.addView(yearPicker);
mSpinners.addView(monthPicker);
mSpinners.addView(dayPicker);
// 想要隐藏某个picker
// dayPicker.setVisibility(View.GONE);
}
使用getIdentifier()获取资源 链接地址
效果图如下:
3.使用物理按键控制numberpicker的滚动
NumberPicker可以滑动改变值,也可以上下点击改变数值,来看看numerpicker源码。
- 源码位置:frameworks/base/core/java/android/widget/NumberPicker.java
/**
* The resource id for the default layout.
*/
private static final int DEFAULT_LAYOUT_RESOURCE_ID = R.layout.number_picker;
- 源码位置:frameworks/base/core/res/res/layout/number_picker.xml
<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"
android:background="@android:drawable/numberpicker_up_btn"
android:paddingTop="22dip"
android:paddingBottom="22dip"
android:contentDescription="@string/number_picker_increment_button" />
<EditText
android:id="@+id/numberpicker_input"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Large.Inverse.NumberPickerInputText"
android:gravity="center"
android:singleLine="true"
android:background="@drawable/numberpicker_input" />
<ImageButton android:id="@+id/decrement"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@android:drawable/numberpicker_down_btn"
android:paddingTop="22dip"
android:paddingBottom="22dip"
android:contentDescription="@string/number_picker_decrement_button" />
</merge>
可以看到numberpicker是有上下各一个ImageButton 和中间的EditText组成,既然点击可以改变numberpicker的值,再来看看ImageButton的点击事件,还是在NumberPicker.java中。
// ImageButton的点击事件
OnClickListener onClickListener = new OnClickListener() {
public void onClick(View v) {
hideSoftInput();
mInputText.clearFocus();
if (v.getId() == R.id.increment) {
changeValueByOne(true);
} else {
changeValueByOne(false);
}
}
};
// 省略部分代码
/**
* Changes the current value by one which is increment or
* decrement based on the passes argument.
* decrement the current value.
*
* @param increment True to increment, false to decrement.
*/
private void changeValueByOne(boolean increment) {
if (mHasSelectorWheel) {
mInputText.setVisibility(View.INVISIBLE);
if (!moveToFinalScrollerPosition(mFlingScroller)) {
moveToFinalScrollerPosition(mAdjustScroller);
}
mPreviousScrollerY = 0;
if (increment) {
mFlingScroller.startScroll(0, 0, 0, -mSelectorElementHeight, SNAP_SCROLL_DURATION);
} else {
mFlingScroller.startScroll(0, 0, 0, mSelectorElementHeight, SNAP_SCROLL_DURATION);
}
invalidate();
} else {
if (increment) {
setValueInternal(mValue + 1, true);
} else {
setValueInternal(mValue - 1, true);
}
}
}
点击就是调用changeValueByOne方法,滑动也是,有兴趣的可以自己研究源码。这个方法是private的,所以只能用反射了,看代码
/**
* 控制numberpicker的滚动
* @param inrecment
*/
private void scrollPicker(boolean inrecment){
int index = getSelectedImageIndex();
NumberPicker picker = mPickerList.get(index);
try {
Class pickerClass = Class.forName(NumberPicker.class.getName());
Method deMethod = pickerClass.getDeclaredMethod("changeValueByOne",boolean.class);
deMethod.setAccessible(true);
deMethod.invoke(picker,inrecment);
Logger.d(deMethod);
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
完事。。。
附上一个在线阅读源码网站,各个版本都有 传送门