项目需要实现预约功能,那么就需要获取用户预约的日期,让用户填写是不明智的选择,因为每个人的写法不一致,如有人喜欢写成“2019.01.01”,而有人喜欢写成“2019-01-01”,还有人喜欢写“2019 年 1 月 1 日”,这样混乱的格式存储在后台中会不方便检索。
那么使用日期选择器来统一格式无论是对用户还是对开发者而言都是更加友好的。
通常情况下我们会使用弹窗来展示日期选择器,我们利用 Android 中内置的控件就可以实现。
DatePicker
DatePicker
实际上就是一个单纯的日期选择器控件,为了使用弹窗展示,我们需要自定义一个 Dialog
来实现,可参考『Android 自定义 Dialog 布局』。
在布局中加入 DatePicker
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:padding="8dp">
<DatePicker
android:id="@+id/date_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
再设计其响应逻辑:
public static void showDatePicker(Context context) {
final Calendar calendar = Calendar.getInstance();
View datePickerView = LayoutInflater.from(context).inflate(R.layout.date_picker_dialog, null);
DatePicker datePicker = datePickerView.findViewById(R.id.date_picker);
datePicker.setMinDate(calendar.getTimeInMillis());
datePicker.init(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH), new DatePicker.OnDateChangedListener() {
@Override
public void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
calendar.set(year, monthOfYear, dayOfMonth);
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
textView.setText(format.format(calendar.getTime()));
// textView.setText(year + "-" + (monthOfYear + 1) + "-" + dayOfMonth);
}
});
final AlertDialog dialog = new AlertDialog.Builder(context).create();
dialog.setCanceledOnTouchOutside(false);
dialog.setCancelable(false);
dialog.setView(datePickerView);
dialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
dialog.show();
}
载入自定义布局后,调用 DatePicker.setMinDate()
方法来设置最小可选择的日期,同理,调用 DatePicker.setMaxDate()
方法来设置最大可选择的日期,另外还有如 DatePicker.setFirstDayOfWeek()
设置每周的第一天等各种配置,需要的时候查查文档即可。
接着就是对 DatePicker
的初始化操作,DatePicker.init()
接收 4 个参数,分别是默认的年月日和 DatePicker
被修改时响应的事件,默认的年月日一般设定为当前时间,响应事件为了方便演示我就用 TextView
来展示这个日期,这里需要注意,建议使用 SimpleDateFormat
来格式化日期,因为月份的计数是从 0
开始的,所以如果直接使用的话要记得加 1
。
DatePicker
虽然有单独的 setOnDateChangedListener()
方法,但其要求 API 26 才能够使用:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
datePicker.setOnDateChangedListener(new DatePicker.OnDateChangedListener() {
@Override
public void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
calendar.set(year, monthOfYear, dayOfMonth);
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
textView.setText(format.format(calendar.getTime()));
}
});
}
如果仅设定了响应事件而没有初始化默认日期,DatePicker
会把当前日期设为默认日期。
然后是关于 AlertDialog
的一些设置,我设置为不可以任何方式取消,并加入一个 PositiveButton
关闭这个 AlertDialog
,原因是当用户点击日期的时候 DatePicker
就已响应,而当响应结束之后,没有任何提示告诉用户已经处理完该事件,除非用户点击返回或其他地方关闭该 AlertDialog
,所以禁用取消以及使用该 PositiveButton
关闭 AlertDialog
是为了更加友好的交互,当然你也可以在用户点击日期后立即关闭,但我认为这样假如用户误触后的修正步骤变长了。
效果如下:
另外,使用 DatePicker
时建议先在需要用到的地方设定一个默认日期,一般也就是 DatePicker.init()
中设定的日期,因为 DatePicker
仅在日期改变时才会作出响应,如果用户打开 DatePicker
日期选择器后没有点击操作而是直接关闭日期选择器(因为有选择器上默认日期)时,响应事件内的逻辑将得不到执行,也就无法获取到选中的日期(即默认的日期)。如图:
除了默认的日历卡样式外,DatePicker
还提供了常见的滚轮样式:
只需在控件中加入两行属性代码即可:
<DatePicker
...
android:calendarViewShown="false"
android:datePickerMode="spinner" />
意思就是指定选择器样式为滚轮样式并隐藏日历卡,如果仅设置滚轮样式而不隐藏日历卡样式的话默认是会两个同时存在的:
一来没有必要,二来也太丑,特别在竖屏时控件就挤到变形,影响体验。
DatePickerDialog
再来看看 DatePickerDialog
,望文生义,DatePickerDialog
就是一个包含了 DatePicker
的对话框,也就是说 Android 帮我们把上面的封装完成了,实际上 DatePickerDialog
在 API 24 才被加进来,使用它有一个好处,就是不需要写布局文件。
public static void showDatePickerDialog(Context context) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
final Calendar calendar = Calendar.getInstance();
DatePickerDialog dialog = new DatePickerDialog(context);
dialog.setOnDateSetListener(new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
calendar.set(year, month, dayOfMonth);
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
textView.setText(format.format(calendar.getTime()));
// textView.setText(year + "-" + (monthOfYear + 1) + "-" + dayOfMonth);
}
});
DatePicker datePicker = dialog.getDatePicker();
datePicker.setMinDate(calendar.getTimeInMillis());
dialog.show();
}
}
与 DatePicker
不同,DatePickerDialog
有多个构造方法,可以不设定默认的日期,当不设定时,DatePickerDialog
会把当前时间设为默认日期。
且 DatePickerDialog
继承自 AlertDialog
,所以会有 PositiveButton
和 NegativeButton
,因此响应事件回调不再是 onDateChanged()
而是 onDateSet()
,即用户点击 PositiveButton
时才会响应事件,把选中的日期回传,这样就不会遇到像 DatePicker
未点击所以获取不到日期的尴尬情况。
但 DatePickerDialog
比较不方便的是不能够直接设置最小可选日期,所以需要借助 DatePicker
来实现,通过 DatePickerDialog.getDatePicker()
就可以获得 DatePick
实例,再调用 DatePicker
的方法就可以进行相关的操作。
效果如下:
DatePickerDialog
因为不需要布局文件来构建,所以没有像 DatePicker
一样可以切换为滚轮样式的选项。
最后需要提醒的是,由于兼容性问题原生的日期选择器在不同的 Android 版本中样式不一,在 Android 4.x、Android 5.x 以及 Android 6.0 以上的版本中有很大的出入:
而且原生的日期选择器本身可定制的 API 也较少,如果介意的话请慎用。