
Android-PickerView实战3个场景快速搞定时间选择器与省市区三级联动【免费下载链接】Android-PickerViewThis is a picker view for android , support linkage effect, timepicker and optionspicker.时间选择器、省市区三级联动项目地址: https://gitcode.com/gh_mirrors/an/Android-PickerView先讲一个真实踩坑经历。我在做一个上门维修 App客户下单时要选预约上门时间和所在城市。第一版我图省事直接用系统自带的TimePickerDialog结果产品经理只看了一眼就扔回来三句话样式太丑、不能只选日期不选时间、不能限制只能选未来 30 天。接着我又试了自定义 Dialog 拼三个滚轮忙活一下午滚轮联动、惯性滑动、选中高亮这些细节一个比一个难啃。后来换成了Android-PickerView这个开源选择器库一个下午把时间选择器、省市区三级联动全部搞定代码量少得惊人。这篇文章就从我当时的踩坑顺序出发带你从最简单的弹出来一直写到定制到亲妈都不认识最后附一个完整可运行的最小示例你可以直接复制改。先花 1 分钟认识这个项目Android-PickerView 是一个 Android 平台的选择器库官方定位就是解决两类需求时间选择器timepicker和选项选择器optionspicker支持省市区这种联动效果。它的视觉风格是 iOS 那种滚轮3D WheelView支持惯性滑动、点击条目、透明度渐变。整个仓库分三个模块理解了它们后面看代码就不会迷路wheelview最底层的滚轮控件负责滑动、绘制、3D 效果一般不用直接碰。pickerview核心库把滚轮组装成时间选择器和条件选择器并封装出链式 API。app官方 Demo里面几乎每种玩法都有可运行的示例学这个库最好的方式就是照着 app 模块抄。它的用法有个特点所有配置都靠 Builder 链式调用你只要记住new 一个 Builder → 链上各种 set 方法 → build() → show()就掌握了 80% 的用法。场景一3 分钟跑通最简用法先别管样式我们的目标是点按钮能弹出来选完能拿到结果。1. 让时间选择器弹出来// 1. 准备回调选完时间后触发 TimePickerView pvTime new TimePickerBuilder(this, new OnTimeSelectListener() { Override public void onTimeSelect(Date date, View v) { // date 就是用户选中的时间 SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:mm); btnTime.setText(sdf.format(date)); } }) .build(); // 2. 弹出可以传触发它的 View回调里会原样返回 pvTime.show(btnTime);show()不传参数也能弹show(view)会把 view 通过回调的第二个参数传回来方便你在回调里直接给对应的按钮赋值。2. 让选项选择器弹出来// 数据源支持 String、Integer以及实现了 IPickerViewData 接口的 Bean ArrayListString food new ArrayList(); food.add(KFC); food.add(麦当劳); food.add(必胜客); OptionsPickerView pvOptions new OptionsPickerBuilder(this, new OnOptionsSelectListener() { Override public void onOptionsSelect(int options1, int options2, int options3, View v) { // 返回的是选中的下标不是数据本身 btnOptions.setText(food.get(options1)); } }) .build(); pvOptions.setPicker(food); // 挂数据这是一级选择器 pvOptions.show();注意回调返回的是下标你需要自己去数据源里取对应内容。这段小结你已经拥有了最核心的能力——两种选择器从弹不出来到能用。整个过程中你没有写任何布局文件、没有处理触摸事件全是库内部完成。场景二让时间选择器按你的规则来跑通最简用法后你会立刻遇到新问题默认显示年月日时分秒但我只想让用户选年月日或时分秒默认范围 1900-2100 年太宽我要限定未来 30 天。这些 Android-PickerView 都留了口子。1. setType随心拼接时间单位这是时间选择器最常用的方法。它接收一个长度为 6 的布尔数组依次对应年、月、日、时、分、秒true显示、false隐藏。// 只显示年月日做生日选择很常见 .setType(new boolean[]{true, true, true, false, false, false}) // 只显示时分秒做打卡时间选择 .setType(new boolean[]{false, false, false, true, true, true}) // 全都要精确到秒 .setType(new boolean[]{true, true, true, true, true, true})注意数组长度必须为 6写错会直接抛异常。2. setDate setRangDate默认值和时间范围Calendar selected Calendar.getInstance(); // 默认选中当前时间 Calendar start Calendar.getInstance(); start.set(2018, 0, 1); // 2018年1月1日 Calendar end Calendar.getInstance(); end.set(2026, 11, 31); // 2026年12月31日 .setDate(selected) // 默认选中项不设则取当前时间 .setRangDate(start, end) // 限定可选范围超出会被裁掉这里有个新手必踩的坑Calendar的月份是从 0 开始的所以set(2024, 1, 23)实际是 2024 年2月 23 日项目源码注释里也特别强调过这一点。另外可选范围被限制在 1900-2100 年之间超出会抛IllegalArgumentException。3. setLabel改单位文字默认单位是年/月/日/时/分/秒你可以全换掉比如英文界面.setLabel(Y, M, D, h, m, s)4. 样式三件套字体、颜色、行距.setContentTextSize(18) // 滚轮文字大小 .setTextColorCenter(0xFF24AD9D) // 选中项文字颜色中间那条 .setTextColorOut(0xFFa8a8a8) // 未选中项文字颜色 .setDividerColor(0xFF24AD9D) // 上下两条分割线颜色 .setLineSpacingMultiplier(1.4f) // 行距倍数有效范围 1.0 - 4.0 .setItemVisibleCount(5) // 最大可见条目数偶数会自动 1 .isAlphaGradient(true) // 边缘条目透明度渐变 .isCenterLabel(false) // false每个条目都带单位true只有选中项带这段小结现在你能控制时间选择器的显示单位、默认值、可选范围、文字颜色和间距足以应对大多数业务需求。场景三省市区三级联动与不联动选项当时项目里最头疼的是省市区。先看结论Android-PickerView 的三级联动对数据格式有硬性要求理解了这个格式联动就通了。1. 认识三级联动的数据格式假设一级是省、二级是市、三级是区那么三个列表要这样组织// 一级省份列表 ListProvinceBean options1Items; // 二级每个省对应一个城市列表所以是 ListListT ListListString options2Items; // 三级每个市对应一个区县列表所以是 ListListListT ListListListString options3Items;简单说就是嵌套层级决定联动级数。挂数据用setPicker的三个重载pvOptions.setPicker(options1Items); // 一级 pvOptions.setPicker(options1Items, options2Items); // 二级 pvOptions.setPicker(options1Items, options2Items, options3Items); // 三级联动2. 让任意 Bean 能被选择器显示选择器显示文字时会调用数据的toString()但如果你想控制显示字段就让 Bean 实现IPickerViewData接口这个接口只有一个方法getPickerViewText()返回什么就显示什么。Demo 里的ProvinceBean就是标准范例位于app/src/main/java/com/bigkoo/pickerviewdemo/bean/public class ProvinceBean implements IPickerViewData { private String name; // ... 省略字段和构造方法 Override public String getPickerViewText() { return name; // 显示名字隐藏 id 等其它字段 } }3. 从 JSON 文件加载省市区数据真实项目里省市区数据通常来自服务端 JSON。官方 Demo 的JsonDataActivity给出了一套可复用的流程assets 放 JSON → 子线程解析 → 拼装成三级嵌套列表 → setPicker。// 子线程里解析Demo 用 Gson JSONArray String json new GetJsonDataUtil().getJson(this, province.json); JSONArray data new JSONArray(json); for (int i 0; i data.length(); i) { JsonBean province gson.fromJson(data.optJSONObject(i).toString(), JsonBean.class); options1Items.add(province); // 一级省 ArrayListString cityList new ArrayList(); ArrayListArrayListString areaList new ArrayList(); for (JsonBean.CityBean city : province.getCityList()) { cityList.add(city.getName()); // 二级市 areaList.add(new ArrayList(city.getArea())); // 三级区 } options2Items.add(cityList); options3Items.add(areaList); } // 解析完成后在主线程 setPicker 再 show pvOptions.setPicker(options1Items, options2Items, options3Items); pvOptions.show();这里有一个真实教训数据没解析完之前不要 show否则空列表会导致联动计算崩溃。Demo 里用 Handler 发消息等MSG_LOAD_SUCCESS才允许弹出。4. 不联动的多列选择器有的场景三列之间没关系比如品牌 尺码 颜色每一列独立滚动。此时用setNPickerN 代表 No Linkage// 三个独立列表互不影响 pvOptions.setNPicker(brands, sizes, colors);对应地setCyclic也按列单独控制.setCyclic(false, true, false) // 只有中间那列循环滚动这段小结你掌握了选项选择器的完整数据模型——联动靠嵌套列表显示靠IPickerViewData不联动有专门的setNPicker。到这一步电商收货地址、筛选条件这类需求已经随便做了。高级玩法自定义布局、农历与对话框模式前面改的都是皮肤接下来是换骨架。1. setLayoutRes完全接管界面默认顶部是取消/标题/确定如果你要加图片、加勾选框、改按钮位置就用自定义布局.setLayoutRes(R.layout.pickerview_custom_time, new CustomListener() { Override public void customLayout(View v) { TextView tvSubmit v.findViewById(R.id.tv_finish); ImageView ivCancel v.findViewById(R.id.iv_cancel); tvSubmit.setOnClickListener(view - { pvCustomTime.returnData(); // 手动触发确认回调 pvCustomTime.dismiss(); // 再关闭 }); ivCancel.setOnClickListener(view - pvCustomTime.dismiss()); } })两个关键点缺一不可自定义布局里必须保留一个 id 为timepicker时间选择器或optionspicker选项选择器的容器滚轮就挂在这下面否则会空指针。官方 Demo 的app/src/main/res/layout/pickerview_custom_time.xml和pickerview_custom_options.xml是现成模板。自定义布局没有默认的确定/取消逻辑你要自己给按钮绑returnData()和dismiss()。2. setLunarCalendar一键农历时间选择器内置了农历转换逻辑pickerview/src/main/java/com/bigkoo/pickerview/utils/下的LunarCalendar、ChinaDate打开开关即可支持 1900-2100 年.setLunarCalendar(true)Demo 里甚至做了一个公农历切换的勾选框运行时动态调用pvTime.setLunarCalendar(!pvTime.isLunarCalendar())就能切换还会调整各列宽度权重。3. isDialog对话框模式默认选择器是贴着屏幕底部的弹出层isDialog(true)则把它变成居中的 Dialog。Demo 里有个更骚的玩法用isDialog(true)创建然后拿到getDialog()把窗口位置改成底部.isDialog(true) // 对话框模式 .build(); Dialog dialog pvTime.getDialog(); Window win dialog.getWindow(); win.setGravity(Gravity.BOTTOM); // 改回底部弹出 win.setWindowAnimations(R.style.picker_view_slide_anim); // 改动画4. 滚动中实时监听如果你需要在用户滑动时还没点确定就拿到当前值用 Change 回调.setTimeSelectChangeListener(new OnTimeSelectChangeListener() { Override public void onTimeSelectChanged(Date date) { // 滚轮每停一次就回调一次 } })选项选择器对应setOptionsSelectChangeListener回调里给的是三个下标。这段小结自定义布局解决长得不像默认样式的问题农历解决垂直领域需求对话框模式解决展示方式问题。到这里你已经具备项目级实战能力了。完整串联示例一个预约上门服务页把前面的技巧串成一个最小可用项目一个 Activity三个入口——选预约时间限定未来范围、选服务城市省市区联动、自定义皮肤的时间选择器。public class OrderActivity extends AppCompatActivity { private TimePickerView pvBookTime; // 预约时间 private OptionsPickerViewJsonBean pvCity; // 省市区 private TimePickerView pvCustom; // 自定义皮肤 private ArrayListJsonBean provinceList new ArrayList(); private ArrayListArrayListString cityList new ArrayList(); private ArrayListArrayListArrayListString areaList new ArrayList(); Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_order); initBookTimePicker(); initCustomTimePicker(); // 省市区数据较大放子线程解析解析完再允许弹出 loadCityData(); findViewById(R.id.btn_time).setOnClickListener(v - pvBookTime.show(v)); findViewById(R.id.btn_city).setOnClickListener(v - { if (provinceList.isEmpty()) toast(数据解析中请稍候); else pvCity.show(); }); findViewById(R.id.btn_custom).setOnClickListener(v - pvCustom.show()); } /** 预约时间只选到小时范围限制在未来 90 天 */ private void initBookTimePicker() { Calendar start Calendar.getInstance(); Calendar end Calendar.getInstance(); end.add(Calendar.DAY_OF_YEAR, 90); // 未来 90 天 pvBookTime new TimePickerBuilder(this, (date, v) - { SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:00); toast(预约时间 sdf.format(date)); }) .setType(new boolean[]{true, true, true, true, false, false}) // 年月日时 .setRangDate(start, end) // 只能选未来 90 天 .setLabel(年, 月, 日, 时, , ) .setDividerColor(0xFF24AD9D) .setTextColorCenter(0xFF24AD9D) .build(); } /** 自定义皮肤去掉默认顶栏用自己的完成/取消按钮 */ private void initCustomTimePicker() { pvCustom new TimePickerBuilder(this, (date, v) - toast(选中 date)) .setLayoutRes(R.layout.pickerview_custom_time, new CustomListener() { Override public void customLayout(View v) { v.findViewById(R.id.tv_finish).setOnClickListener(view - { pvCustom.returnData(); pvCustom.dismiss(); }); v.findViewById(R.id.iv_cancel).setOnClickListener(view - pvCustom.dismiss()); } }) .setType(new boolean[]{true, true, true, false, false, false}) .build(); } /** 省市区联动Gson 解析 province.json 后挂数据 */ private void loadCityData() { new Thread(() - { String json new GetJsonDataUtil().getJson(this, province.json); ArrayListJsonBean beans new Gson() .fromJson(json, new TypeTokenArrayListJsonBean() {}.getType()); for (JsonBean p : beans) { provinceList.add(p); ArrayListString cities new ArrayList(); ArrayListArrayListString areas new ArrayList(); for (JsonBean.CityBean c : p.getCityList()) { cities.add(c.getName()); areas.add(new ArrayList(c.getArea())); } cityList.add(cities); areaList.add(areas); } runOnUiThread(() - pvCity new OptionsPickerBuilder(this, (o1, o2, o3, v) - { String tx provinceList.get(o1).getPickerViewText() cityList.get(o1).get(o2) areaList.get(o1).get(o2).get(o3); toast(服务城市 tx); }) .setTitleText(选择服务城市) .setDividerColor(0xFF24AD9D) .build()); pvCity.setPicker(provinceList, cityList, areaList); // 三级联动 }).start(); } }运行前记得在build.gradle里引入依赖仓库地址https://gitcode.com/gh_mirrors/an/Android-PickerView或直接 clone 后 import 其中的pickerview、wheelview两个 module。这段小结一个真实业务的三个需求加起来不到 100 行核心代码。从自己造轮子到组装轮子差距就在这里。实战避坑清单与适用边界把这些坑提前写在前面能帮你少走一周弯路坑点正确姿势Calendar 月份从 0 开始set(2024, 1, 23)是 2 月 23 日setType数组长度必须为 6否则抛异常时间范围限制在 1900-2100 年超出抛异常自定义布局必须保留 id 为timepicker或optionspicker的容器自定义布局的按钮需手动调returnData()和dismiss()数据未加载完就 show空列表会导致联动崩溃务必等解析完成自定义 Bean实现IPickerViewData的getPickerViewText()setLineSpacingMultiplier有效范围 1.0-4.0越界自动取极值适用边界它适合原生 Android 项目、需要 iOS 风格滚轮选择器的场景比如订单预约、省市区地址、筛选条件、生日选择。如果你的项目是 Compose、Flutter 或者需要高度定制的多平台方案它就不合适。另外源码基于android.support包开发老项目接入零成本新项目如果全量迁移了 AndroidX需要留意 support 与 AndroidX 的兼容问题。结尾能力速查表把全文浓缩成一张表收藏即可随时回查需求用哪个方法弹出时间选择器TimePickerBuilder(...).build()show()控制显示年月日/时分秒setType(boolean[6])默认选中时间setDate(Calendar)限定可选时间范围setRangDate(start, end)修改单位文字setLabel(年,月,日,时,分,秒)调整字体颜色间距setContentTextSize/setTextColorCenter/setDividerColor/setLineSpacingMultiplier省市区三级联动三级嵌套列表 setPicker(l1, l2, l3)多列不联动setNPicker(l1, l2, l3)自定义 Bean 显示实现IPickerViewData换掉整个界面setLayoutRes(res, customListener)农历模式setLunarCalendar(true)居中对话框isDialog(true)滚动实时回调setTimeSelectChangeListener/setOptionsSelectChangeListener默认选中哪一项setSelectOptions(0, 1, 0)掌握了这张表Android-PickerView 在时间选择器和省市区三级联动上的绝大多数玩法就都在你手里了。剩下的就是对着官方 Demo 的MainActivity和JsonDataActivity边抄边改——这通常是上手最快的方式。【免费下载链接】Android-PickerViewThis is a picker view for android , support linkage effect, timepicker and optionspicker.时间选择器、省市区三级联动项目地址: https://gitcode.com/gh_mirrors/an/Android-PickerView创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考