Flutter 實現類似TabPicker省市區選擇

Rx_Re發表於2020-07-20

最近做一個省市區選擇的控制元件,產品的需求則是參考某銀行的省市區選擇的互動,是一個TabPicker的互動的控制元件

1.主要的頁面邏輯

本身Flutter自帶有一個CupertinoPicker可以實現三級聯動選擇

後面根據TabPicker互動,發現實現也不難,使用TabBar+TabBarView+ListView 就可以實現對應的互動功能

2.構建細節

主要是根據city_pickers 這個開源專案做的修改

以下是實現的主要程式碼

2.1 構建底部彈出框

 ///構建底部彈出框
  Widget _bottomBuild() {
    if (provinceCityAreaList == null) {
      return Container();
    }
    return Scaffold(
      appBar: PreferredSize(
          child: Container(
            color: Colors.white,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisAlignment: MainAxisAlignment.start,
              children: <Widget>[
                _buildHeaderTitle(),
                _buildHeaderTabBar(),
                Container(
                  height: 0.5,
                  margin: EdgeInsets.symmetric(horizontal: 16),
                  color: Color(0xFFEBEBEB),
                )
              ],
            ),
          ),
          preferredSize: Size.fromHeight(84)),
      body: TabBarView(
        controller: _tabController,
        children: _buildTabBarView(),
      ),
    );
複製程式碼

構建整體的彈框介面,使用PreferredSize來構建頂部的title和TabBar,body則是對應的TabBarView,TabBar和TabBarView則是通過同一個_tabController來聯動的

2.2 TabBarView 的實現邏輯

 ///構建Header tabBar
  List<Widget> _buildTabBarView() {
    List<Widget> tabList = [];
    tabList.add(_MyCityPicker(
      key: Key('province'),
      isShow: widget.showType.contain(ShowType.p),
      height: widget.height,
      controller: provinceController,
      value: targetProvince.label,
      itemList: provinceCityAreaList.map((v) {
        return LabelSelectBean(
            label: v.label, select: v.select == null ? false : v.select);
      }).toList(),
      changed: (index) {
        _onProvinceChange(index);
        Future.delayed(Duration(milliseconds: 200))
            .then((value) => _tabController.animateTo(1));
      },
    ));
    if (_provinceName != null) {
      tabList.add(_MyCityPicker(
        key: Key('citys $targetProvince'),
        // 這個屬性是為了強制重新整理
        isShow: widget.showType.contain(ShowType.c),
        controller: cityController,
        height: widget.height,
        value: targetCity == null ? null : targetCity.label,
        itemList: getCityItemList(),
        changed: (index) {
          _onCityChange(index);
        },
      ));
    }

    if (_cityName != null && widget.showType == ShowType.pca) {
      tabList.add(_MyCityPicker(
        key: Key('towns $targetCity'),
        isShow: widget.showType.contain(ShowType.a),
        controller: areaController,
        value: targetArea == null ? null : targetArea.label,
        height: widget.height,
        itemList: getAreaItemList(),
        changed: (index) {
          _areaName = targetCity.children[index].label;
          _onAreaChange(index);
        },
      ));
    }
    return tabList;
  }
複製程式碼

通過新增不同的CityPicker,來動態的展示對應的TabBarView。

這裡有個坑,動態新增TabBar和TabBarView的時候需要重新賦值給_tabController,以及動態新增TabBarView,兩者要長度匹配才行,主要的實現在各自的選擇回撥方法

setState(() {
          _tabLabels = [targetProvince.label, "請選擇"];
          _provinceName = targetProvince.label;
          _tabController =
              TabController(length: _tabLabels.length, vsync: this);
        });
複製程式碼

每次改變TabController都需要動態設定一次

2.3 TabBarView展示

TabBar展示主要是展示List列表即可

_MyCityPicker主要的builder程式碼

Container(
      color: Colors.white,
      child: ListView.builder(
          itemBuilder: (context, index) {
            return Material(
                color: Colors.white,
                child: InkWell(
                  child: Container(
                      padding: EdgeInsets.only(left: 16, top: 8, bottom: 8),
                      child: Row(
                        children: <Widget>[
                          Text(
                            '${widget.itemList[index].label}  ',
                            maxLines: 1,
                            style: TextStyle(color: Colors.black, fontSize: 16),
                            overflow: TextOverflow.ellipsis,
                          ),
                          widget.itemList[index].select
                              ? Icon(
                                  Icons.check,
                                  size: 20,
                                  color: Color(0xFFD71718),
                                )
                              : Container()
                        ],
                      )),
                  onTap: () {
                    widget.changed(index);
                  },
                ));
          },
          itemCount: widget.itemList.length),
    );
複製程式碼

就是一個簡單的ListBuilder

3.資料回顯

選擇完資料之後需要,再一次點選的時候,需要回顯上一次的資料,邏輯也比較簡單

void _initLocation(String locationCode) {
    Result dataResult = widget.initDataResult;

    if (dataResult != null && dataResult.provinceName != null) {
      {
        CityPoint point = provinceCityAreaList.firstWhere(
            (province) => dataResult.provinceName == province.label,
            orElse: () => null);

        if (point != null) {
          targetProvince = point;
          targetProvince.select = true;
          _provinceName = targetProvince.label;
        } else {
          targetProvince = provinceCityAreaList[0];
        }
      }

      {
        CityPoint point = targetProvince.children.firstWhere(
            (city) => dataResult.cityName == city.label,
            orElse: () => null);

        if (point != null) {
          targetCity = point;
          targetCity.select = true;
          _cityName = targetCity.label;
        } else {
          targetCity = targetProvince.children[0];
        }
      }
      {
        CityPoint point = targetCity.children.firstWhere(
            (area) => dataResult.areaName == area.label,
            orElse: () => null);

        if (point != null) {
          targetArea = point;
          targetArea.select = true;
          _areaName = targetArea.label;
        } else {
          targetArea = targetCity.children[0];
        }
      }
    } else {
      targetProvince = provinceCityAreaList[0];
      targetCity = targetProvince.children[0];
      targetArea = targetCity.children[0];
    }
  }
複製程式碼

需要把回顯的資料通過Result結果返回,然後遍歷對應的List資料,設定即可

主要程式碼可以通過 fluttertabpicker 檢視具體的程式碼邏輯

相關文章