日期时间选择是Android开发中非常常见的需求,比如注册页面选择生日、订票应用选择出发日期、闹钟应用设置提醒时间等场景都离不开它。Android SDK原生提供了DatePicker和TimePicker两个控件,配合DatePickerDialog和TimePickerDialog两个弹窗封装,基本可以覆盖绝大多数业务场景。这篇文章将从基础用法、事件监听、弹窗封装到进阶定制,带你完整掌握这两个控件的使用。

一、DatePicker和TimePicker的基础用法
DatePicker用于选择日期(年、月、日),TimePicker用于选择时间(时、分)。两者既可以在XML布局中直接声明,也可以在代码中动态创建。先看最简单的XML声明方式:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<DatePicker
android:id="@+id/datePicker"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TimePicker
android:id="@+id/timePicker"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
如果不想让用户直接操作控件,只想展示日期,可以设置android:datePickerMode="spinner"来切换为旧版的下拉样式(默认在新版本上是calendar日历模式)。TimePicker也有对应的android:timePickerMode属性,可选值同样是spinner和clock。
除了XML方式,也可以在Activity中通过代码创建并添加到布局中,这种方式适合需要动态生成多个选择器的场景,比如表单动态添加字段:
DatePicker datePicker = new DatePicker(this);
datePicker.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
layout.addView(datePicker);
二、监听用户选择的变化
光展示控件还不够,我们需要在用户改变日期或时间时拿到最新的值。TimePicker提供了setOnTimeChangedListener方法,用法比较直接:
TimePicker timePicker = findViewById(R.id.timePicker);
timePicker.setIs24HourView(true); // 设置为24小时制
timePicker.setOnTimeChangedListener(new TimePicker.OnTimeChangedListener() {
@Override
public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
Log.d("TimePicker", "当前选择:" + hourOfDay + ":" + minute);
}
});
DatePicker的监听在新旧API上有差异,这一点需要特别注意。在API 24之前的旧版本中,使用init(year, month, day, OnDateChangedListener)方法来初始化并设置监听;而API 24及以后,推荐直接调用setOnDateChangedListener。为了兼容性,一般项目中仍然使用init方式,它对所有版本都有效:
DatePicker datePicker = findViewById(R.id.datePicker);
Calendar calendar = Calendar.getInstance();
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) {
// 注意monthOfYear从0开始,显示时要加1
String date = year + "-" + (monthOfYear + 1) + "-" + dayOfMonth;
Log.d("DatePicker", "当前选择:" + date);
}
});
这里有一个非常经典的坑:monthOfYear返回的月份是从0开始计数的,也就是1月返回0、12月返回11。如果你直接拿这个值去拼接字符串展示,就会出现“2024-0-15”这种奇怪的结果。同样,通过getMonth()方法获取月份时也要记得加1。
三、使用DatePickerDialog和TimePickerDialog弹窗
在实际项目中,更常见的做法是点击某个输入框或按钮后弹出选择器对话框,而不是把控件常驻在页面上。Android提供了DatePickerDialog和TimePickerDialog两个现成的Dialog封装,几行代码就能搞定:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Calendar calendar = Calendar.getInstance();
new DatePickerDialog(MainActivity.this,
new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
String date = year + "-" + (month + 1) + "-" + dayOfMonth;
editText.setText(date);
}
},
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH))
.show();
}
});
TimePickerDialog的用法几乎一样,构造参数依次为上下文、OnTimeSetListener、默认小时、默认分钟以及是否24小时制的布尔值。回调方法onTimeSet中会直接返回用户选好的小时和分钟,不需要再去控件上取值。
弹窗方式的优势在于不占用页面布局空间,交互上更符合移动端的使用习惯。另外它自带取消和确认按钮,用户可以随时放弃修改。如果需要在用户取消时做一些处理,可以重写Dialog的onCancel方法,或者使用setButton自定义按钮行为。
四、进阶技巧与常见问题处理
1. 获取当前选择的值
除了在监听回调中拿值,也可以随时主动从控件上读取当前的值。DatePicker提供getYear()、getMonth()、getDayOfMonth()三个方法,TimePicker提供getCurrentHour()、getCurrentMinute()(这两者在API 23后被标记为过时,替代方法返回的是Integer类型,需要处理可能的null值)。
2. 限制可选的日期范围
业务上经常需要限制用户只能选择某个范围内的日期,比如预约功能不能选过去的日期。在API 11及以上,可以调用datePicker.setMinDate(long millis)和setMaxDate(long millis)来设置最小和最大可选日期:
DatePicker datePicker = findViewById(R.id.datePicker); long now = System.currentTimeMillis(); datePicker.setMinDate(now); // 不能选今天之前 datePicker.setMaxDate(now + 1000L * 60 * 60 * 24 * 30); // 最长提前30天
3. Material风格与主题适配
从Android 5.0开始,DatePicker默认呈现为日历视图,如果你的应用主题使用的是Material Components,还可以引入Material Design库中的MaterialDatePicker,它支持范围选择、深色模式自适应,视觉效果和交互体验都比原生控件更好。对于时间选择,第三方库如TimePicker的滚轮样式实现也是常见选择,不过如果只是简单需求,系统自带的控件完全够用,没必要额外增加依赖。
4. 屏幕旋转的状态保存
弹窗在选择过程中如果发生屏幕旋转,Dialog会消失且选择状态丢失。解决方案是在Activity的onCreate中恢复DialogFragment,也就是把DatePickerDialog包在DialogFragment里使用,利用Fragment自动恢复的机制保证状态不丢失。这是官方文档推荐的标准做法,建议在正式项目中都采用这种封装方式。
五、完整示例:选择生日并格式化显示
最后通过一个完整的小例子把前面的知识点串起来。假设页面上有一个按钮和一个文本框,点击按钮弹出日期选择器,选择完成后把生日格式化成“1995年06月15日”的形式展示:
public class MainActivity extends AppCompatActivity {
private TextView tvBirthday;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvBirthday = findViewById(R.id.tvBirthday);
findViewById(R.id.btnPick).setOnClickListener(v -> showDatePicker());
}
private void showDatePicker() {
Calendar calendar = Calendar.getInstance();
DatePickerDialog dialog = new DatePickerDialog(this,
(view, year, month, dayOfMonth) -> {
// 使用String.format保证月日补零
String text = String.format(Locale.CHINA,
"%d年%02d月%02d日", year, month + 1, dayOfMonth);
tvBirthday.setText(text);
},
1995, 0, 1); // 默认显示1995年1月1日
dialog.show();
}
}
注意这里用了String.format配合%02d来保证月份和日期始终是两位数,避免出现“1995年6月5日”和“1995年06月05日”混杂的不统一显示。另外默认日期参数中月份同样是从0开始,传0代表1月。
总结一下,DatePicker和TimePicker配合对应的Dialog封装,能够满足绝大多数日期时间选择需求。开发时重点记住三点:月份从0开始计数、监听器的版本兼容、屏幕旋转时的状态保存。把这些细节处理好,选择器功能就能稳定可靠地运行在你的应用里。
Android日期选择器DatePickerTimePicker修改时间:2026-09-07 01:46:36