Flutter 自定义数据选择器
flutter_picker,跟着demo敲代码尝试去实现自定义选择器,发现还是不行,做了一版,发现滚动多列的时候,容易导致后面一列的样式错乱,安装 city-picker 插件尝试拉取源码去改动,发现里面匹配就是全国的省市区的code,改动起来有点麻烦……
看到网上有一篇博客(现在没找到地址),基本实现多层级数据的,缺点是:数据必须需要按照层级都固定写死了,扩展性不好,最重要的,没有实现选中数据,点击确定之后,再次回显。
整理思路自己重新实现
针对 下面这种 数据格式,都能实现
[ { "value": "xxx", "label": "xxx", "children": [ { "value": "xxxx", "label": "xxx" ]} ] }, …… ]复制代码
如果需要改动 键的名称,直接拿取改,稍看一下,就能改,核心逻辑都是按照多层级去实现的(当然移动端这种插件不会超过3-4列)
flutter_custom_dialog 用它来做dialog
dependencies: flutter_custom_dialog: ^1.2.0复制代码
Main.dart (关键代码)
import 'package:city_picker_demo/custom_picker.dart'; List<dynamic> paramsIndexs = []; List<dynamic> paramsNames = []; //按钮的点击事件 industriesData 为原始数据 RaisedButton( padding: EdgeInsets.all(0), onPressed: () { customPicker(context,{"indexs":paramsIndexs, "initData": industriesData, "colNum":2}, (opt){ print(opt['indexs']); print(opt['names']); setState(() { paramsIndexs = opt['indexs']; paramsNames = opt['names']; }); }); }, child: Row( children: [ Text('自定义城市选择'), ], ), ),复制代码
custom_picker.dart (完整代码)
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_custom_dialog/flutter_custom_dialog.dart'; /** * params 里面目前支持参数 * indexs 指多个列的数组下标,如果是初始的时候可以传空数组 * colNum 指多少列 * initData 传入的初始数据 */ YYDialog customPicker(BuildContext context, params, Function onConfirm) { return YYDialog().build(context) ..gravity = Gravity.bottom ..gravityAnimationEnable = true ..backgroundColor = Colors.transparent ..widget(ChooseList(params: params, onConfirm: onConfirm,)) ..show(); } class ChooseList extends StatefulWidget { final Function onConfirm; final params; const ChooseList({Key key, this.params, this.onConfirm,}) : super(key: key); @override _ChooseListState createState() => _ChooseListState(); } class _ChooseListState extends State<ChooseList> { List<dynamic> colIndex=[]; //数组下标集合 List<dynamic> colContentList = []; //所有列实时的数据源 final List<FixedExtentScrollController> scrollController = []; @override void initState() { super.initState(); final indexs = widget.params['indexs']; final colNum = widget.params['colNum']; if (scrollController.length == 0) { if(indexs.length == 0) { //没选择数据的时候是这个逻辑 for (int i = 0; i < colNum; i++) { // colNum 表示多少列 scrollController.add(FixedExtentScrollController(initialItem: 0)); } } else { // 选择完数据的时候 for (int i = 0; i < colNum; i++) { scrollController.add(FixedExtentScrollController(initialItem: indexs[i])); } } } initIndexs(indexs, colNum); colContentList = initData(colIndex, colNum, widget.params['initData']); } //初始化下标 void initIndexs(indexs,colNum) { if(indexs.length ==0){ for (int i = 0; i < colNum; i++) { colIndex.add(0); } } else { // 选中之后再次进来 colIndex = indexs; } } /** * industriesData 数据源 * * */ initData(indexs, colNum, initData) { var dataList = []; //最终的各列的数据 var level = 0; for(var i = 0; i < colNum; i++) { dataList.add([]); } recursionDataHandle(indexs, colNum, initData, level,dataList); return dataList; } /** * 递归执行函数 */ void recursionDataHandle(indexs, colNum, initData, level,dataList) { for(var i = 0; i < initData.length; i++) { if(level != colNum) { dataList[level].add({"value": initData[i]['value'], "label": initData[i]['label']}); } else { //已经执行n层 return ; } } //处理下一级的数据 var levelData = initData[indexs[level]]; if(levelData !=null && levelData['children'] != null && levelData['children'].length>0) { //递归 level++; //层级加1 recursionDataHandle(indexs, colNum, initData[indexs[level-1]]['children'], level, dataList); } } @override Widget build(BuildContext context) { return Container( width: double.infinity, height: 280, decoration: BoxDecoration( borderRadius: BorderRadius.vertical( top: Radius.circular(16)), color: Colors.white, ), child: Column( children: [ vGap(10), Padding( padding: EdgeInsets.symmetric(horizontal: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, //横轴居中对齐(默认) children: [ GestureDetector( //手势 onTap: () { Navigator.pop(context); }, child: Text( "取消", style: TextStyle( color: Colors.grey, fontSize: 14), )), Text( "", style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600), ), GestureDetector( onTap: () { Navigator.pop(context); var names = getAllNames(); widget.onConfirm({'indexs':colIndex, "names":names }); }, child: Text("确定", style: TextStyle( color: Colors.blueAccent, fontSize: 14))), ], ), ), vGap(10), Row( children: cuputedScroll() ) ], ), ); } Widget buildCity( {List<dynamic> list, FixedExtentScrollController scroll, int columnNum, Function onSelected}) { return Expanded( flex: 1, child: Container( height: 230, child: list.length != 0 ? CupertinoPicker.builder( scrollController: scroll, itemExtent: 30, diameterRatio: 3, squeeze: 0.8, onSelectedItemChanged: (int _index) { selectdeHandel(_index, columnNum); }, itemBuilder: (context, index) { return Center( child: Text( "${list[index]['label']}", style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600), )); }, childCount: list.length, ) : Container()), ); } // 纵向间距 static SizedBox vGap(double height){ return SizedBox( height: height, ); } // 计算 scroll的列 cuputedScroll () { List<Widget> buildCitys =[]; for(var i =0; i< colContentList.length; i++ ){ buildCitys.add(buildCity(list: colContentList[i], scroll: scrollController[i],columnNum: i)); } return buildCitys; } // 滑动某一列的列的时候 selectdeHandel(int _index, columnNum) { for(var i = columnNum; i< colIndex.length; i++) { setState(() { if(i== columnNum) { colIndex[i] = _index; } else { colIndex[i] = 0; } }); } var tmpData = initData(colIndex, widget.params['colNum'], widget.params['initData']); setState(() { colContentList = tmpData; }); if(columnNum !=colIndex.length-1) { //不是最后一列,滚动前一列,则后面每一列都需要滚动第一个元素位置上 if (scrollController[columnNum+1].hasClients) { scrollController[columnNum+1].jumpTo(0.0); } } } //获取数据的对应的名称 getAllNames() { List<dynamic> names = []; for(var i = 0; i < colContentList.length; i++) { names.add(colContentList[i][colIndex[i]]['label']); } return names; } }复制代码
效果: 1.做到了选中数据再次点击选择器数据回显。 2同时滑动第一列的时候,第二列(后续列都会重新回到第一个元素上),同时实现数据的联动
作者:陈炳
链接:https://juejin.cn/post/7037507200163512351
伪原创工具 SEO网站优化 https://www.237it.com/