城市地区选择器 级联选择器
级联选择 Cascader - Ant Design https://ant-design.gitee.io/components/cascader-cn
city_picker_china | Flutter Package https://pub-web.flutter-io.cn/packages/city_picker_china
flutter_city_picker/lib/src/picker.dart at master · cenumi/flutter_city_picker · GitHub https://github.com/cenumi/flutter_city_picker/blob/master/lib/src/picker.dart
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:collection/collection.dart';
const _nameChildren = 'children';
const _nameCode = 'code';
const _nameName = 'name';
/// The result object returned by Navigator
class CityResult {
const CityResult({
required this.code,
required this.province,
this.city,
this.county,
});
final String code;
final String province;
final String? city;
final String? county;
@override
String toString() {
return '$province${city == null ? '' : ',$city'}${county == null ? '' : ',$county'}';
}
}
/// Data object for serialization
class CityNode {
CityNode(this.name, this.code, [this.children]);
final String name;
final String code;
final List<CityNode>? children;
factory CityNode.fromJson(Map<String, dynamic> json) {
final leaf = json[_nameChildren] == null;
return CityNode(
json[_nameName] as String,
json[_nameCode] as String,
leaf ? null : fromJsonList(json[_nameChildren] as List<dynamic>),
);
}
static List<CityNode> fromJsonList(List<dynamic> list) {
return list
.map((e) => CityNode.fromJson(e as Map<String, dynamic>))
.toList();
}
}
/// The picker widget
///
/// Normally put it in a dialog or bottomsheet
class CityPicker extends StatefulWidget {
/// Default constructor
///
/// All column index will be 0
const CityPicker({Key? key})
: code = null,
province = null,
city = null,
county = null,
super(key: key);
/// Construct the picker by name provided
///
/// The picker column index will stop at the name provided
/// If any property is null or not found in dataset, the index after will be 0
const CityPicker.fromName({Key? key, this.province, this.city, this.county})
: code = null,
super(key: key);
/// Construct the pick by code provided
///
/// The picker column index will stop at the code provided
/// if code is not in dataset, all column index will be 0
const CityPicker.fromCode({Key? key, this.code})
: province = null,
city = null,
county = null,
super(key: key);
/// The city code, e.g. 110111
final String? code;
/// The province name, e.g. 北京市
final String? province;
/// The city name, e.g. 北京市
final String? city;
/// The county name, e.g. 房山区
final String? county;
/// Data set from baidu 202104
/// rootBundle.loadStructuredData will cache the result so no worries
static Future<List<dynamic>> loadAssets() {
return rootBundle.loadStructuredData(
'packages/city_picker_china/assets/data_202104.json',
(str) async => jsonDecode(str),
);
}
/// Convert the data set to structured data
static Future<List<CityNode>> loadCityNodes() async {
return CityNode.fromJsonList(await loadAssets());
}
/// Search city infomation by code
///
/// If code is null or not in dataset, null is returned
static Future<CityResult?> searchWithCode(
String? code, {
List<dynamic>? dataSet,
}) async {
if (code == null || code.isEmpty) {
return null;
}
final provinces = dataSet ?? await loadAssets();
for (final province in provinces) {
if (province[_nameCode] == code) {
return CityResult(code: code, province: province[_nameName]);
}
for (final city in province[_nameChildren]) {
if (city[_nameCode] == code) {
return CityResult(
code: code,
province: province[_nameName],
city: city[_nameName],
);
}
for (final county in city[_nameChildren]) {
if (county[_nameCode] == code) {
return CityResult(
code: code,
province: province[_nameName],
city: city[_nameName],
county: county[_nameName],
);
}
}
}
}
return null;
}
/// Search city infomation by names
///
/// If [province] is null or not in dataset, null is returned
/// If any other name is null or not in dataset, the corresponding will be null
static Future<CityResult?> searchWithName({
String? province,
String? city,
String? county,
List<dynamic>? dataSet,
}) async {
if (province == null || province.isEmpty) {
return null;
}
final provinces = dataSet ?? await loadAssets();
final provinceInfo = provinces.firstWhereOrNull(
(element) => element[_nameName] == province,
);
if (provinceInfo == null) {
return null;
}
final cityInfo = (city == null || city.isEmpty)
? null
: (provinceInfo[_nameChildren] as List<dynamic>).firstWhereOrNull(
(element) => element[_nameName] == city,
);
if (cityInfo == null) {
return CityResult(code: provinceInfo[_nameCode], province: province);
}
final countyInfo = (county == null || county.isEmpty)
? null
: (cityInfo[_nameChildren] as List<dynamic>).firstWhereOrNull(
(element) => element[_nameName] == county,
);
if (countyInfo == null) {
return CityResult(
code: cityInfo[_nameCode],
province: province,
city: city,
);
}
return CityResult(
code: countyInfo[_nameCode],
province: province,
city: city,
county: county,
);
}
@override
State<CityPicker> createState() => _CityPickerState();
}
class _CityPickerState extends State<CityPicker> {
List<dynamic>? _data;
List<String>? _provinces;
List<String>? _cities;
List<String>? _counties;
final _provinceController = FixedExtentScrollController();
final _cityController = FixedExtentScrollController();
final _countyController = FixedExtentScrollController();
@override
void initState() {
super.initState();
() async {
_data = await CityPicker.loadAssets();
final result = widget.code != null
? await CityPicker.searchWithCode(widget.code!, dataSet: _data)
: await CityPicker.searchWithName(
province: widget.province,
city: widget.city,
county: widget.county,
dataSet: _data,
);
int indexProvince = 0;
int indexCity = 0;
int indexCounty = 0;
_provinces = _data!.mapIndexed<String>((index, element) {
final name = element[_nameName];
if (name == result?.province) {
indexProvince = index;
}
return name;
}).toList();
final dataCities = _data![indexProvince][_nameChildren] as List<dynamic>;
_cities = dataCities.mapIndexed<String>((index, element) {
final name = element[_nameName];
if (name == result?.city) {
indexCity = index;
}
return name;
}).toList();
final dataCounties = _data![indexProvince][_nameChildren][indexCity]
[_nameChildren] as List<dynamic>;
_counties = dataCounties.mapIndexed<String>((index, element) {
final name = element[_nameName];
if (name == result?.county) {
indexCounty = index;
}
return name;
}).toList();
setState(() {});
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
_provinceController.jumpToItem(indexProvince);
_cityController.jumpToItem(indexCity);
_countyController.jumpToItem(indexCounty);
});
}();
}
@override
void dispose() {
_provinceController.dispose();
_cityController.dispose();
_countyController.dispose();
super.dispose();
}
void _updateCities() {
final list = _data![_provinceController.selectedItem][_nameChildren]
as List<dynamic>;
_cities = list.map<String>((e) => e[_nameName]).toList();
}
void _updateCounties() {
final list = _data![_provinceController.selectedItem][_nameChildren]
[_cityController.selectedItem][_nameChildren] as List<dynamic>;
_counties = list.map<String>((e) => e[_nameName]).toList();
}
@override
Widget build(BuildContext context) {
final localization = MaterialLocalizations.of(context);
return Column(
children: [
ButtonBar(
alignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(localization.cancelButtonLabel),
),
TextButton(
onPressed: () {
CityResult? result;
if (_data == null) {
result = null;
} else {
final indexProvince = _provinceController.selectedItem;
final indexCity = _cityController.selectedItem;
final indexCounty = _countyController.selectedItem;
result = CityResult(
code: _data![indexProvince][_nameChildren][indexCity]
[_nameChildren][indexCounty][_nameCode],
province: _provinces![indexProvince],
city: _cities![indexCity],
county: _counties![indexCounty],
);
}
Navigator.pop(context, result);
},
child: Text(localization.okButtonLabel),
),
],
),
Expanded(
child: Row(
children: [
_Picker(
controller: _provinceController,
data: _provinces ?? [],
onSelectedItemChanged: () {
_cityController.jumpTo(0);
_countyController.jumpTo(0);
setState(() {
_updateCities();
_updateCounties();
});
},
),
_Picker(
controller: _cityController,
data: _cities ?? [],
onSelectedItemChanged: () {
_countyController.jumpTo(0);
setState(() {
_updateCounties();
});
},
),
_Picker(
controller: _countyController,
data: _counties ?? [],
onSelectedItemChanged: () {},
),
],
),
),
],
);
}
}
class _Picker extends StatelessWidget {
const _Picker({
Key? key,
required this.data,
required this.onSelectedItemChanged,
required this.controller,
}) : super(key: key);
final List<String> data;
final VoidCallback onSelectedItemChanged;
final FixedExtentScrollController controller;
@override
Widget build(BuildContext context) {
return Expanded(
child: CupertinoPicker.builder(
itemExtent: 40,
scrollController: controller,
onSelectedItemChanged: (_) => onSelectedItemChanged(),
itemBuilder: (_, index) => Center(
child: FittedBox(
child: Text(data[index], style: const TextStyle(fontSize: 14)),
),
),
childCount: data.length,
),
);
}
}

浙公网安备 33010602011771号