Flutter之城市选择 2024-09-06 周五

需求简介

涉及到地址相关的内容,首先要区分国内和国外。如果涉外,第一层就是选国家。如果仅仅是国内,那么会分成两部分:

  1. 省市区三级(比如浙江省,杭州市,临安区)一般采用选择的方式;

  2. 详细地址(街道,社区,门牌号),一般会提供一个输入框进行填写;

插件选择

  1. 省市区三级选择,我们采用了flutter_city_picker这个插件。虽然热度不是很高,不过用下来感觉还可以的。
  1. 按首字母进行分组,这里就要用到插件lpinyin。虽然热度也不是很高,但是实用。

数据处理

接口获取31个省市区三级数据

需要后台提供一个接口,把全国31个省市区的内容都返回。比如我们这里就用map嵌套的方式返回31个省市区的数据。

image.png

将Map转化为插件需要的数据结构

  • 插件需要的数据结构AddressNode定义如下:
/// 城市的数据模型
class AddressNode {
  /// 名称
  String? name;

  /// 代码
  String? code;

  /// 首字母
  String? letter;

  AddressNode({
    this.name,
    this.code,
    this.letter,
  });

  factory AddressNode.fromJson(Map<String, dynamic> json) {
    return AddressNode(
      name: json["name"].toString(),
      code: json["code"].toString(),
      letter: json["letter"].toString(),
    );
  }
}
  • 为了满足插件的需要,定义了如下变量,保留中间的交互数据
  /// 选择后显示的文本,当前的格式为“省市区”,name字段直接拼接
  String area = "";

  /// 从后台获取所有省市区三级数据,Map结构
  List allCityList = [];

  /// 用户选择的省,有带选择的城市列表,Map结构
  Map selectProvince = {};

  /// 用户选择的城市,有带选择的区列表,Map结构
  Map selectCity = {};

  /// 插件需要的省列表,AddressNode结构
  List<AddressNode> firstList = [];

  /// 插件需要的城市列表,AddressNode结构
  List<AddressNode> secondList = [];

  /// 插件需要的地区列表,AddressNode结构
  List<AddressNode> thirdList = [];

  /// 省市区三级结构选择完毕后,选择组件消失,返回用户选择的省市区三个AddressNode结构
  List<AddressNode> finishData = [];
  • 获取省市区三级数据的后台调用,返回的是三级的Map列表。由于第一级的插件数据List<AddressNode> firstList固定,所以,这个时候可以一起确定。一进入页面就可以调用接口把数据获取到,等用户选择地区的时候直接显示。
  /// 获取所有省市区三级数据的接口调用
  /// 同时构造选择组件的第1级数据(省列表)
  void getCityList() async {
    ApiResponse response = await CommonApi.getCities();
    if (response.code == 0) {
      allCityList = response.data;
      firstList = allCityList.map((item) => AddressNode(
        name: item['name'],
        code: item['code'],
        letter: PinyinHelper.getFirstWordPinyin(item['name']).substring(0, 1).toUpperCase(),
      )).toList();
      update();
    }
  }

实现接口

插件的城市列表和地区列表数据的构造方式比较特殊,需要实现接口CityPickerListener中定义的方法。由于我们采用的是GetX构造页面,所以我们可以在Logic文件中实现接口函数。

class EditProfileLogic extends GetxController implements CityPickerListener {
/// 其他代码……

