参考GitHub:https://github.com/Bigkoo/Android-PickerView
这个框架的效果感觉不错,但是作者不得不让人吐槽,但是没办法,人家效果好,还得想办法用,接下来我写的比较基础简单,其他方法设置可以去GitHub或Demo中去查看,希望我下面的讲解能帮助大家,如果还是不明白并且导入他的框架运行报错的话,可以找我解决报错问题。
一、主要说明如下功能,如果想实现其他功能可以下载GitHub中的Demo或者去调用方法看看效果。
1. 时间选择器
2. 条件选择器和省市区三级联动
3. 自定义选择器
二、加入依赖
implementation 'com.contrarywind:Android-PickerView:4.1.7'
三、时间选择器
private void initTimePicker() {//Dialog 模式下,在底部弹出
TimePickerView pvTime = new TimePickerBuilder(this, new OnTimeSelectListener() {
@Override
public void onTimeSelect(Date date, View v) {
Toast.makeText(MainActivity.this, getTime(date), Toast.LENGTH_SHORT).show();
}
}).build();
}
private String getTime(Date date) {//可根据需要自行截取数据显示
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return format.format(date);
}
四、条件选择器和省市区三级联动
1. 省市区三级联动
第一步:先在main文件下创建assets文件夹,从Demo的main/assets中复制一个province.json文件在此文件下。
第二步:布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_json_data"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:paddingBottom="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp">
<Button
android:id="@+id/btn_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/picker_text_parse"/>
<Button
android:id="@+id/btn_show"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/picker_text_show"
android:layout_marginTop="@dimen/activity_horizontal_margin" />
</LinearLayout>
第三步:解析province.json数据的工具类
public class GetJsonDataUtil {
public String getJson(Context context, String fileName) {
StringBuilder stringBuilder = new StringBuilder();
try {
AssetManager assetManager = context.getAssets();
BufferedReader bf = new BufferedReader(new InputStreamReader(
assetManager.open(fileName)));
String line;
while ((line = bf.readLine()) != null) {
stringBuilder.append(line);
}
} catch (IOException e) {
e.printStackTrace();
}
return stringBuilder.toString();
}
}
第四步:创建一个Bean类
public class JsonBean implements IPickerViewData {
/**
* name : 省份
* city : [{"name":"北京市","area":["东城区","西城区","崇文区","宣武区","朝阳区"]}]
*/
private String name;
private List<CityBean> city;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<CityBean> getCityList() {
return city;
}
public void setCityList(List<CityBean> city) {
this.city = city;
}
// 实现 IPickerViewData 接口,
// 这个用来显示在PickerView上面的字符串,
// PickerView会通过IPickerViewData获取getPickerViewText方法显示出来。
@Override
public String getPickerViewText() {
return this.name;
}
public static class CityBean {
/**
* name : 城市
* area : ["东城区","西城区","崇文区","昌平区"]
*/
private String name;
private List<String> area;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getArea() {
return area;
}
public void setArea(List<String> area) {
this.area = area;
}
}
}
第五步:Activity,你可以让他页面一打开就开始解析数据
public class JsonDataActivity extends AppCompatActivity implements View.OnClickListener {
private List<JsonBean> options1Items = new ArrayList<>();
private ArrayList<ArrayList<String>> options2Items = new ArrayList<>();
private ArrayList<ArrayList<ArrayList<String>>> options3Items = new ArrayList<>();
private Thread thread;
private static final int MSG_LOAD_DATA = 0x0001;
private static final int MSG_LOAD_SUCCESS = 0x0002;
private static final int MSG_LOAD_FAILED = 0x0003;
private static boolean isLoaded = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_json_data);
initView();
}
private void initView() {
findViewById(R.id.btn_data).setOnClickListener(this);
findViewById(R.id.btn_show).setOnClickListener(this);
}
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_LOAD_DATA:
if (thread == null) {//如果已创建就不再重新创建子线程了
Toast.makeText(JsonDataActivity.this, "Begin Parse Data", Toast.LENGTH_SHORT).show();
thread = new Thread(new Runnable() {
@Override
public void run() {
// 子线程中解析省市区数据
initJsonData();
}
});
thread.start();
}
break;
case MSG_LOAD_SUCCESS:
Toast.makeText(JsonDataActivity.this, "Parse Succeed", Toast.LENGTH_SHORT).show();
isLoaded = true;
break;
case MSG_LOAD_FAILED:
Toast.makeText(JsonDataActivity.this, "Parse Failed", Toast.LENGTH_SHORT).show();
break;
}
}
};
private void initJsonData() {//解析数据
/**
* 注意:assets 目录下的Json文件仅供参考,实际使用可自行替换文件
* 关键逻辑在于循环体
*
* */
String JsonData = new GetJsonDataUtil().getJson(this, "province.json");//获取assets目录下的json文件数据
ArrayList<JsonBean> jsonBean = parseData(JsonData);//用Gson 转成实体
/**
* 添加省份数据
*
* 注意:如果是添加的JavaBean实体,则实体类需要实现 IPickerViewData 接口,
* PickerView会通过getPickerViewText方法获取字符串显示出来。
*/
options1Items = jsonBean;
for (int i = 0; i < jsonBean.size(); i++) {//遍历省份
ArrayList<String> cityList = new ArrayList<>();//该省的城市列表(第二级)
ArrayList<ArrayList<String>> province_AreaList = new ArrayList<>();//该省的所有地区列表(第三极)
for (int c = 0; c < jsonBean.get(i).getCityList().size(); c++) {//遍历该省份的所有城市
String cityName = jsonBean.get(i).getCityList().get(c).getName();
cityList.add(cityName);//添加城市
ArrayList<String> city_AreaList = new ArrayList<>();//该城市的所有地区列表
//如果无地区数据,建议添加空字符串,防止数据为null 导致三个选项长度不匹配造成崩溃
/*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());
}*/
city_AreaList.addAll(jsonBean.get(i).getCityList().get(c).getArea());
province_AreaList.add(city_AreaList);//添加该省所有地区数据
}
/**
* 添加城市数据
*/
options2Items.add(cityList);
/**
* 添加地区数据
*/
options3Items.add(province_AreaList);
}
mHandler.sendEmptyMessage(MSG_LOAD_SUCCESS);
}
public ArrayList<JsonBean> parseData(String result) {//Gson 解析
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();
mHandler.sendEmptyMessage(MSG_LOAD_FAILED);
}
return detail;
}
private void showPickerView() {// 弹出选择器
OptionsPickerView pvOptions = new OptionsPickerBuilder(this, new OnOptionsSelectListener() {
@Override
public void onOptionsSelect(int options1, int options2, int options3, View v) {
//返回的分别是三个级别的选中位置
String opt1tx = options1Items.size() > 0 ?
options1Items.get(options1).getPickerViewText() : "";
String opt2tx = options2Items.size() > 0
&& options2Items.get(options1).size() > 0 ?
options2Items.get(options1).get(options2) : "";
String opt3tx = options2Items.size() > 0
&& options3Items.get(options1).size() > 0
&& options3Items.get(options1).get(options2).size() > 0 ?
options3Items.get(options1).get(options2).get(options3) : "";
String tx = opt1tx + opt2tx + opt3tx;
Toast.makeText(JsonDataActivity.this, tx, Toast.LENGTH_SHORT).show();
}
})
.setTitleText("城市选择")
.setDividerColor(Color.BLACK)
.setTextColorCenter(Color.BLACK) //设置选中项文字颜色
.setContentTextSize(20)
.build();
/*pvOptions.setPicker(options1Items);//一级选择器
pvOptions.setPicker(options1Items, options2Items);//二级选择器*/
pvOptions.setPicker(options1Items, options2Items, options3Items);//三级选择器
pvOptions.show();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_data:
mHandler.sendEmptyMessage(MSG_LOAD_DATA);
break;
case R.id.btn_show:
if (isLoaded) {
showPickerView();
} else {
Toast.makeText(JsonDataActivity.this, "Please waiting until the data is parsed", Toast.LENGTH_SHORT).show();
}
break;
}
}
}
2. 条件选择器:只需要把上面的数据源修改成你需要的即可。
五、自定义选择器
private void initCustomTimePicker() {
/**
* @description
*
* 注意事项:
* 1.自定义布局中,id为 optionspicker 或者 timepicker 的布局以及其子控件必须要有,否则会报空指针.
* 具体可参考demo 里面的两个自定义layout布局。
* 2.因为系统Calendar的月份是从0-11的,所以如果是调用Calendar的set方法来设置时间,月份的范围也要是从0-11
* setRangDate方法控制起始终止时间(如果不设置范围,则使用默认时间1900-2100年,此段代码可注释)
*/
Calendar selectedDate = Calendar.getInstance();//系统当前时间
Calendar startDate = Calendar.getInstance();
startDate.set(2014, 1, 23);
Calendar endDate = Calendar.getInstance();
endDate.set(2027, 2, 28);
//时间选择器 ,自定义布局
pvCustomTime = new TimePickerBuilder(this, new OnTimeSelectListener() {
@Override
public void onTimeSelect(Date date, View v) {//选中事件回调
mTv.setText(getTime(date));
}
})
.setDate(selectedDate)
.setRangDate(startDate, endDate)
//添加自定义布局的回调方法
.setLayoutRes(R.layout.pickerview_custom_time, new CustomListener() {
@Override
public void customLayout(View v) {
final TextView tvSubmit = (TextView) v.findViewById(R.id.tv_finish);
ImageView ivCancel = (ImageView) 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();
}
});
}
})
.setContentTextSize(18)
.setType(new boolean[]{false, false, false, true, true, true})
.setLabel("年", "月", "日", "时", "分", "秒")
.setLineSpacingMultiplier(1.2f)
.setTextXOffset(0, 0, 0, 40, 0, -40)
.isCenterLabel(false) //是否只显示中间选中项的label文字,false则每项item全部都带有label。
.setDividerColor(0xFF24AD9D)
.build();
}
六、其他
1. 上面的最后一步都是用picker.show();控制展示。
2. 其他样式可根据GitHub中的讲解的方法选择性添加。
3. 有什么疑问,欢迎大家来提问。