Android-PickerView 完全解析:从零开始构建高颜值Android选择器
引言:告别丑陋选择器,打造专业级用户体验
你是否还在为Android原生Spinner控件的单调外观而烦恼?是否在寻找一个既能实现时间选择又能完成省市区联动的全能选择器?Android-PickerView作为一款仿iOS风格的高颜值选择器库,提供了时间选择器(TimePickerView)和选项选择器(OptionsPickerView)两大核心组件,支持联动效果、自定义布局和丰富的样式配置,让你的应用瞬间提升专业质感。本文将从基础集成到高级定制,全方位带你掌握这款强大控件的使用技巧。
一、项目概述:功能与架构解析
1.1 核心功能概览
Android-PickerView是一个专为Android平台设计的选择器控件库,主要特点包括:
- 双核心组件:时间选择器(支持年月日时分秒等多格式)与选项选择器(支持一至三级联动)
- 高度自定义:从文字颜色到整体布局,几乎所有视觉元素均可定制
- 丰富交互体验:支持循环滚动、实时回调、平滑动画过渡
- 多场景适配:对话框模式、底部弹窗、自定义容器等多种展示方式
1.2 项目架构与核心类
核心类关系说明:
- BasePickerView:所有选择器的基类,封装了弹窗显示、消失等基础功能
- TimePickerView/OptionsPickerView:具体功能实现类,分别处理时间选择和选项选择逻辑
- XXXPickerBuilder:构建者模式实现,提供链式API配置选择器属性
二、快速集成:从环境配置到第一个选择器
2.1 环境准备与依赖添加
Android-PickerView支持Android API 9及以上版本,推荐通过Gradle集成:
// 在Module级别的build.gradle中添加依赖
dependencies {
implementation 'com.contrarywind:Android-PickerView:4.1.9'
}
注意:由于JCenter仓库已停止服务,建议直接下载源码作为Module导入项目进行自定义改造。源码获取地址:https://gitcode.com/gh_mirrors/an/Android-PickerView
2.2 第一个时间选择器:基础实现
// 初始化时间选择器
private void initTimePicker() {
// 获取当前时间作为默认选中时间
Calendar selectedDate = Calendar.getInstance();
// 创建时间选择器构建器
pvTime = new TimePickerBuilder(this, new OnTimeSelectListener() {
@Override
public void onTimeSelect(Date date, View v) {
// 选中时间回调
Toast.makeText(MainActivity.this, getTime(date), Toast.LENGTH_SHORT).show();
}
})
// 设置时间选择范围(可选)
.setRangDate(startDate, endDate)
// 设置显示类型(年月日时分秒)
.setType(new boolean[]{true, true, true, true, true, true})
// 设置标题文字
.setTitleText("选择时间")
// 构建选择器实例
.build();
// 设置默认选中时间
pvTime.setDate(selectedDate);
}
// 日期格式化工具
private String getTime(Date date) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return format.format(date);
}
// 点击按钮显示选择器
btnTime.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
pvTime.show();
}
});
2.3 第一个选项选择器:二级联动实现
// 准备数据源
private ArrayList<ProvinceBean> options1Items = new ArrayList<>();
private ArrayList<ArrayList<String>> options2Items = new ArrayList<>();
// 初始化选项选择器
private void initOptionPicker() {
// 准备数据(实际项目中通常从网络或本地文件加载)
getOptionData();
// 创建选项选择器构建器
pvOptions = new OptionsPickerBuilder(this, new OnOptionsSelectListener() {
@Override
public void onOptionsSelect(int options1, int option2, int options3, View v) {
// 选中结果回调
String tx = options1Items.get(options1).getPickerViewText()
+ options2Items.get(options1).get(option2);
btnOptions.setText(tx);
}
})
.setTitleText("城市选择")
.setContentTextSize(20)
.setDividerColor(Color.LTGRAY)
.setSelectOptions(0, 1) // 设置默认选中项
.setLabels("省", "市", "") // 设置单位标签
.build();
// 设置数据源(二级联动)
pvOptions.setPicker(options1Items, options2Items);
}
// 准备模拟数据
private void getOptionData() {
// 选项1
options1Items.add(new ProvinceBean(0, "广东", "", ""));
options1Items.add(new ProvinceBean(1, "湖南", "", ""));
// 选项2(二级数据)
ArrayList<String> options2Items_01 = new ArrayList<>();
options2Items_01.add("广州");
options2Items_01.add("佛山");
options2Items_01.add("东莞");
ArrayList<String> options2Items_02 = new ArrayList<>();
options2Items_02.add("长沙");
options2Items_02.add("岳阳");
options2Items.add(options2Items_01);
options2Items.add(options2Items_02);
}
三、高级定制:打造专属选择器
3.1 时间选择器高级配置
时间选择器支持丰富的自定义选项,以下是一些常用配置示例:
pvTime = new TimePickerBuilder(this, new OnTimeSelectListener() {
@Override
public void onTimeSelect(Date date, View v) {
// 选中回调
}
})
// 设置时间类型(年月日时分秒)
.setType(new boolean[]{true, true, true, false, false, false}) // 只显示年月日
// 设置标题样式
.setTitleText("自定义标题")
.setTitleSize(20)
.setTitleColor(Color.BLACK)
.setTitleBgColor(0xFFEEEEEE)
// 设置滚轮样式
.setContentTextSize(18)
.setTextColorCenter(Color.BLUE) // 选中项文字颜色
.setDividerColor(Color.LTGRAY) // 分割线颜色
.setLineSpacingMultiplier(1.5f) // 行距倍数
// 设置按钮样式
.setSubmitText("确定")
.setCancelText("取消")
.setSubmitColor(Color.RED)
.setCancelColor(Color.GRAY)
// 设置其他属性
.setOutSideCancelable(false) // 点击外部是否取消
.isCyclic(true) // 是否循环滚动
.setDate(selectedDate) // 设置默认日期
.setRangDate(startDate, endDate) // 设置范围日期
.isCenterLabel(false) // 是否只显示选中项的单位标签
.addOnCancelClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 取消按钮点击事件
}
})
.setTimeSelectChangeListener(new OnTimeSelectChangeListener() {
@Override
public void onTimeSelectChanged(Date date) {
// 滚动时实时回调
}
})
.build();
3.2 选项选择器高级配置
选项选择器同样支持丰富的自定义选项:
pvOptions = new OptionsPickerBuilder(this, new OnOptionsSelectListener() {
@Override
public void onOptionsSelect(int options1, int option2, int options3, View v) {
// 选中结果回调
}
})
.setTitleText("城市选择")
.setContentTextSize(20)
.setDividerColor(Color.LTGRAY)
.setSelectOptions(0, 1) // 默认选中项
.setBgColor(Color.WHITE)
.setTitleBgColor(Color.DKGRAY)
.setTitleColor(Color.WHITE)
.setCancelColor(Color.YELLOW)
.setSubmitColor(Color.YELLOW)
.setTextColorCenter(Color.BLUE) // 选中项文字颜色
.isRestoreItem(true) // 切换时是否还原默认选中项
.isCenterLabel(false) // 是否只显示中间选中项的label
.setLabels("省", "市", "区") // 设置单位标签
.setOptionsSelectChangeListener(new OnOptionsSelectChangeListener() {
@Override
public void onOptionsSelectChanged(int options1, int options2, int options3) {
// 选项变化实时回调
}
})
.setLinkage(true) // 是否联动(三级联动必须设置为true)
.build();
// 设置数据源
pvOptions.setPicker(options1Items, options2Items, options3Items); // 三级联动
// pvOptions.setPicker(options1Items, options2Items); // 二级联动
// pvOptions.setPicker(options1Items); // 一级选择
3.3 自定义布局实现
Android-PickerView支持完全自定义选择器的布局,满足特殊UI需求:
// 自定义时间选择器布局
pvCustomTime = new TimePickerBuilder(this, new OnTimeSelectListener() {
@Override
public void onTimeSelect(Date date, View v) {
// 选中回调
}
})
.setDate(selectedDate)
.setRangDate(startDate, endDate)
.setLayoutRes(R.layout.pickerview_custom_time, new CustomListener() {
@Override
public void customLayout(View v) {
// 自定义布局中控件初始化
final TextView tvSubmit = v.findViewById(R.id.tv_finish);
ImageView ivCancel = v.findViewById(R.id.iv_cancel);
// 设置事件监听
tvSubmit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
pvCustomTime.returnData(); // 返回选中数据
pvCustomTime.dismiss();
}
});
ivCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
pvCustomTime.dismiss();
}
});
// 可以在这里添加自定义控件的事件处理
}
})
.setType(new boolean[]{true, true, true, true, true, false}) // 不显示秒
.build();
自定义布局文件示例(res/layout/pickerview_custom_time.xml):
<?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="wrap_content"
android:orientation="vertical">
<!-- 标题栏 -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="44dp"
android:background="#EEEEEE">
<ImageView
android:id="@+id/iv_cancel"
android:layout_width="44dp"
android:layout_height="44dp"
android:padding="12dp"
android:src="@drawable/ic_cancel" />
<TextView
android:layout_width="wrap_content"
android:layout_height="44dp"
android:layout_centerInParent="true"
android:text="自定义时间选择"
android:textSize="16sp"
android:textColor="#333333" />
<TextView
android:id="@+id/tv_finish"
android:layout_width="60dp"
android:layout_height="44dp"
android:layout_alignParentRight="true"
android:gravity="center"
android:text="完成"
android:textSize="16sp"
android:textColor="#FF4081" />
</RelativeLayout>
<!-- 时间选择器容器 -->
<LinearLayout
android:id="@+id/timepicker"
android:layout_width="match_parent"
android:layout_height="200dp"
android:orientation="horizontal" />
</LinearLayout>
注意:自定义布局中,id为
timepicker(时间选择器)或optionspicker(选项选择器)的容器必须存在,否则会报空指针异常。
四、实战场景:省市区三级联动实现
4.1 数据准备与解析
省市区联动是选项选择器的典型应用场景,通常使用JSON格式数据:
// 从assets目录读取JSON数据
private void getJsonData() {
String JsonData = new GetJsonDataUtil().getJson(this, "province.json");//获取assets目录下的json文件数据
ArrayList<JsonBean> jsonBean = parseData(JsonData);//用Gson 转成实体
/**
* 添加省份数据
*
* 注意:如果是添加的JavaBean实体,则实体类需要实现 IPickerViewData 接口,
* PickerView会通过getPickerViewText方法获取字符串显示出来。
*/
options1Items = new ArrayList<>();
for (int i = 0; i < jsonBean.size(); i++) {
options1Items.add(new ProvinceBean(i, jsonBean.get(i).getName(), jsonBean.get(i).getCode(), jsonBean.get(i).getProvince()));
// 添加城市数据
ArrayList<String> CityList = new ArrayList<>();
//如果无城市数据,添加空字符串,防止三个选项长度不匹配造成崩溃
if (jsonBean.get(i).getCityList() == null || jsonBean.get(i).getCityList().size() == 0) {
CityList.add("");
} else {
for (int c = 0; c < jsonBean.get(i).getCityList().size(); c++) {
CityList.add(jsonBean.get(i).getCityList().get(c).getName());
}
}
options2Items.add(CityList);
// 添加区县数据
ArrayList<ArrayList<String>> Province_AreaList = new ArrayList<>();
if (jsonBean.get(i).getCityList() == null || jsonBean.get(i).getCityList().size() == 0) {
Province_AreaList.add(new ArrayList<String>());
} else {
Province_AreaList = new ArrayList<>();
for (int c = 0; c < jsonBean.get(i).getCityList().size(); c++) {
ArrayList<String> City_AreaList = new ArrayList<>();//该城市的所有地区
if (jsonBean.get(i).getCityList().get(c).getArea() == null || jsonBean.get(i).getCityList().get(c).getArea().size() == 0) {
City_AreaList.add("");
} else {
City_AreaList.addAll(jsonBean.get(i).getCityList().get(c).getArea());
}
Province_AreaList.add(City_AreaList);
}
}
options3Items.add(Province_AreaList);
}
// 更新选择器数据
pvOptions.setPicker(options1Items, options2Items, options3Items);
pvOptions.postInvalidate();
}
// 解析JSON数据
public ArrayList<JsonBean> parseData(String result) {
ArrayList<JsonBean> detail = new ArrayList<>();
try {
JSONArray data = new JSONArray(result);
Gson gson = new Gson();
for (int i = 0; i < data.length(); i++) {
JsonBean entity = gson.fromJson(data.optJSONObject(i).toString(), JsonBean.class);
detail.add(entity);
}
} catch (Exception e) {
e.printStackTrace();
}
return detail;
}
4.2 三级联动实现代码
// 初始化三级联动选择器
private void initJsonDataPicker() {
// 初始化选择器
pvOptions = new OptionsPickerBuilder(this, new OnOptionsSelectListener() {
@Override
public void onOptionsSelect(int options1, int options2, int options3, View v) {
//返回的分别是三个级别的选中位置
String province = options1Items.get(options1).getPickerViewText();
String city = options2Items.get(options1).get(options2);
String area = options3Items.get(options1).get(options2).get(options3);
String address = province + " " + city + " " + area;
tvAddress.setText(address);
}
})
.setTitleText("城市选择")
.setDividerColor(Color.LTGRAY)
.setTextColorCenter(Color.BLACK) //设置选中项文字颜色
.setContentTextSize(18)
.setLinkage(true)
.setLabels("省", "市", "区")
.build();
// 获取JSON数据并解析
getJsonData();
// 设置默认选中项
pvOptions.setSelectOptions(0, 0, 0);
}
五、性能优化与最佳实践
5.1 数据加载优化
- 异步加载:大量数据(如省市区数据)应异步加载,避免阻塞UI线程
- 数据复用:选择器实例可复用,避免频繁创建和销毁
- 懒加载:初始化选择器时只加载必要数据,滚动时再加载后续数据(适用于大数据集)
// 优化的初始化方式
private void initOptimizedPicker() {
// 先创建选择器实例但不设置数据
pvOptions = new OptionsPickerBuilder(this, listener)
.setTitleText("优化选择器")
.build();
// 异步加载数据
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
// 后台加载数据
loadLargeData();
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
// 数据加载完成后设置给选择器
pvOptions.setPicker(options1Items, options2Items, options3Items);
// 通知数据已准备好
Toast.makeText(MainActivity.this, "数据加载完成", Toast.LENGTH_SHORT).show();
}
}.execute();
}
5.2 内存管理
- 生命周期管理:在Activity的onDestroy中销毁选择器,避免内存泄漏
- 避免内存抖动:避免在滚动监听中执行耗时操作或频繁创建对象
@Override
protected void onDestroy() {
super.onDestroy();
// 销毁选择器
if (pvTime != null) {
pvTime.dismiss();
pvTime = null;
}
if (pvOptions != null) {
pvOptions.dismiss();
pvOptions = null;
}
}
5.3 常见问题解决方案
- 日期选择器月份设置问题
// 错误示例:
Calendar calendar = Calendar.getInstance();
calendar.set(2023, 12, 1); // 月份设置为12,实际会自动滚动到下一年
// 正确示例:
Calendar calendar = Calendar.getInstance();
calendar.set(2023, 11, 1); // 11表示12月(0-11代表1-12月)
- 联动选择器数据长度不匹配问题
确保各级数据长度匹配,无数据时添加空字符串占位:
// 确保每个省份都有对应的城市列表
for (int i = 0; i < provinceList.size(); i++) {
ArrayList<String> cityList = new ArrayList<>();
if (provinceList.get(i).getCities() == null || provinceList.get(i).getCities().isEmpty()) {
cityList.add(""); // 添加空字符串占位
} else {
// 添加实际城市数据
}
options2Items.add(cityList);
}
- 自定义布局不显示问题
- 确保自定义布局中有id为
timepicker或optionspicker的容器 - 检查布局文件是否正确放置在res/layout目录下
- 确保自定义布局中的控件初始化代码正确
六、总结与扩展
Android-PickerView作为一款功能全面的选择器库,凭借其丰富的自定义选项和良好的用户体验,成为Android开发中选择器控件的首选。本文从基础集成到高级定制,详细介绍了时间选择器和选项选择器的使用方法,并通过实战案例展示了省市区三级联动的实现。
除了本文介绍的内容,Android-PickerView还支持农历日期选择、Dialog模式显示、自定义动画等高级功能。开发者可以根据项目需求,结合官方文档和源码进一步探索更多可能性。
最后,由于该库已停止更新,建议在实际项目中根据需求进行适当的源码定制,以满足特定的业务场景和最新的Android系统要求。通过掌握本文介绍的知识,相信你已经能够轻松应对各种选择器需求,为你的应用打造出专业级的用户界面。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



