本来一直没有用到过xml,也从来不知道为什么xml/json应用会这么广,谁让我是一个水逼代码货。但是果然就是与到了,从别人的网站上扒下来需要用到的xml行政区域代码,然后要做一个三级联动选择器。于是github,谷歌开始。

Android-PickerView

android-pickerview项目地址

据说这个可以,但用起来看了一下果然应该是可以的。懒得自己去适配wheelview了,就打算用这个吧。这个需要三个列表。一个列表保存的是省级数据,一个保存市级数据,一个保存区县的数据。我们就需要把我们的xml文档给解析过来。

dom4j

我的文档格式类似下面:

<?xml version="1.0" encoding="UTF-8"?>
<r tm="2018/08/01 14:29:46">
<t c="110000">北京</t>
<t>
<t c="110100">北京-北京市</t>
<t>
<t c="110101">北京-北京市-东城区</t>
<t c="110102">北京-北京市-西城区</t>
<t c="110103">北京-北京市-崇文区</t>
<t c="110104">北京-北京市-宣武区</t>
<t c="110105">北京-北京市-朝阳区</t>
<t c="110106">北京-北京市-丰台区</t>
<t c="110107">北京-北京市-石景山区</t>
<t c="110108">北京-北京市-海淀区</t>
<t c="110109">北京-北京市-门头沟区</t>
<t c="110111">北京-北京市-房山区</t>
<t c="110112">北京-北京市-通州区</t>
<t c="110113">北京-北京市-顺义区</t>
<t c="110114">北京-北京市-昌平区</t>
<t c="110115">北京-北京市-大兴区</t>
<t c="110116">北京-北京市-怀柔区</t>
<t c="110117">北京-北京市-平谷区</t>
</t>
<t c="110200">北京-县</t>
<t>
<t c="110228">北京-县-密云县</t>
<t c="110229">北京-县-延庆县</t>
</t>
</t>
<t c="120000">天津</t>
<t>
....

比较简单,开始的时候我是准备用map来搞的,但是不好适配上面的那个选择器,最后还是麻烦点,效率低点的用list来适配了。

基本概念

dom 把xml文档内的标签都当做元素,有一个根元素,我们文档内就是 r 这个元素。然后其有很多子节点,有的节点有属性 c,而有的没有。没有 c 属性的,则其具有子节点。理解后解析过程就很简单了。

代码解析

我首先构造出省,市,区县的实体类:

public class Province {
private String code;
private String name;
private List<City> citys;
}

public class City {
private String code;
private String name;
private List<Zone> zones;
}

public class Zone {
private String code;
private String value;
}

最后我们得出的是一个List\数据结构。

public class AreaDataModel {
// 需要一个安卓的上下文来获取assets目录
public static List<Province> getData(Context ctx){
List<Province> provinces = new ArrayList<Province>();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(ctx.getAssets().open("area.xml"));
Element root = document.getDocumentElement();
NodeList items = root.getChildNodes();
// 根节点的子节点,一级节点,我这样称呼它,是为省份信息
for (int i = 0; i < items.getLength(); i++) {
Node node = items.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element e = (Element) node;
if (e.hasAttribute("c")) {
Province province = new Province();
province.setCode(e.getAttribute("c").substring(0,2));
province.setName(e.getTextContent());
province.setCitys(new ArrayList<City>());
provinces.add(province);
// 如果没有`c`属性,那么这个拥有子节点,是市级数据。
} else {
addCitysToProvince(e, provinces);
}
}
}

System.out.println(provinces.size());
int n1 = 0;
int n2 = 0;
for (int i = 0; i < provinces.size(); i++) {
Province province = provinces.get(i);
n1 += province.getCitys().size();
for (int j = 0; j < province.getCitys().size(); j++) {
n2 += province.getCitys().get(j).getZones().size();
}
}
System.out.println(n1);
System.out.println(n2);
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
return provinces;
}
// 把市级数据添加到省级数据内
private static void addCitysToProvince(Element element, List<Province> provinces) {
NodeList nodeList = element.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE){
Element e = (Element) node;
// 有 `c` 属性的,是市级数据。
if (e.hasAttribute("c")){
Province province = findProvinceByCode(e.getAttribute("c").substring(0,2),provinces);
if (province!=null){
City city = new City();
city.setCode(e.getAttribute("c").substring(2,4));
String name = e.getTextContent();
city.setName(name.substring(name.lastIndexOf("-")+1));
city.setZones(new ArrayList<Zone>());
province.getCitys().add(city);
} else {
System.out.printf("%s,%s\n",e.getAttribute("c"),e.getTextContent());
}
// 否则的话,是区县数据,
} else{
addZonesToProvince(e,provinces);
}
}

}

}
// 通过省分代码找到对应的省级数据
private static Province findProvinceByCode(String code, List<Province> provinces){
for (int i = 0; i < provinces.size(); i++) {
Province province = provinces.get(i);
if (province.getCode().equals(code)){
return province;
}
}
return null;
}


