一、什么是三级地区联动
安卓的三级地区联动 ,是指在移动应用中实现的 省、市、区(或县) 的三级级联选择器(也叫地区联动选择器)。用户在选择省份后,系统自动更新市级选项,选择市级后再自动更新区级选项,从而快速准确地定位用户所选的具体区域。
该功能广泛用于地址选择、区域划分、社区服务等场景。。
示例说明:
比如用户依次选择:
- 省:广东省
- 市:广州市
- 区:天河区
这个过程就是三级联动。每一层级的选项都会根据上一级的选项动态变化。
二、实现效果展示
📌 用户点击"地区选择"按钮,即可弹出省市区三级联动选择框,选择完成后将结果展示在按钮上。

三、核心实现思路
3.1使用三级地区联动,需要引入以下两个核心库:
groovy
// 引入地址选择器核心库
implementation 'com.contrarywind:Android-PickerView:4.1.9'
// 如果你还希望结合更复杂的城市选择功能(可选)
implementation 'liji.library.dev:citypickerview:5.2.2'

3.2 自定义实体类 JsonBeanNew
自定义实体类是为了更好地表达业务结构、实现所需功能(如 PickerView 显示),弥补原生类在结构契合度、接口实现、可读性和扩展性上的不足。
java
@Data
public class JsonBeanNew implements IPickerViewData {
/**
* name : 省份
* city : [{"name":"北京市","area":["东城区","西城区","崇文区","宣武区","朝阳区"]}]
*/
private String name;
private List<CityBeanNew> city;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<CityBeanNew> getCityList() {
return city;
}
public void setCityList(List<CityBeanNew> city) {
this.city = city;
}
// 实现 IPickerViewData 接口,
// 这个用来显示在PickerView上面的字符串,
// PickerView会通过IPickerViewData获取getPickerViewText方法显示出来。
@Override
public String getPickerViewText() {
return this.name;
}
@Data
public static class CityBeanNew {
/**
* name : 城市
* area : ["东城区","西城区","崇文区","昌平区"]
*/
private String name;
private List<String> area;
}
}
3.4 Activity 基本结构
-
JsonDataActivity
继承AppCompatActivity
-
作用:实现城市数据加载,并提供 UI 供用户选择省、市、区。
-
主要成员变量:
options1Items
:存储省份/区域列表(第一列)。options2Items
:存储城市/街道列表(第二列)。options3Items
:存储区/社区列表(第三列)。selectedOption1, selectedOption2, selectedOption3
:存储用户选中的省、市、区索引。btnShow
:点击后显示选择的省市区。
3.5 onCreate()
方法
java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_json_data);
initView();
mHandler.sendEmptyMessage(MSG_LOAD_DATA);
}
-
作用:
- 设置 UI 布局
activity_json_data.xml
。 - 初始化 UI 组件(按钮
btn_data
、btn_show
)。 - 发送
MSG_LOAD_DATA
消息,开始加载数据。
- 设置 UI 布局
3.6 initView()
方法
java
private void initView() {
findViewById(R.id.btn_data).setOnClickListener(this);
btnShow = findViewById(R.id.btn_show);
btnShow.setOnClickListener(this);
}
- 作用:为
btn_data
和btn_show
按钮添加点击事件监听。
3.7 Handler
消息处理
java
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_LOAD_DATA:
if (thread == null) {
thread = new Thread(() -> initJsonData());
thread.start();
}
break;
case MSG_LOAD_SUCCESS:
isLoaded = true;
break;
case MSG_LOAD_FAILED:
Toast.makeText(JsonDataActivity.this, "Parse Failed", Toast.LENGTH_SHORT).show();
break;
}
}
};
MSG_LOAD_DATA
:启动新线程加载数据(initJsonData()
)。MSG_LOAD_SUCCESS
:数据加载成功,isLoaded
置为true
。MSG_LOAD_FAILED
:加载失败时显示Toast
提示。
3.8 initJsonData()
方法
java
private void initJsonData() {
ArrayList<JsonBeanNew> jsonBeanNewList = new ArrayList<>();
// 示例数据:端州区 -> 城东街道 -> 杏花社区、工农社区
JsonBeanNew province = new JsonBeanNew();
province.setName("端州区");
List<JsonBeanNew.CityBeanNew> cityList = new ArrayList<>();
JsonBeanNew.CityBeanNew city = new JsonBeanNew.CityBeanNew();
city.setName("城东街道");
List<String> areaList = new ArrayList<>();
areaList.add("杏花社区居委会");
areaList.add("工农社区居委会");
city.setArea(areaList);
cityList.add(city);
province.setCityList(cityList);
jsonBeanNewList.add(province);
// 第二个城市样例
JsonBeanNew province2 = new JsonBeanNew();
province2.setName("鼎湖区");
List<JsonBeanNew.CityBeanNew> cityList2 = new ArrayList<>();
JsonBeanNew.CityBeanNew city2 = new JsonBeanNew.CityBeanNew();
city2.setName("坑口街道");
List<String> areaList2 = new ArrayList<>();
areaList2.add("坑口第一社区");
areaList2.add("坑口第二社区");
city2.setArea(areaList2);
cityList2.add(city2);
province2.setCityList(cityList2);
jsonBeanNewList.add(province2);
// 初始化 Picker 数据结构
options1Items = jsonBeanNewList;
options2Items = new ArrayList<>();
options3Items = new ArrayList<>();
for (JsonBeanNew provinceItem : jsonBeanNewList) {
ArrayList<String> cities = new ArrayList<>();
ArrayList<ArrayList<String>> areasList = new ArrayList<>();
for (JsonBeanNew.CityBeanNew cityItem : provinceItem.getCityList()) {
cities.add(cityItem.getName());
areasList.add(new ArrayList<>(cityItem.getArea()));
}
options2Items.add(cities);
options3Items.add(areasList);
}
mHandler.sendEmptyMessage(MSG_LOAD_SUCCESS);
}
-
作用:
-
构造省、市、区三级数据。
-
示例数据
端州区
→城东街道
→杏花社区、工农社区
鼎湖区
→坑口街道
→坑口第一社区、坑口第二社区
-
数据格式
options1Items
→ 省份(端州区、鼎湖区)options2Items
→ 城市列表(城东街道、坑口街道)options3Items
→ 区列表(杏花社区、工农社区、坑口第一社区、坑口第二社区)
-
3.9 showPickerView()
方法
java
private void showPickerView() {
OptionsPickerView pvOptions = new OptionsPickerBuilder(this, (options1, options2, options3, v) -> {
selectedOption1 = options1;
selectedOption2 = options2;
selectedOption3 = options3;
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;
if (btnShow instanceof android.widget.Button) {
((android.widget.Button) btnShow).setText(tx);
} else {
Toast.makeText(JsonDataActivity.this, tx, Toast.LENGTH_SHORT).show();
}
})
.setTitleText("城市选择")
.setDividerColor(Color.BLACK)
.setTextColorCenter(Color.BLACK)
.setContentTextSize(20)
.build();
pvOptions.setPicker(options1Items, options2Items, options3Items);
pvOptions.setSelectOptions(selectedOption1, selectedOption2, selectedOption3);
pvOptions.show();
}
-
作用:
- 创建 PickerView,并绑定
options1Items
、options2Items
、options3Items
数据。 - 显示选择的省、市、区,并渲染到
btn_show
按钮或Toast
提示。
- 创建 PickerView,并绑定
3.10 onClick()
方法**
java
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_data:
Toast.makeText(this, "地址已在启动时加载,不再重复加载", Toast.LENGTH_SHORT).show();
break;
case R.id.btn_show:
if (isLoaded) {
showPickerView();
} else {
Toast.makeText(JsonDataActivity.this, "正在加载地址数据,请稍后...", Toast.LENGTH_SHORT).show();
}
break;
}
}
-
作用
- 点击
btn_data
提示数据已加载。 - 点击
btn_show
,如果数据已加载,则显示 PickerView,否则提示等待加载。
- 点击
四、完整的代码
4.1 JsonDataActivity
java
import static cn.hutool.json.XMLTokener.entity;
import android.annotation.SuppressLint;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.bigkoo.pickerview.builder.OptionsPickerBuilder;
import com.bigkoo.pickerview.view.OptionsPickerView;
import com.google.gson.Gson;
import com.imindbot.medicalinformation.R;
import com.lljjcoder.bean.CityBean;
import org.json.JSONArray;
import java.util.ArrayList;
import java.util.List;
public class JsonDataActivity extends AppCompatActivity implements View.OnClickListener {
private List<JsonBeanNew> 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;
private int selectedOption1 = 0;
private int selectedOption2 = 0;
private int selectedOption3 = 0;
private View btnShow;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_json_data);
initView();
// ✅ 自动加载数据
mHandler.sendEmptyMessage(MSG_LOAD_DATA);
}
private void initView() {
findViewById(R.id.btn_data).setOnClickListener(this);
btnShow = findViewById(R.id.btn_show);
btnShow.setOnClickListener(this);
}
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_LOAD_DATA:
if (thread == null) {
thread = new Thread(() -> initJsonData());
thread.start();
}
break;
case MSG_LOAD_SUCCESS:
isLoaded = true;
break;
case MSG_LOAD_FAILED:
Toast.makeText(JsonDataActivity.this, "Parse Failed", Toast.LENGTH_SHORT).show();
break;
}
}
};
private void initJsonData() {
ArrayList<JsonBeanNew> jsonBeanNewList = new ArrayList<>();
// 构造示例数据:端州区 -> 城东街道 -> 杏花社区、工农社区
JsonBeanNew province = new JsonBeanNew();
province.setName("端州区");
List<JsonBeanNew.CityBeanNew> cityList = new ArrayList<>();
JsonBeanNew.CityBeanNew city = new JsonBeanNew.CityBeanNew();
city.setName("城东街道");
List<String> areaList = new ArrayList<>();
areaList.add("杏花社区居委会");
areaList.add("工农社区居委会");
city.setArea(areaList);
cityList.add(city);
province.setCityList(cityList);
jsonBeanNewList.add(province);
// 第二个城市样例
JsonBeanNew province2 = new JsonBeanNew();
province2.setName("鼎湖区");
List<JsonBeanNew.CityBeanNew> cityList2 = new ArrayList<>();
JsonBeanNew.CityBeanNew city2 = new JsonBeanNew.CityBeanNew();
city2.setName("坑口街道");
List<String> areaList2 = new ArrayList<>();
areaList2.add("坑口第一社区");
areaList2.add("坑口第二社区");
city2.setArea(areaList2);
cityList2.add(city2);
province2.setCityList(cityList2);
jsonBeanNewList.add(province2);
// 初始化 Picker 数据结构
options1Items = jsonBeanNewList;
options2Items = new ArrayList<>();
options3Items = new ArrayList<>();
for (JsonBeanNew provinceItem : jsonBeanNewList) {
ArrayList<String> cities = new ArrayList<>();
ArrayList<ArrayList<String>> areasList = new ArrayList<>();
for (JsonBeanNew.CityBeanNew cityItem : provinceItem.getCityList()) {
cities.add(cityItem.getName());
areasList.add(new ArrayList<>(cityItem.getArea()));
}
options2Items.add(cities);
options3Items.add(areasList);
}
mHandler.sendEmptyMessage(MSG_LOAD_SUCCESS);
}
private void showPickerView() {
OptionsPickerView pvOptions = new OptionsPickerBuilder(this, (options1, options2, options3, v) -> {
selectedOption1 = options1;
selectedOption2 = options2;
selectedOption3 = options3;
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;
// ✅ 渲染到 btn_show 上
if (btnShow instanceof android.widget.Button) {
((android.widget.Button) btnShow).setText(tx);
} else {
Toast.makeText(JsonDataActivity.this, tx, Toast.LENGTH_SHORT).show();
}
})
.setTitleText("城市选择")
.setDividerColor(Color.BLACK)
.setTextColorCenter(Color.BLACK)
.setContentTextSize(20)
.build();
// ✅ 设置默认选中项
pvOptions.setPicker(options1Items, options2Items, options3Items);
pvOptions.setSelectOptions(selectedOption1, selectedOption2, selectedOption3);
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:
Toast.makeText(this, "地址已在启动时加载,不再重复加载", Toast.LENGTH_SHORT).show();
break;
case R.id.btn_show:
if (isLoaded) {
showPickerView();
} else {
Toast.makeText(JsonDataActivity.this, "正在加载地址数据,请稍后...", Toast.LENGTH_SHORT).show();
}
break;
}
}
}
4.2 activity_json_data.xml
xml
<?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:visibility="gone"
android:text="隐藏"/>
<Button
android:id="@+id/btn_show"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="地区选择"
android:layout_marginTop="@dimen/activity_horizontal_margin" />
</LinearLayout>
五、总结
通过本文的实现方案,开发者可以快速在 Android 应用中集成省市区三级地区选择器。不仅支持异步加载本地或远程 JSON 数据,还具备结构清晰、扩展性强、界面友好的特点。实际项目中,也可对接国家统计局标准数据或调用后端接口获取地区列表,进一步提升准确性与可维护性。