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);

  ///回調(diào)函數(shù)
  final Function valueCb;

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

class _SelectAddressWidgetState extends State<SelectAddressWidget>
    with SingleTickerProviderStateMixin {

  ///區(qū)域信息列表
  List<HHSRegion> localList;

  ///TabBarController
  TabController _tabController;

  ///選擇的省市縣的名字
  String selectProvinceStr = '省份';
  String selectCityStr = '城市';
  String selectDistrictStr = '區(qū)/縣';

  ///選擇的省市縣Id
  int selectProvinceId = -1;
  int selectCityId = -1;
  int selectDistrictId = -1;

  ///當(dāng)前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();

    ///給區(qū)域Id Map一個初始值
    selectMap['selectProvinceId'] = -1;
    selectMap['selectCityId'] = -1;
    selectMap['selectDistrictId'] = -1;

    ///初始化控制器
    _tabController = new TabController(length: 3, vsync: this);
    _tabController.addListener(() {
      ///獲取tab當(dāng)前點(diǎn)擊位置
      currentTabPos = _tabController.index;
      ///切換Tab重新請求列表數(shù)據(jù)
      if (_checkTabCanSelect(currentTabPos)) {
        _queryLocal(currentTabPos == 0 ? 1 : currentTabPos == 1
            ? selectProvinceId
            : selectCityId);
      }

      print(currentTabPos);
    });

    ///第一次進(jìn)來 這里調(diào)用我自己的接口 查詢?nèi)珖乃惺?可以替換成其他
    _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(),
    );
  }

  ///構(gòu)建底部視圖
  Widget _getBody() {
    if (_showLoadingDialog()) {
      return new Center(child: new CircularProgressIndicator(),);
    } else {
      return _buildContent();
    }
  }

  ///根據(jù)數(shù)據(jù)是否有返回顯示加載條或者列表
  bool _showLoadingDialog() {
    if (localList == null || localList.length == 0) {
      return true;
    } else {
      return false;
    }
  }

  ///有數(shù)據(jù)時構(gòu)建tab和區(qū)域列表
  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,
    );
  }

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

  ///構(gòu)建子項(xiàng)
  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),);
  }

  ///區(qū)域位置選擇
  _onLocalSelect(int position) {
    _setSelectData(position);
    if (currentTabPos != 2) {
      _queryLocal(localList[position].regionId);
    }
  }

  ///設(shè)置選擇的數(shù)據(jù)
  ///根據(jù)當(dāng)前選擇的列表項(xiàng)的position 和 Tab的currentTabPos
  ///@params position 當(dāng)前列表選擇的省或市或縣的position
  _setSelectData(position) {
    if (currentTabPos == 0) {
      selectProvinceId = localList[position].regionId;
      selectProvinceStr = localList[position].regionName;
      selectMap['provinceId'] = selectProvinceId;
      setState(() {
        selectCityStr = '城市';
        selectDistrictStr = '區(qū)/縣';
      });
      selectCityId = -1;
      selectDistrictId = -1;
    }

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

      ///拼接區(qū)域字符串 回調(diào)給上個頁面 關(guān)閉彈窗
      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('請先選擇上級地區(qū)')));
  }

  ///查詢區(qū)域信息
  _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);

      ///這里防止沒有區(qū)的城市查詢不到還不會關(guān)閉對話框回調(diào)到地址頁面 例如三亞市
      ///寫的有點(diǎn)亂
      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; // 當(dāng)前區(qū)域ID
  int parentId; // 當(dāng)前區(qū)域父ID
  String regionName; // 區(qū)域名字
  int regionType; // 區(qū)域類型(0:國;1:省;2:市;3:區(qū))
  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

做的不完善 而且靈活度非常低 對于選擇地址應(yīng)該夠了 再變態(tài)也改不到哪去了

使用方法

///顯示選擇所在地址彈窗
  _showSelectAddrDialog() {
    ///顯示一個BottomSheet
    showModalBottomSheet(
      context: context,
      ///_handleLocal是被回調(diào)頁面的方法 用于接收選擇的地址
      builder: (context) => new SelectAddressWidget(valueCb: _handleLocal,),
    );
  }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末杖狼,一起剝皮案震驚了整個濱河市草雕,隨后出現(xiàn)的幾起案子上枕,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件讼稚,死亡現(xiàn)場離奇詭異,居然都是意外死亡雇庙,警方通過查閱死者的電腦和手機(jī)瞧甩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門钉跷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人肚逸,你說我怎么就攤上這事爷辙”蚧担” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵膝晾,是天一觀的道長栓始。 經(jīng)常有香客問我,道長玷犹,這世上最難降的妖魔是什么混滔? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮歹颓,結(jié)果婚禮上坯屿,老公的妹妹穿的比我還像新娘。我一直安慰自己巍扛,他們只是感情好领跛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著撤奸,像睡著了一般吠昭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上胧瓜,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天矢棚,我揣著相機(jī)與錄音,去河邊找鬼府喳。 笑死蒲肋,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的钝满。 我是一名探鬼主播兜粘,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼弯蚜!你這毒婦竟也來了孔轴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤碎捺,失蹤者是張志新(化名)和其女友劉穎路鹰,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體收厨,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡悍引,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了帽氓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片趣斤。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖黎休,靈堂內(nèi)的尸體忽然破棺而出浓领,到底是詐尸還是另有隱情玉凯,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布联贩,位于F島的核電站漫仆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏泪幌。R本人自食惡果不足惜盲厌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望祸泪。 院中可真熱鬧吗浩,春花似錦、人聲如沸没隘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽右蒲。三九已至阀湿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瑰妄,已是汗流浹背陷嘴。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留间坐,地道東北人罩旋。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像眶诈,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瓜饥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容