Flutter仿京东地址选择器

1.jpg

2.jpg

3.jpg

样式如图


贴代码


import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app/common/Constant.dart';
import 'package:flutter_app/common/http/Api.dart';
import 'package:flutter_app/common/http/ApiUrl.dart';
import 'package:flutter_app/model/user_center/HHSRegion.dart';
import 'package:flutter_app/model/user_center/LocalQueryBean.dart';


///地址选择器
class SelectAddressWidget extends StatefulWidget {

  SelectAddressWidget({Key key, @required this.valueCb}) :super(key: key);

  ///回调函数
  final Function valueCb;

  @override
  _SelectAddressWidgetState createState() => new _SelectAddressWidgetState();
}

class _SelectAddressWidgetState extends State<SelectAddressWidget>
    with SingleTickerProviderStateMixin {

  ///区域信息列表
  List<HHSRegion> localList;

  ///TabBarController
  TabController _tabController;

  ///选择的省市县的名字
  String selectProvinceStr = '省份';
  String selectCityStr = '城市';
  String selectDistrictStr = '区/县';

  ///选择的省市县Id
  int selectProvinceId = -1;
  int selectCityId = -1;
  int selectDistrictId = -1;

  ///当前Tab位置
  int currentTabPos = 0;

  Map<String, int> selectMap = new Map();

  ///Tab Text样式
  TextStyle tabTvStyle = new TextStyle(
      color: Colors.black, fontSize: 18.0, fontWeight: FontWeight.w300);

  @override
  void initState() {
    // TODO: implement initState
    super.initState();

    ///给区域Id Map一个初始值
    selectMap['selectProvinceId'] = -1;
    selectMap['selectCityId'] = -1;
    selectMap['selectDistrictId'] = -1;

    ///初始化控制器
    _tabController = new TabController(length: 3, vsync: this);
    _tabController.addListener(() {
      ///获取tab当前点击位置
      currentTabPos = _tabController.index;
      ///切换Tab重新请求列表数据
      if (_checkTabCanSelect(currentTabPos)) {
        _queryLocal(currentTabPos == 0 ? 1 : currentTabPos == 1
            ? selectProvinceId
            : selectCityId);
      }

      print(currentTabPos);
    });

    ///第一次进来 这里调用我自己的接口 查询全国的所有省 可以替换成其他
    _queryLocal(1);
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(

        ///去掉左箭头
        automaticallyImplyLeading: false,
        title: new Text(
          '配送至', style: new TextStyle(color: const Color(0xff666666)),),
        centerTitle: true,
        elevation: 0.5,
        backgroundColor: Colors.white,
        actions: <Widget>[
          new IconButton(
              icon: new Icon(Icons.close, color: const Color(0xff666666),),
              onPressed: () => Navigator.pop(context))
        ],
      ),
      body: _getBody(),
    );
  }

  ///构建底部视图
  Widget _getBody() {
    if (_showLoadingDialog()) {
      return new Center(child: new CircularProgressIndicator(),);
    } else {
      return _buildContent();
    }
  }

  ///根据数据是否有返回显示加载条或者列表
  bool _showLoadingDialog() {
    if (localList == null || localList.length == 0) {
      return true;
    } else {
      return false;
    }
  }

  ///有数据时构建tab和区域列表
  Widget _buildContent() {
    return new Container(
      child: new Column(
        children: <Widget>[
          new Padding(padding: const EdgeInsets.only(top: 18.0)),
          new TabBar(
            indicatorColor: Colors.black,
            controller: _tabController,
            tabs: <Widget>[
              new Text('$selectProvinceStr', style: tabTvStyle, maxLines: 1,),
              new Text('$selectCityStr', style: tabTvStyle, maxLines: 1,),
              new Text('$selectDistrictStr', style: tabTvStyle, maxLines: 1,),
            ],
          ),
          new Padding(padding: const EdgeInsets.only(top: 18.0)),

          _buildListView(),
        ],
      ),
      color: Colors.white,
    );
  }

  ///构建列表
  Widget _buildListView() {
    return new Expanded(
        child: new ListView.builder(
          shrinkWrap: true,
          itemCount: localList.length,
          itemBuilder: (BuildContext context, int position) {
            return _buildListRow(position);
          },
        )
    );
  }

  ///构建子项
  Widget _buildListRow(int position) {
    return new ListTile(
      title: new Text('${localList[position].regionName}',
        style: new TextStyle(color: const Color(0xff666666), fontSize: 15.0),),
      onTap: () => _onLocalSelect(position),);
  }

  ///区域位置选择
  _onLocalSelect(int position) {
    _setSelectData(position);
    if (currentTabPos != 2) {
      _queryLocal(localList[position].regionId);
    }
  }

  ///设置选择的数据
  ///根据当前选择的列表项的position 和 Tab的currentTabPos
  ///@params position 当前列表选择的省或市或县的position
  _setSelectData(position) {
    if (currentTabPos == 0) {
      selectProvinceId = localList[position].regionId;
      selectProvinceStr = localList[position].regionName;
      selectMap['provinceId'] = selectProvinceId;
      setState(() {
        selectCityStr = '城市';
        selectDistrictStr = '区/县';
      });
      selectCityId = -1;
      selectDistrictId = -1;
    }

    if (currentTabPos == 1) {
      selectCityId = localList[position].regionId;
      selectCityStr = localList[position].regionName;
      selectMap['selectCityId'] = selectCityId;
      setState(() {
        selectDistrictStr = '区/县';
      });
      selectDistrictId = -1;
    }
    if (currentTabPos == 2) {
      selectDistrictId = localList[position].regionId;
      selectDistrictStr = localList[position].regionName;
      selectMap['selectDistrictId'] = selectDistrictId;

      ///拼接区域字符串 回调给上个页面 关闭弹窗
      String localStr = selectProvinceStr + ' ' + selectCityStr + ' ' +
          selectDistrictStr;
      widget.valueCb(selectMap, localStr);
      Navigator.pop(context);
    }
    currentTabPos++;
    _tabController.animateTo(currentTabPos);
  }

  ///检查是否可以选择下级Tab
  bool _checkTabCanSelect(int position) {
    if (position == 0) {
      return true;
    }
    if (position == 1) {
      if (selectProvinceId == -1) {
        _tabController.animateTo(0);
        _showSnack();
        return false;
      }
      return true;
    }

    if (position == 2) {
      if (selectProvinceId == -1 && selectCityId == -1) {
        _tabController.animateTo(0);
        _showSnack();
        return false;
      }
      if (selectProvinceId != -1 && selectCityId == -1) {
        _tabController.animateTo(1);
        _showSnack();
        return false;
      }
      return true;
    }
  }

  ///显示错误信息
  _showSnack() {
    Scaffold.of(context).showSnackBar(
        new SnackBar(content: new Text('请先选择上级地区')));
  }

  ///查询区域信息
  _queryLocal(int parentId) {
    String url = Constant.HOST + ApiUrl.LOCAL_QUERY;
    Map<String, dynamic> params = new Map();
    params['parentId'] = parentId;
    Api.post(url, (Response response) {
      LocalQueryBean localQueryBean = LocalQueryBean.fromJSON(response.data);

      ///这里防止没有区的城市查询不到还不会关闭对话框回调到地址页面 例如三亚市
      ///写的有点乱
      if (currentTabPos == 2 && localQueryBean.regionList == null) {
        String localStr = selectProvinceStr + ' ' + selectCityStr;
        widget.valueCb(selectMap, localStr);
        Navigator.pop(context);
      }

      setState(() {
        localList = localQueryBean.regionList;
      });
    },
        errorCallBack: (errMsg) {

        },
        params: params);
  }

  @override
  void dispose() {
    // TODO: implement dispose
    _tabController.dispose();
    super.dispose();
  }
}

HHSRegion类

import 'package:json_annotation/json_annotation.dart';

part 'HHSRegion.g.dart';

@JsonSerializable()
class HHSRegion {
  int regionId; // 当前区域ID
  int parentId; // 当前区域父ID
  String regionName; // 区域名字
  int regionType; // 区域类型(0:国;1:省;2:市;3:区)
  int agencyId;

  HHSRegion(this.regionId, this.parentId, this.regionName, this.regionType,
      this.agencyId); // 不知道是啥

  //反序列化
  factory HHSRegion.fromJson(Map<String, dynamic> json)=>
      _$HHSRegionFromJson(json);

  //序列化
  Map<String, dynamic> toJson() => _$HHSRegionToJson(this);


}

需要将 _queryLocal(int parentId)这个方法替换成你自己的请求地址列表 或者加载本地JSON

做的不完善 而且灵活度非常低 对于选择地址应该够了 再变态也改不到哪去了

使用方法

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

推荐阅读更多精彩内容