/**
* 作者:crazyandcoder
* 联系:
* QQ : 275137657
* email: lijiwork@sina.com
* 转载请注明出处!
*/
三级城市联动的实现
所谓的三级城市选择联动就是说当我们选择省份时将会出现该省份下所有的城市,当我们选择其中某一个城市时又将会出现该城市下所有的区,如果存在区的话。在本次实例中,主要用到了json解析,文件I/O流,android Spinner控件等知识点,记录一下实现过程,便于以后查阅参考。
三级城市联动肯定少不了我国各个地区的地址信息,该例子中的数据我们以json的格式放在工程assets目录下,然后通过解析出来即可,不需要我们直接放在res/values/array.xml中,这样可以减少很多代码。首先我们来看一下城市的json格式文件的内容:
从上面的文件中我们可以清楚的看到全国的城市名按照json的格式排列。我们通过解析该文件,然后放入到spinner中显示出来即可。
下面便是本次实现的效果图:
一、代码分析
布局很简单,一个TextView用于显示选择的地址信息,三个spinner分别用于加载省、市、区地址数据。代码就不贴了。我们直接来看下主类。因为我们将省市区数据放在文件当中的,所以第一步便是要将其读取出来然后解析成string类型的数据,方便下面使用。读取本地资源的方法都是统一的,不熟悉的话可以参考上篇博客:
直接上代码:
/**
* 从asset目录下读取城市json文件转化为String类型
*
* @Title: InitData
* @param
* @return void
* @throws @date
* [2015年8月21日 上午9:40:00]
*/
private String InitData() {
StringBuffer sb = new StringBuffer();
AssetManager mAssetManager = this.getAssets();
try {
InputStream is = mAssetManager.open("city.json");
byte[] data = new byte[is.available()];
int len = -1;
while ((len = is.read(data)) != -1) {
sb.append(new String(data, 0, len, "gb2312"));
}
is.close();
return sb.toString();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
接下来我们便对上面读取出来的数据进行解析,也是本文中的重点内容,我们细细地来分析一下解析过程,解析的数据格式还是蛮复杂的,数组中包含数组。首先我们得定义几个变量,便于我们存储省市区数据。
// 省份
private String[] mProvinceDatas;
// 城市
private String[] mCitiesDatas;
// 地区
private String[] mAreaDatas;
// 列表选择的省市区
private String selectedPro = "";
private String selectedCity = "";
private String selectedArea = "";
private Spinner mProvinceSpinner;
private Spinner mCitySpinner;
private Spinner mAreaSpinner;
private ArrayAdapter<String> mProvinceAdapter;
private ArrayAdapter<String> mCityAdapter;
private ArrayAdapter<String> mAreaAdapter;
// 存储省对应的所有市
private Map<String, String[]> mCitiesDataMap = new HashMap<String, String[]>();
// 存储市对应的所有区
private Map<String, String[]> mAreaDataMap = new HashMap<String, String[]>();
01~06行:定义的数组用于存储省市区信息
09~11行:定义的变量用于存储我们通过spinner选择的省市区数据
21~24行:定义的变量用于存储我们选择一个省份后它所包含的所有城市信息以及对应的区域信息。
/**
* 开始解析城市数据
*
* @Title: BeginJsonCitisData
* @param
* @return void
* @throws @date
* [2015年8月21日 上午10:02:23]
*/
private void BeginJsonCitisData(String cityJson) {
if (!TextUtils.isEmpty(cityJson)) {
try {
JSONObject object = new JSONObject(cityJson);
JSONArray array = object.getJSONArray("citylist");
// 获取省份的数量
mProvinceDatas = new String[array.length()];
String mProvinceStr = null;
// 循环遍历
for (int i = 0; i < array.length(); i++) {
// 循环遍历省份,并将省保存在mProvinceDatas[]中
JSONObject mProvinceObject = array.getJSONObject(i);
if (mProvinceObject.has("p")) {
mProvinceStr = mProvinceObject.getString("p");
mProvinceDatas[i] = mProvinceStr;
} else {
mProvinceDatas[i] = "unknown province";
}
JSONArray cityArray;
String mCityStr = null;
try {
// 循环遍历省对应下的城市
cityArray = mProvinceObject.getJSONArray("c");
} catch (Exception e) {
e.printStackTrace();
continue;
}
mCitiesDatas = new String[cityArray.length()];
for (int j = 0; j < cityArray.length(); j++) {
// 循环遍历城市,并将城市保存在mCitiesDatas[]中
JSONObject mCityObject = cityArray.getJSONObject(j);
if (mCityObject.has("n")) {
mCityStr = mCityObject.getString("n");
mCitiesDatas[j] = mCityStr;
} else {
mCitiesDatas[j] = "unknown city";
}
// 循环遍历地区
JSONArray areaArray;
try {
// 判断是否含有第三级区域划分,若没有,则跳出本次循环,重新执行循环遍历城市
areaArray = mCityObject.getJSONArray("a");
} catch (Exception e) {
e.printStackTrace();
continue;
}
// 执行下面过程则说明存在第三级区域
mAreaDatas = new String[areaArray.length()];
for (int m = 0; m < areaArray.length(); m++) {
// 循环遍历第三级区域,并将区域保存在mAreaDatas[]中
JSONObject areaObject = areaArray.getJSONObject(m);
if (areaObject.has("s")) {
mAreaDatas[m] = areaObject.getString("s");
} else {
mAreaDatas[m] = "unknown area";
}
Log.d(TAG, mAreaDatas[m]);
}
// 存储市对应的所有第三级区域
mAreaDataMap.put(mCityStr, mAreaDatas);
}
// 存储省份对应的所有市
mCitiesDataMap.put(mProvinceStr, mCitiesDatas);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
上面的代码其实已经注释的很详细了,我们再来仔细的分析记录一下。14行:我们首先拿到要解析的根节点,从city.json文件中我们可以清楚的看到,这个根节点是一个数组类型。数组的内容便是我们需要的省份信息。
20~29行:循环遍历所有的省份,并将遍历的结果存入到mProvinceDatas[]中去。
31~49行:将每个省份下对应的城市 遍历存放到mCitiesDatas[]中去。
53~61行:这一段代码比较重要,主要意思是:对于直辖市,它是没有第三级区域的,所以,当我们遍历时发现如果没有第三级区域,我们将直接跳过该城市,遍历下个城市。
63~75行:这一段就是遍历第三级区域,并将遍历的结果存入到mAreaDatas[]中去。
78行:城市和区域一一对应。便于后面通过城市获取该城市所对应的区域的数据。
82行:省份和城市一一对应。便于后面通过省份获取该省份所对应的城市的数据。
上面便是解析的全部过程,注释还是比较详细的,接下来就比较简单了。将解析出来的结果放入到spinner中的适配器当中去显示数据即可。这里的适配器我们选取ArrayAdapter。
mProvinceAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, mProvinceDatas);
mProvinceAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mProvinceSpinner.setAdapter(mProvinceAdapter);
// 省份选择
mProvinceSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
selectedPro = "";
selectedPro = (String) parent.getSelectedItem();
// 根据省份更新城市区域信息
updateCity(selectedPro);
Log.d(TAG, "mProvinceSpinner has excuted !" + "selectedPro is " + selectedPro);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
// 市选择
mCitySpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
selectedCity = "";
selectedCity = (String) parent.getSelectedItem();
updateArea(selectedCity);
Log.d(TAG, "mCitySpinner has excuted !" + "selectedCity is " + selectedCity);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
// 区选择
mAreaSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
selectedArea = "";
selectedArea = (String) parent.getSelectedItem();
tv_address.setText("已选择: " + selectedPro + selectedCity + selectedArea);
Log.d(TAG, "mAreaSpinner has excuted !" + "selectedArea is " + selectedArea);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
/**
* 根据市 选择对应的区
*
* @Title: updateArea
* @param @param
* city
* @return void
* @throws @date
* [2015年8月21日 下午3:52:17]
*/
private void updateArea(String city) {
String[] areas = mAreaDataMap.get(city);
// 存在区
if (areas != null) {
// 存在区
mAreaSpinner.setVisibility(View.VISIBLE);
mAreaAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, areas);
mAreaAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mAreaSpinner.setAdapter(mAreaAdapter);
mAreaAdapter.notifyDataSetChanged();
mAreaSpinner.setSelection(0);
} else {
tv_address.setText("已选择: " + selectedPro + selectedCity);
mAreaSpinner.setVisibility(View.GONE);
}
}
/**
* 根据省份更新城市数据
*
* @Title: updateCityAndAreaData
* @param @param
* pro 省份
* @return void
* @throws @date
* [2015年8月21日 下午3:20:15]
*/
private void updateCity(String pro) {
String[] cities = mCitiesDataMap.get(pro);
for (int i = 0; i < cities.length; i++) {
// 存在区
mCityAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, cities);
mCityAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mCitySpinner.setAdapter(mCityAdapter);
mCityAdapter.notifyDataSetChanged();
mCitySpinner.setSelection(0);
}
}
整体的代码注释比较详细,最后附上整个工程的源代码。
下载地址:JsonDemo