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

相关推荐
QING61840 分钟前
详解:Kotlin 类的继承与方法重载
android·kotlin·app
QING61841 分钟前
Kotlin 伴生对象(Companion Object)详解 —— 使用指南
android·kotlin·app
声声codeGrandMaster44 分钟前
Django之modelform使用
后端·python·django
一一Null1 小时前
Android studio 动态布局
android·java·android studio
假女吖☌1 小时前
Maven 编译指定模版
java·开发语言·maven
体育分享_大眼3 小时前
从零搭建高并发体育直播网站:架构设计、核心技术与性能优化实战
java·性能优化·系统架构
琢磨先生David4 小时前
Java 在人工智能领域的突围:从企业级架构到边缘计算的技术革新
java·人工智能·架构
计算机学姐4 小时前
基于SpringBoo的地方美食分享网站
java·vue.js·mysql·tomcat·mybatis·springboot·美食
Hanson Huang7 小时前
【数据结构】堆排序详细图解
java·数据结构·排序算法·堆排序
慕容静漪7 小时前
如何本地安装Python Flask并结合内网穿透实现远程开发
开发语言·后端·golang