最近做一個省市區選擇的控制元件,產品的需求則是參考某銀行的省市區選擇的互動,是一個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 檢視具體的程式碼邏輯