🐳 《Android》 安卓开发教程 - 三级地区联动

一、什么是三级地区联动

安卓的三级地区联动 ,是指在移动应用中实现的 省、市、区(或县) 的三级级联选择器(也叫地区联动选择器)。用户在选择省份后,系统自动更新市级选项,选择市级后再自动更新区级选项,从而快速准确地定位用户所选的具体区域。

该功能广泛用于地址选择、区域划分、社区服务等场景。。

示例说明:

比如用户依次选择:

  • 省:广东省
  • 市:广州市
  • 区:天河区

这个过程就是三级联动。每一层级的选项都会根据上一级的选项动态变化。

二、实现效果展示

📌 用户点击"地区选择"按钮,即可弹出省市区三级联动选择框,选择完成后将结果展示在按钮上。

三、核心实现思路

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_databtn_show)。
    • 发送 MSG_LOAD_DATA 消息,开始加载数据。

3.6 initView() 方法

java 复制代码
private void initView() {
    findViewById(R.id.btn_data).setOnClickListener(this);
    btnShow = findViewById(R.id.btn_show);
    btnShow.setOnClickListener(this);
}
  • 作用:为 btn_databtn_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,并绑定 options1Itemsoptions2Itemsoptions3Items 数据。
    • 显示选择的省、市、区,并渲染到 btn_show 按钮或 Toast 提示。

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 数据,还具备结构清晰、扩展性强、界面友好的特点。实际项目中,也可对接国家统计局标准数据或调用后端接口获取地区列表,进一步提升准确性与可维护性。

相关推荐
蓝澈112111 分钟前
迪杰斯特拉算法之解决单源最短路径问题
java·数据结构
Kali_0718 分钟前
使用 Mathematical_Expression 从零开始实现数学题目的作答小游戏【可复制代码】
java·人工智能·免费
rzl0230 分钟前
java web5(黑马)
java·开发语言·前端
君爱学习36 分钟前
RocketMQ延迟消息是如何实现的?
后端
guojl1 小时前
深度解读jdk8 HashMap设计与源码
java
Falling421 小时前
使用 CNB 构建并部署maven项目
后端
guojl1 小时前
深度解读jdk8 ConcurrentHashMap设计与源码
java
程序员小假1 小时前
我们来讲一讲 ConcurrentHashMap
后端
爱上语文1 小时前
Redis基础(5):Redis的Java客户端
java·开发语言·数据库·redis·后端