Android 三级联动 省市区选择器
今天公司需求出现了一个选择区域的功能,在网上看了一下,没有看到比较直接的文章,于是自己动手写一下。
项目地址:https://gitee.com/hbhbh/area-selector
这是一篇非常用心的文章,先上效果图(没有动图不要嫌弃)
首先
省市区数据的获取地址:https://lbs.qq.com/service/webService/webServiceGuide/webServiceDistrict
API:https://apis.map.qq.com/ws/district/v1/list
去注册一下获取个key就可以用了,或者页面下面有个调用示例,可以直接用他们的key
页面XML:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".Fragment.MerchantDataFragment">
<LinearLayout
android:layout_marginTop="@dimen/dp_50"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<NumberPicker
android:id="@+id/picker1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.5"/>
<NumberPicker
android:id="@+id/picker2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<NumberPicker
android:id="@+id/picker3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>
</LinearLayout>
</FrameLayout>
GsonFormat生成实体:
小提示:同一省份下的所有市和区的ID前两位都跟省ID前两位相同,同市区下的所有区ID前四位都跟市ID前四位相同
public class CitySuccess {
/**
* status : 0
* message : query ok
* data_version : 20200417
* result : [[{"id":"110101","fullname":"东城区"}]]
*/
@Override
public String toString() {
return "CitySuccess{" +
"status=" + status +
", message='" + message + '\'' +
", data_version='" + data_version + '\'' +
", result=" + result +
'}';
}
private int status;
private String message;
private String data_version;
private List<List<ResultBean>> result;
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getData_version() {
return data_version;
}
public void setData_version(String data_version) {
this.data_version = data_version;
}
public List<List<ResultBean>> getResult() {
return result;
}
public void setResult(List<List<ResultBean>> result) {
this.result = result;
}
public static class ResultBean {
@Override
public String toString() {
return "ResultBean{" +
"id='" + id + '\'' +
", fullname='" + fullname + '\'' +
'}';
}
/**
* id : 110101
* fullname : 东城区
*/
private String id;
private String fullname;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getFullname() {
return fullname;
}
public void setFullname(String fullname) {
this.fullname = fullname;
}
}
}
完整的代码:
public class MerchantDataFragment extends Fragment {
@BindView(R.id.picker1)
NumberPicker picker1;
@BindView(R.id.picker2)
NumberPicker picker2;
@BindView(R.id.picker3)
NumberPicker picker3;
private List<ResultBean> province;//省
private List<ResultBean> city;//市
private List<ResultBean> area;//区
private List<ResultBean> cityList;//当前的市
private List<ResultBean> areaList;//当前的区
//定义数据是否已经添加完的标识
private boolean isEnd = false;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_merchant_data, container, false);
ButterKnife.bind(this, view);
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
province = new ArrayList<>();
city = new ArrayList<>();
area = new ArrayList<>();
cityList = new ArrayList<>();
areaList = new ArrayList<>();
//把你的Key填这里
getCity("your key");
}
private void getCity(String key) {
Call<CitySuccess> call = new RetrofitUtil().getApiInstance().getCity(key);
call.enqueue(new Callback<CitySuccess>() {
@Override
public void onResponse(Call<CitySuccess> call, Response<CitySuccess> response) {
CitySuccess success = response.body();
//获取到数据时,将数据赋值给省市区列表
province = success.getResult().get(0);
city = success.getResult().get(1);
area = success.getResult().get(2);
//初始化控件的数据
initPicket(picker1, province);
//获取省级数据ID的前两位
String num = province.get(0).getId().substring(0, 2);
//获取数据
cityList = getList(num,city);
//初始化控件的数据
initPicket(picker2, cityList);
//areaList不赋值直接传是因为 腾讯的数据第一条是北京
//北京上海香港澳门那些城市 他们的数据只到市级不到区级 所以北京的区级数据本来就是空的
initPicket(picker3, areaList);
//设置监听 当省级数据改变时 需要改变市和区的数据
picker1.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker numberPicker, int i, int i1) {
//获取省级数据ID的前两位 下面道理同上
String num = province.get(i1).getId().substring(0, 2);
//获取数据
cityList = getList(num,city);
//初始化控件
initPicket(picker2, cityList);
//获取当前市级数据ID的前四位
num = cityList.get(0).getId().substring(0, 4);
areaList = getList(num,area);
initPicket(picker3, areaList);
}
});
//设置监听 当省级数据改变时 只需要改变区级数据
picker2.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker numberPicker, int i, int i1) {
String num = cityList.get(i1).getId().substring(0, 4);
areaList = getList(num,area);
initPicket(picker3, areaList);
}
});
}
@Override
public void onFailure(Call<CitySuccess> call, Throwable t) {
}
});
}
private List<ResultBean> getList(String num,List<ResultBean> list){
List<ResultBean> myList = new ArrayList<>();
int length = num.length();
for (int x = 0; x < list.size(); x++) {
//对比市级列表Id的前两位是否跟选中的省级Id前两位相等 相等的话则代表是该省下面的市
//对比区级列表Id的前四位是否跟选中的市级Id前四位相等 相等的话则代表是该市下面的区
if (num.equals(list.get(x).getId().substring(0, length))) {
myList.add(list.get(x));
if(!isEnd){
isEnd = true;
}
continue;
}
if (isEnd){
//数据添加完成后 将isEnd重置并退出循环
isEnd = false;
break;
}
}
return myList;
}
private void initPicket(NumberPicker picker, List<ResultBean> list) {
//将数据里的地区名称抽出来 赋值给新列表
List<String> list1 = list.stream().map(ResultBean::getFullname).collect(Collectors.toList());
//新建控件使用的数据数组
String[] strings = new String[list1.size()];
//将列表转成数组
list1.toArray(strings);
//数据发生变化之后 将选中值变成第一个
picker.setValue(0);
//当没有数据的时候(北京上海香港澳门那些没有区级数据的) 给它们设置一个空数组
if (strings.length == 0) {
picker.setMinValue(0);
picker.setMaxValue(0);
picker.setDisplayedValues(new String[]{" "});
return;
}
//设置数据不循环
picker.setWrapSelectorWheel(false);
picker.setMinValue(0);
picker.setDisplayedValues(strings);
picker.setMaxValue(strings.length - 1);
//设置数据不可编辑
picker.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS);
}
}
我已经尽可能的写多一点注释了,不然我怕我以后看到这个代码都不知道是啥,本来并没有把getList方法抽出来,看起来会很臃肿,但是代码会比较好理解一点,还是抽出来整洁一点。
那个isEnd的存在,主要是在将所有需要添加进数组的数据遍历完之后,不再遍历下面别的省市的数据,能提升一点蚊子腿性能,我的手机没有加isEnd遍历4ms,加了isEnd遍历0ms,所以当数据量大的时候还是有点用处的。
有哪里写错或者哪里可以优化的话,可以在下面告诉我哦。