  @override
  Future<List<AddressNode>> onDataLoad(int index, String code, String name) async {
    if (index == 0) {
      return firstList;
    } else if (index == 1) {
      for (Map province in allCityList) {
        if (province["code"] == code && province["name"] == name ) {
          selectProvince = province;
          break;
        }
      }
      List cityList = selectProvince["children"] ?? [];
      secondList = cityList.map((item) => AddressNode(
        name: item['name'],
        code: item['code'],
        letter: PinyinHelper.getFirstWordPinyin(item['name']).substring(0, 1).toUpperCase(),
      )).toList();
      return secondList;
    } else if (index == 2) {
      List cityList = selectProvince["children"] ?? [];
      for (Map city in cityList) {
        if (city["code"] == code && city["name"] == name ) {
          selectCity = city;
          break;
        }
      }
      List districtList = selectCity["children"] ?? [];
      thirdList = districtList.map((item) => AddressNode(
        name: item['name'],
        code: item['code'],
        letter: PinyinHelper.getFirstWordPinyin(item['name']).substring(0, 1).toUpperCase(),
      )).toList();
      return thirdList;
    } else {
      return [];
    }
  }
}
  • 第1列(省列表)的逻辑比较简单,在调用后台接口时候构造一次就可以了,这里直接调用就好。

  • 第2列(城市列表)的逻辑稍微复杂一点:onDataLoad中的参数name和code是用户选中的省的name和code;通过这两个参数,就可以遍历原始的allCityList,找出选中的省,数据暂存在selectProvince这个Map变量中。其中的children字段就是该省对应的城市列表,将Map数据成员转化为AddressNode成员(会丢失children字段),就是第2列的显示数据,保存在secondList中),返回给插件使用。

  • 第3列(地区列表)的逻辑和城市列表的逻辑类似:onDataLoad中的参数name和code是用户选中的城市的name和code;而遍历的对象就是selectProvince这个Map变量的children字段,这样就可以把用户选择的城市找出来,暂存在selectCity中,其中的children字段就是该城市对应的地区列表。将数据成员由Map转化为AddressNode,就是第3列的显示数据,保存在thirdList,返回给插件使用。

省市区三级结构选择完成后的处理

这个可以再接口函数onFinish中处理。我们这里的处理代码如下所示:

  @override
  void onFinish(List<AddressNode> data) async {
    /// 拼接用户选择的字符串
    String newCity = "";
    for (var node in data) {
      newCity += "${node.name}";
    }

    /// 调用接口修改数据
    ApiResponse response =
        await PersonApi.editDetail({"city": newCity});
    if (response.code == 0) {
      /// 更新显示,并保留用户上次选择的数据
      area = newCity;
      finishData = data;
      update();

      /// 发送消息,让我的页面更新用户信息
      EventBusExtension.emitUpdateUserInfo({"source": "编辑资料页面修改地区"});
    } else {
      ToastUtil.showText(text: response.msg ?? "");
    }
  }
  • 接口函数onFinish的参数List<AddressNode> data就是用户选择的省市区三个AddressNode结构。

  • 我们根据返回的用户选择数据,构造接口需要的数据;然后调用接口,修改后台数据。接口成功之后,回显在界面上,同时将用户选择的数据保存起来。

  • 如果接口调用失败,那么相当于什么也做。

  • 用户选择的数据保存在finishData变量当中,作为参数,传入插件的界面组件中,可以在第二次显示插件的时候以不同的颜色显示上次的选项。

界面组件

界面组件包装成了一个静态函数,用起来比较方便。这里是一个点击响应,点一下,显示地址选择插件。在GetX的view文件中。

          onSelectClick: () {
            CityPicker.show(
              context: Get.context!,
              cityPickerListener: logic, /// 实现接口的类,这里是GetX的Logic
              initialAddress: logic.finishData, /// 用户上次的选项,在第二次显示的时候会以不同颜色显示
              itemHeadLineColor: StyleUtils.themeColor,
              selectedLabelColor: StyleUtils.themeColor,
              tabIndicatorColor: StyleUtils.themeColor,
              itemSelectedTextStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: StyleUtils.themeColor),
            );
          },

扩展

  • 不单单是城市选择,其实其他的多级选择也可以用这个插件(比如标签)。数据元素name和code两个字段一个用来显示,一个用来做id,很多场景也是够用了。

  • 级数也不一定是三级,可以根据实际情况进行定义。多几级少几级都是可以的,只要在接口函数中定义清楚就可以了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容