城市選擇器在項目開發中一般都會用到,基於flutter版本的也有一個city_pickers但是已經很久沒有人維護了,項目中之前也用的是這個,最近升級到flutter1.17.x后,發現有一定的概率閃退,無奈之下,只能自動動手擼一個了
demo下載地址:https://github.com/qqcc1388/city_picker
CityPickerView能夠實現以下功能
- 顯示省市區地址,市或者區可以為空白數據
- 省市區數據支持自定義,但是格式要按照city.json中個格式來,如果需要外部傳入省市區數據,直接使用params參數即可
- 支持選中之后回調選中內容以及對於的省市區code碼
思路
利用cupertino.dart中的CupertinoPicker來實現單層數據顯示,通過row實現3層數據的滾動顯示,然后讓3個CupertinoPicker實現聯動即可實現地址選擇器,
代碼
import 'dart:convert';
import 'package:city_picker/city_result.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
typedef ResultBlock = void Function(CityResult result);
class CityPickerView extends StatefulWidget {
// json數據可以從外部傳入,如果外部有值,取外部值
final List params;
// 結果返回
final ResultBlock onResult;
CityPickerView({this.onResult, this.params});
@override
_CityPickerViewState createState() => _CityPickerViewState();
}
class _CityPickerViewState extends State<CityPickerView> {
List datas = [];
int provinceIndex;
int cityIndex;
int areaIndex;
FixedExtentScrollController provinceScrollController;
FixedExtentScrollController cityScrollController;
FixedExtentScrollController areaScrollController;
CityResult result = CityResult();
bool isShow = false;
List get provinces {
if (datas.length > 0) {
if (provinceIndex == null) {
provinceIndex = 0;
result.province = provinces[provinceIndex]['label'];
result.provinceCode = provinces[provinceIndex]['value'].toString();
}
return datas;
}
return [];
}
List get citys {
if (provinces.length > 0) {
return provinces[provinceIndex]['children'] ?? [];
}
return [];
}
List get areas {
if (citys.length > 0) {
if (cityIndex == null) {
cityIndex = 0;
result.city = citys[cityIndex]['label'];
result.cityCode = citys[cityIndex]['value'].toString();
}
List list = citys[cityIndex]['children'] ?? [];
if (list.length > 0) {
if (areaIndex == null) {
areaIndex = 0;
result.area = list[areaIndex]['label'];
result.areaCode = list[areaIndex]['value'].toString();
}
}
return list;
}
return [];
}
// 保存選擇結果
_saveInfoData() {
var prs = provinces;
var cts = citys;
var ars = areas;
if (provinceIndex != null && prs.length > 0) {
result.province = prs[provinceIndex]['label'];
result.provinceCode = prs[provinceIndex]['value'].toString();
} else {
result.province = '';
result.provinceCode = '';
}
if (cityIndex != null && cts.length > 0) {
result.city = cts[cityIndex]['label'];
result.cityCode = cts[cityIndex]['value'].toString();
} else {
result.city = '';
result.cityCode = '';
}
if (areaIndex != null && ars.length > 0) {
result.area = ars[areaIndex]['label'];
result.areaCode = ars[areaIndex]['value'].toString();
} else {
result.area = '';
result.areaCode = '';
}
}
@override
void dispose() {
provinceScrollController.dispose();
cityScrollController.dispose();
areaScrollController.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
//初始化控制器
provinceScrollController = FixedExtentScrollController();
cityScrollController = FixedExtentScrollController();
areaScrollController = FixedExtentScrollController();
//讀取city.json數據
if (widget.params == null) {
_loadCitys().then((value) {
setState(() {
isShow = true;
});
});
} else {
datas = widget.params;
setState(() {
isShow = true;
});
}
}
Future _loadCitys() async {
var cityStr = await rootBundle.loadString('assets/city.json');
datas = json.decode(cityStr) as List;
//result默認取第一組值
return Future.value(true);
}
@override
Widget build(BuildContext context) {
return Material(
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
_firstView(),
_contentView(),
],
),
),
);
}
Widget _firstView() {
return Container(
height: 44,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
FlatButton(
child: Text('取消'),
onPressed: () {
Navigator.pop(context);
},
),
FlatButton(
child: Text('確定'),
onPressed: () {
if (widget.onResult != null) {
widget.onResult(result);
}
Navigator.pop(context);
},
),
]),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Colors.grey.withOpacity(0.1), width: 1)),
),
);
}
Widget _contentView() {
return Container(
// color: Colors.orange,
height: 200,
child: isShow
? Row(
children: <Widget>[
Expanded(child: _provincePickerView()),
Expanded(child: _cityPickerView()),
Expanded(child: _areaPickerView()),
],
)
: Center(
child: CupertinoActivityIndicator(
animating: true,
),
),
);
}
Widget _provincePickerView() {
return Container(
child: CupertinoPicker(
scrollController: provinceScrollController,
children: provinces.map((item) {
return Center(
child: Text(
item['label'],
style: TextStyle(color: Colors.black87, fontSize: 16),
maxLines: 1,
),
);
}).toList(),
onSelectedItemChanged: (index) {
provinceIndex = index;
if (cityIndex != null) {
cityIndex = 0;
if (cityScrollController.positions.length > 0) {
cityScrollController.jumpTo(0);
}
}
if (areaIndex != null) {
areaIndex = 0;
if (areaScrollController.positions.length > 0) {
areaScrollController.jumpTo(0);
}
}
_saveInfoData();
setState(() {});
},
itemExtent: 36,
),
);
}
Widget _cityPickerView() {
return Container(
child: citys.length == 0
? Container()
: CupertinoPicker(
scrollController: cityScrollController,
children: citys.map((item) {
return Center(
child: Text(
item['label'],
style: TextStyle(color: Colors.black87, fontSize: 16),
maxLines: 1,
),
);
}).toList(),
onSelectedItemChanged: (index) {
cityIndex = index;
if (areaIndex != null) {
areaIndex = 0;
if (areaScrollController.positions.length > 0) {
areaScrollController.jumpTo(0);
}
}
_saveInfoData();
setState(() {});
},
itemExtent: 36,
),
);
}
Widget _areaPickerView() {
return Container(
width: double.infinity,
child: areas.length == 0
? Container()
: CupertinoPicker(
scrollController: areaScrollController,
children: areas.map((item) {
return Center(
child: Text(
item['label'],
style: TextStyle(color: Colors.black87, fontSize: 16),
maxLines: 1,
),
);
}).toList(),
onSelectedItemChanged: (index) {
areaIndex = index;
_saveInfoData();
setState(() {});
},
itemExtent: 36,
),
);
}
}
class CityResult {
/// 省市區
String province = '';
String city = '';
String area = '';
/// 對應的編碼
String provinceCode = '';
String cityCode = '';
String areaCode = '';
CityResult({
this.province,
this.city,
this.area,
this.provinceCode,
this.cityCode,
this.areaCode,
});
CityResult.fromJson(Map<String, dynamic> json) {
province = json['province'];
city = json['city'];
area = json['area'];
provinceCode = json['provinceCode'];
cityCode = json['cityCode'];
areaCode = json['areaCode'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> datas = new Map<String, dynamic>();
datas['province'] = this.province;
datas['city'] = this.city;
datas['area'] = this.area;
datas['provinceCode'] = this.provinceCode;
datas['cityCode'] = this.cityCode;
datas['areaCode'] = this.areaCode;
return datas;
}
}
用法
// var cityStr = await rootBundle.loadString('assets/city.json');
// List datas = json.decode(cityStr) as List;
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (ctx) {
return CityPickerView(
// params: datas,
onResult: (res) {
print(res.toJson());
setState(() {
citySelect = res.toJson().toString();
});
},
);
},
);
demo下載地址:https://github.com/qqcc1388/city_picker
轉載請標注來源:https://www.cnblogs.com/qqcc1388/p/13452530.html