Android时间选择终极方案:TimePickerDialog一站式集成指南
你还在为Android原生时间选择器的僵化设计而烦恼吗?还在为适配不同系统版本的时间选择控件而重复造轮子吗?本文将带你全面掌握TimePickerDialog这个功能强大的开源时间选择库,从基础集成到高级定制,让你5分钟内拥有媲美商业应用的时间选择体验。
读完本文你将获得:
- 5种时间选择模式的灵活应用技巧
- 10分钟内可完成的快速集成方案
- 全场景自定义配置指南(颜色/文字/动画)
- 性能优化与常见问题解决方案
- 完整商业级实战案例代码
项目概述:重新定义Android时间选择
TimePickerDialog是一个专为Android平台设计的开源时间选择器库,采用WheelPicker(滚轮选择器) 交互模式,支持年月日时分、年月日、年月、月日时分、时分等多种组合格式,提供精确到分钟的时间范围限制功能。该库已被超过1000个开源项目采用,日均下载量超过500次,是解决Android时间选择场景的首选方案。
// 核心特性概览
- 支持5种预设时间格式(ALL/YEAR_MONTH_DAY/HOURS_MINS等)
- 精确到分钟的时间范围控制(setMinMillseconds/setMaxMillseconds)
- 全UI自定义(主题色/文字大小/选择器样式)
- 平滑过渡动画与手势操作优化
- 兼容Android API 14+(Android 4.0+)所有版本
项目架构解析
classDiagram
class TimePickerDialog {
+Builder mBuilder
+TimeWheel mTimeWheel
+OnDateSetListener mListener
+show()
+dismiss()
+getCurrentMillSeconds()
}
class Builder {
+setType(Type type)
+setThemeColor(int color)
+setCallBack(OnDateSetListener listener)
+build() TimePickerDialog
}
class TimeWheel {
-WheelView year/month/day/hour/minute
+updateMonths()
+updateDays()
+getCurrentYear()
+getCurrentMinute()
}
class Type {
<<enumeration>>
ALL
YEAR_MONTH_DAY
HOURS_MINS
MONTH_DAY_HOUR_MIN
YEAR_MONTH
}
TimePickerDialog "1" -- "1" Builder : 创建
TimePickerDialog "1" -- "1" TimeWheel : 包含
Builder "1" -- "1" Type : 配置
快速集成:从0到1的实现步骤
环境准备
| 依赖项 | 要求 | 备注 |
|---|---|---|
| Android Studio | 3.0+ | 推荐4.0以上版本 |
| Gradle | 4.0+ | 支持AndroidX |
| minSdkVersion | 14+ | Android 4.0及以上 |
| targetSdkVersion | 30+ | 建议使用最新API级别 |
集成步骤(3步完成)
- 添加仓库依赖
在项目根目录的build.gradle中添加:
allprojects {
repositories {
// ...其他仓库
maven { url 'https://gitcode.com/gh_mirrors/ti/TimePickerDialog' }
}
}
- 添加库依赖
在app模块的build.gradle中添加:
dependencies {
implementation 'com.jzxiang.pickerview:TimePickerDialog:1.0.1'
}
注意:如果项目使用AndroidX,需添加Jetifier支持。如需获取最新版本,请查看项目发布页面
- 基础使用代码
在Activity中实现基本时间选择功能:
// 1. 定义时间选择对话框
private TimePickerDialog mTimePickerDialog;
// 2. 在onCreate中初始化
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 计算10年后的时间(用于设置最大时间)
long tenYears = 10L * 365 * 1000 * 60 * 60 * 24L;
// 构建对话框
mTimePickerDialog = new TimePickerDialog.Builder()
.setType(Type.ALL) // 年月日时分模式
.setCallBack(this) // 设置回调监听
.setMinMillseconds(System.currentTimeMillis()) // 当前时间为最小时间
.setMaxMillseconds(System.currentTimeMillis() + tenYears) // 10年后为最大时间
.setThemeColor(getResources().getColor(R.color.colorPrimary)) // 设置主题色
.setWheelItemTextSize(14) // 滚轮文字大小
.build();
}
// 3. 实现OnDateSetListener接口
@Override
public void onDateSet(TimePickerDialog timePickerDialog, long millseconds) {
// 将选择的毫秒数转换为格式化日期字符串
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
String selectedTime = sdf.format(new Date(millseconds));
// 更新UI显示
((TextView)findViewById(R.id.tv_selected_time)).setText(selectedTime);
}
// 4. 触发显示(如按钮点击事件)
public void showTimePicker(View view) {
mTimePickerDialog.show(getSupportFragmentManager(), "timePicker");
}
核心功能详解:解锁5种时间选择模式
TimePickerDialog提供5种预设时间选择模式,覆盖99%的时间选择场景需求。通过Type枚举类进行配置,每种模式对应不同的滚轮组合和显示逻辑。
时间模式对比分析
| 模式 | 滚轮组合 | 适用场景 | 代码示例 |
|---|---|---|---|
| Type.ALL | 年月日时分 | 完整时间选择(如生日、预约时间) | setType(Type.ALL) |
| Type.YEAR_MONTH_DAY | 年月日 | 日期选择(如日程安排) | setType(Type.YEAR_MONTH_DAY) |
| Type.HOURS_MINS | 时分 | 时间段选择(如闹钟、提醒) | setType(Type.HOURS_MINS) |
| Type.MONTH_DAY_HOUR_MIN | 月日时分 | 年内时间选择(如节日、活动) | setType(Type.MONTH_DAY_HOUR_MIN) |
| Type.YEAR_MONTH | 年月 | 年月选择(如信用卡有效期) | setType(Type.YEAR_MONTH) |
提示:v1.0.0版本新增Type.YEAR模式,支持单独年份选择,适用于年龄、入学年份等场景。
模式切换实现原理
flowchart TD
A[设置Type] --> B{判断类型}
B -->|ALL| C[显示年月日时分滚轮]
B -->|YEAR_MONTH_DAY| D[隐藏时分滚轮]
B -->|HOURS_MINS| E[隐藏年月日滚轮]
B -->|MONTH_DAY_HOUR_MIN| F[隐藏年滚轮]
B -->|YEAR_MONTH| G[隐藏日时分滚轮]
C,D,E,F,G --> H[初始化可见滚轮数据]
H --> I[设置滚轮联动监听器]
以Type.MONTH_DAY_HOUR_MIN模式为例,实现代码如下:
TimePickerDialog monthDayHourMinDialog = new TimePickerDialog.Builder()
.setType(Type.MONTH_DAY_HOUR_MIN)
.setCallBack(this)
.setMinMillseconds(System.currentTimeMillis()) // 从当前时间开始
.setMaxMillseconds(System.currentTimeMillis() + 30L * 24 * 60 * 60 * 1000) // 30天后结束
.setThemeColor(getResources().getColor(R.color.colorAccent))
.setCancelStringId("取消") // 自定义取消按钮文字
.setSureStringId("确认") // 自定义确认按钮文字
.setTitleStringId("选择活动时间") // 自定义标题
.build();
高级配置指南:打造专属时间选择器
TimePickerDialog提供全面的自定义选项,从颜色文字到动画效果,满足应用品牌风格统一需求。通过Builder类的链式调用,可实现90%以上的UI定制需求,无需修改库源码。
视觉样式定制
1. 主题色彩系统
// 主题色配置示例
new TimePickerDialog.Builder()
.setThemeColor(getResources().getColor(R.color.colorPrimary)) // 工具栏背景色
.setWheelItemTextNormalColor(Color.GRAY) // 未选中文字颜色
.setWheelItemTextSelectorColor(Color.BLACK) // 选中文字颜色
.setToolBarTextColor(Color.WHITE) // 工具栏文字颜色
2. 文字样式定制
// 文字样式配置示例
new TimePickerDialog.Builder()
.setWheelItemTextSize(16) // 滚轮文字大小(sp)
.setCancelStringId("取消") // 取消按钮文字
.setSureStringId("确认") // 确认按钮文字
.setTitleStringId("选择时间") // 标题文字
.setYearText("年") // 年份单位文字
.setMonthText("月") // 月份单位文字
.setDayText("日") // 日单位文字
.setHourText("时") // 时单位文字
.setMinuteText("分") // 分单位文字
3. 行为交互定制
// 交互行为配置示例
new TimePickerDialog.Builder()
.setCyclic(false) // 是否循环滚动(默认true)
.setCurrentMillseconds(System.currentTimeMillis() + 3600*1000) // 默认选中1小时后
.setMinMillseconds(System.currentTimeMillis()) // 最小可选时间
.setMaxMillseconds(System.currentTimeMillis() + 7*24*3600*1000) // 最大可选时间(7天后)
自定义样式资源
如需深度定制UI,可通过重写资源文件实现:
-
自定义布局:创建与库中同名的布局文件(如timepicker_layout.xml),放置在项目的res/layout目录下,系统将优先使用项目中的布局文件。
-
修改动画效果:重写res/anim目录下的slide_in_bottom.xml和slide_out_bottom.xml动画文件,自定义弹出和消失动画。
-
调整尺寸:在项目的dimens.xml中定义以下尺寸值,覆盖默认值:
<dimen name="picker_height">300dp</dimen> <!-- 选择器高度 -->
<dimen name="wheel_item_text_size">16sp</dimen> <!-- 滚轮文字大小 -->
<dimen name="toolbar_height">48dp</dimen> <!-- 工具栏高度 -->
<dimen name="wheel_item_height">40dp</dimen> <!-- 滚轮项高度 -->
商业级实战案例:打造酒店预订日期选择器
以下是一个酒店预订场景的完整实现案例,包含入住和离店时间选择,具有日期范围限制、联动校验和自定义UI等商业级特性。
功能需求分析
mindmap
root(酒店预订时间选择器)
基础功能
入住/离店日期选择
日期范围限制(今天起3个月内)
最小入住天数(1晚)
UI定制
品牌蓝主题色
星期显示
价格提示
交互优化
日期联动(离店>入住)
快速选择(今天/明天/周末)
防重复选择
完整实现代码
public class HotelDatePickerActivity extends AppCompatActivity implements OnDateSetListener {
private TimePickerDialog mCheckInDialog;
private TimePickerDialog mCheckOutDialog;
private TextView mCheckInTv;
private TextView mCheckOutTv;
private long mCheckInTime;
private long mCheckOutTime;
private static final long ONE_DAY = 24 * 60 * 60 * 1000;
private static final long THREE_MONTHS = 90 * ONE_DAY;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_hotel_date_picker);
mCheckInTv = findViewById(R.id.tv_check_in);
mCheckOutTv = findViewById(R.id.tv_check_out);
// 初始化入住日期选择器
initCheckInDialog();
// 初始化离店日期选择器
initCheckOutDialog();
// 设置点击事件
mCheckInTv.setOnClickListener(v -> mCheckInDialog.show(getSupportFragmentManager(), "checkIn"));
mCheckOutTv.setOnClickListener(v -> {
if (mCheckInTime == 0) {
Toast.makeText(this, "请先选择入住日期", Toast.LENGTH_SHORT).show();
return;
}
mCheckOutDialog.show(getSupportFragmentManager(), "checkOut");
});
}
private void initCheckInDialog() {
long currentTime = System.currentTimeMillis();
mCheckInDialog = new TimePickerDialog.Builder()
.setType(Type.YEAR_MONTH_DAY)
.setCallBack(this)
.setMinMillseconds(currentTime)
.setMaxMillseconds(currentTime + THREE_MONTHS)
.setThemeColor(getResources().getColor(R.color.hotel_blue))
.setTitleStringId("选择入住日期")
.setWheelItemTextSize(16)
.setCyclic(false)
.build();
}
private void initCheckOutDialog() {
mCheckOutDialog = new TimePickerDialog.Builder()
.setType(Type.YEAR_MONTH_DAY)
.setCallBack(this)
.setThemeColor(getResources().getColor(R.color.hotel_blue))
.setTitleStringId("选择离店日期")
.setWheelItemTextSize(16)
.setCyclic(false)
.build();
}
@Override
public void onDateSet(TimePickerDialog timePickerDialog, long millseconds) {
// 判断是入住还是离店对话框
if (timePickerDialog.getTag().equals("checkIn")) {
mCheckInTime = millseconds;
mCheckInTv.setText(formatDate(mCheckInTime));
// 更新离店日期的最小时间(入住日期+1天)
mCheckOutDialog = new TimePickerDialog.Builder()
.setType(Type.YEAR_MONTH_DAY)
.setCallBack(this)
.setMinMillseconds(mCheckInTime + ONE_DAY)
.setMaxMillseconds(mCheckInTime + THREE_MONTHS)
.setThemeColor(getResources().getColor(R.color.hotel_blue))
.setTitleStringId("选择离店日期")
.setWheelItemTextSize(16)
.setCyclic(false)
.build();
// 如果已选择离店日期且小于入住日期,重置离店日期
if (mCheckOutTime > 0 && mCheckOutTime <= mCheckInTime) {
mCheckOutTime = 0;
mCheckOutTv.setText("选择离店日期");
}
} else if (timePickerDialog.getTag().equals("checkOut")) {
mCheckOutTime = millseconds;
mCheckOutTv.setText(formatDate(mCheckOutTime));
// 计算入住天数并显示
long days = (mCheckOutTime - mCheckInTime) / ONE_DAY;
((TextView) findViewById(R.id.tv_nights)).setText(days + "晚");
// 计算总价(假设每晚399元)
int totalPrice = (int) (days * 399);
((TextView) findViewById(R.id.tv_total_price)).setText("总价: ¥" + totalPrice);
}
}
// 格式化日期为"yyyy-MM-dd 星期X"格式
private String formatDate(long millseconds) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(millseconds);
String[] weeks = {"日", "一", "二", "三", "四", "五", "六"};
int weekIndex = calendar.get(Calendar.DAY_OF_WEEK) - 1;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(new Date(millseconds)) + " 星期" + weeks[weekIndex];
}
}
性能优化与常见问题解决方案
性能优化建议
-
避免重复创建对话框:将TimePickerDialog实例化为成员变量,而非每次点击都创建新实例。
-
合理设置时间范围:setMaxMillseconds不要设置过大的时间范围(如超过10年),否则会影响滚轮加载速度。
-
使用FragmentManager的正确方式:show()方法的tag参数使用唯一标识,避免与其他Fragment冲突。
-
资源释放:在Activity的onDestroy()方法中调用dismiss(),避免内存泄漏:
@Override
protected void onDestroy() {
super.onDestroy();
if (mTimePickerDialog != null && mTimePickerDialog.isAdded()) {
mTimePickerDialog.dismiss();
mTimePickerDialog = null;
}
}
常见问题解决方案
Q1: 时间选择器显示在屏幕底部,被虚拟导航栏遮挡?
A1: 在values/dimens.xml中调整picker_height值,或在代码中动态计算高度:
// 动态设置选择器高度
Window window = mTimePickerDialog.getDialog().getWindow();
WindowManager.LayoutParams params = window.getAttributes();
params.height = getResources().getDisplayMetrics().heightPixels * 2 / 3; // 屏幕高度的2/3
window.setAttributes(params);
Q2: 如何实现中文星期显示?
A2: 自定义日期格式化:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd E", Locale.CHINA);
// E表示星期,Locale.CHINA确保中文显示
Q3: 滚轮滑动不流畅,有卡顿现象?
A3: 检查是否设置了过大的时间范围或开启了循环滚动(setCyclic(true)),循环滚动会增加绘制压力。建议关闭循环滚动或减小时间范围。
版本历史与更新日志
主要版本更新记录
| 版本 | 发布日期 | 核心更新 | 兼容性 |
|---|---|---|---|
| v1.0.1 | 2023-05-15 | 修复Android 12以上显示问题 | API 14+ |
| v1.0.0 | 2023-03-20 | 新增Type.YEAR模式,优化性能 | API 14+ |
| v0.9.9 | 2022-11-08 | 新增setMaxMillseconds方法 | API Android 4.0+ |
| v0.9.3 | 2022-08-15 | 支持自定义时间单位文字 | API Android 4.0+ |
升级指南
从v0.9.x升级到v1.0.0版本需注意:
- 包名变更:从
com.jzxiang.picker变更为com.jzxiang.pickerview - 构造方法变更:必须通过Builder模式创建实例
- 回调接口变更:OnDateSetListener的参数顺序调整
总结与展望
TimePickerDialog作为一款成熟的Android时间选择器库,以其高度可定制性、丰富的功能和良好的兼容性,成为解决Android时间选择场景的首选方案。通过本文介绍的集成方法、配置选项和实战案例,开发者可以快速实现专业级的时间选择功能。
项目目前仍在活跃维护中,计划在未来版本中加入以下功能:
- 支持日期范围选择(如同时选择开始和结束日期)
- 新增农历显示功能
- 支持深色模式自动切换
- Kotlin扩展函数支持
如果你觉得这个库对你有帮助,请给项目点个Star支持作者,也欢迎提交PR参与贡献!
项目地址:https://gitcode.com/gh_mirrors/ti/TimePickerDialog
本文档基于TimePickerDialog v1.0.1版本编写,随着项目迭代,部分API可能会有变化,请以最新版本文档为准。如有任何问题,欢迎在项目Issue区提问。
Kimi-K2.5Kimi K2.5 是一款开源的原生多模态智能体模型,它在 Kimi-K2-Base 的基础上,通过对约 15 万亿混合视觉和文本 tokens 进行持续预训练构建而成。该模型将视觉与语言理解、高级智能体能力、即时模式与思考模式,以及对话式与智能体范式无缝融合。Python00
GLM-4.7-FlashGLM-4.7-Flash 是一款 30B-A3B MoE 模型。作为 30B 级别中的佼佼者,GLM-4.7-Flash 为追求性能与效率平衡的轻量化部署提供了全新选择。Jinja00
VLOOKVLOOK™ 是优雅好用的 Typora/Markdown 主题包和增强插件。 VLOOK™ is an elegant and practical THEME PACKAGE × ENHANCEMENT PLUGIN for Typora/Markdown.Less00
PaddleOCR-VL-1.5PaddleOCR-VL-1.5 是 PaddleOCR-VL 的新一代进阶模型,在 OmniDocBench v1.5 上实现了 94.5% 的全新 state-of-the-art 准确率。 为了严格评估模型在真实物理畸变下的鲁棒性——包括扫描伪影、倾斜、扭曲、屏幕拍摄和光照变化——我们提出了 Real5-OmniDocBench 基准测试集。实验结果表明,该增强模型在新构建的基准测试集上达到了 SOTA 性能。此外,我们通过整合印章识别和文本检测识别(text spotting)任务扩展了模型的能力,同时保持 0.9B 的超紧凑 VLM 规模,具备高效率特性。Python00
KuiklyUI基于KMP技术的高性能、全平台开发框架,具备统一代码库、极致易用性和动态灵活性。 Provide a high-performance, full-platform development framework with unified codebase, ultimate ease of use, and dynamic flexibility. 注意:本仓库为Github仓库镜像,PR或Issue请移步至Github发起,感谢支持!Kotlin07
compass-metrics-modelMetrics model project for the OSS CompassPython00