// 把区县数据添加到省内
private static void addZonesToProvince(Element element, List<Province> provinces){
NodeList nodeList = element.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE){
Element e = (Element) node;
String code = e.getAttribute("c");
String pro = code.substring(0,2);
String cityCode = code.substring(2,4);
String zoneCode = code.substring(4);

Province province = findProvinceByCode(pro,provinces);
City city = findCityByCode(cityCode,province.getCitys());
if (city == null){
System.out.printf("%s,%s\n",code,e.getTextContent());
} else {
Zone zone = new Zone();
zone.setCode(zoneCode);
String name = e.getTextContent();
zone.setValue(name.substring(name.lastIndexOf("-")+1));
city.getZones().add(zone);
}
}

}
}
// 根据区域代码找出归属的城市
private static City findCityByCode(String code, List<City> cities){
for (int i = 0; i < cities.size(); i++) {
City city = cities.get(i);
if (city.getCode().equals(code)){
return city;
}

}
return null;

}
}

当我们得出了我们想要的数据结构,那我们要适配选择器了。

实现选择代码

// 分别省,市,县的列表
private List<Province> mProvinces = new ArrayList<>();
private List<List<City>> mCities = new ArrayList<>();
private List<List<List<Zone>>> mZones = new ArrayList<>();

通过我们解析的数据来初始化这三个列表:

    mProvinces = AreaDataModel.getData(this);
for (int i = 0; i < mProvinces.size(); i++) {
Province province = mProvinces.get(i);
// 如果省份的城市为空,为了避免NPE错误,给他加上一个空的市,空的区
if (province.getCitys().isEmpty()) {
City newCity = new City();
newCity.setName("");
newCity.setCode("00");
List<Zone> tmp = new ArrayList<>();
tmp.add(new Zone("00", ""));
newCity.setZones(tmp);
province.getCitys().add(newCity);
}
mCities.add(province.getCitys());
List<List<Zone>> cityZone = new ArrayList<>();
for (int j = 0; j < province.getCitys().size(); j++) {
//没有区县的市给他加一个空的。
if (province.getCitys().get(j).getZones().isEmpty()){
province.getCitys().get(j).getZones().add(new Zone("00",""));
}
cityZone.add(province.getCitys().get(j).getZones());
}
mZones.add(cityZone);
}
}

然后把数据绑定到我们的选择器上:

final OptionsPickerView pvOptions = new OptionsPickerBuilder(this, new OnOptionsSelectListener() {
@Override
public void onOptionsSelect(int options1, int options2, int options3, View v) {
StringBuffer sb = new StringBuffer();
sb.append(mProvinces.get(options1).getName());
sb.append("-");
sb.append(mCities.get(options1).get(options2).getName());
sb.append("-");
sb.append(mZones.get(options1).get(options2).get(options3).getValue());
String areazone = sb.toString().replace("--","");
idcardAddress.setTvContent(areazone);
areaCode = String.format("%s%s%s", mProvinces.get(options1).getCode(),
mCities.get(options1).get(options2).getCode(),
mZones.get(options1).get(options2).get(options3).getCode());
showToast(areazone);
Log.d(TAG, "onOptionsSelect: " + areaCode);
}
})

.setTitleText("城市选择")
.setDividerColor(Color.BLACK)
.setTextColorCenter(Color.BLACK) //设置选中项文字颜色
.setContentTextSize(20)
.build();

pvOptions.setPicker(mProvinces, mCities, mZones);//三级选择器
pvOptions.show();

这样就大功告成了。