Flutter实现省市区级联组件完整代码

在Flutter中实现轮子效果的省市区级联组件,当然也可以是任意的选择器,该Widget已经加入到ym_flutter_widget 0.0.2中,可以直接引入,你也可以拷贝下面的源码实现。

最终效果如图:


首先看下该组件的使用方法:

///
/// Cascader级联选择器
///
class CascaderPage extends StatefulWidget {

  CascaderPage({Key? key}) : super(key: key);

  final String title = "Cascader";

  @override
  _CascaderPageState createState() => _CascaderPageState();
}

class _CascaderPageState extends State {

  //显示底部弹窗
  bool _showPickerDialog = false;

  //计算拼接地址字符串的
  List _pickerSelected = [0,0,0];
  String  _address3 = "请选择";

  //{"label": "请选择", "value": ""},
  List> _regionData = [[],[],[]];    //数据
  List _regionValue = ["0","0","0"];   //默认值

  @override
  void initState() {
    super.initState();
    //加载数据
    _loadData(0,0);
  }

  ///返回
  Future _goBack() async{Navigator.pop(context);
  }

  ///请求数据
  Future _loadData(int pid,int index) async {

    List listNew = [
    ];
    for (var i = 0; i < 20; i++) {
      listNew.add({"label": pid.toString() + "-" + i.toString(), "value": i.toString()});
      //判断默认值
      if(i.toString() == _regionValue[index]){
        _pickerSelected[index] = i;
      }
    }
    if(index == 0){
      setState(() {
        _regionData[0] = listNew;
      });

      //接着查询市
      _loadData(int.parse(_regionValue[0]),1);

    }else if(index == 1){
      setState(() {
        _regionData[1] = listNew;
      });
      //接着查询区
      _loadData(int.parse(_regionValue[1]),2);

    }else if(index == 2){
      setState(() {
        _regionData[2] = listNew;
      });
    }
  }

  Future _changeData(int pid,int index) async {
    List listNew = [
    ];

    for (var i = 0; i < 20; i++) {
      listNew.add({"label": pid.toString() + "-" + i.toString(), "value": i.toString()});
    }

    if(index == 1){
      print("update list2...............");
      setState(() {
        _regionData[1] = listNew;
      });

      //父节点变化后,选中第一个
      _regionValue[1] = _regionData[1][0]['value'];
      //接着查询区
      _loadData(pid,2);

    }else if(index == 2){
      print("update list3...............");
      setState(() {
        _regionData[2] = listNew;
      });

      //父节点变化后,选中第一个
      _regionValue[2] = _regionData[2][0]['value'];

    }
  }

  @override
  Widget build(BuildContext context) {

    return MaterialApp(
      home: Scaffold(
        body: Stack(
          fit: StackFit.expand,
          children:[

            ///导航栏
            Positioned(
              top: 0,
              child: Container(
                width: MediaQuery.of(context).size.width,
                child: YmAppBar(widget.title,[
                  const Color(0xFF606FFF),
                  const Color(0xFF3446F2),
                ],_goBack,titleTextColor: Colors.white,),
              ),
            ),

            Positioned(
              top: 80,
              left:0,
              width:  MediaQuery.of(context).size.width,
              child:Padding(
                padding: EdgeInsets.only(top: 10, left: 16, right: 16, bottom: 16),
                child:Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: [
                    Container(
                      width: 100,
                      child: Padding(
                          padding: EdgeInsets.only(top:0,left: 16,bottom: 0),
                          child:Text(
                            "级联组件",
                            textAlign:TextAlign.left,
                            style: const TextStyle(
                              color: Colors.black,
                              fontSize: 16,
                            ),
                          )
                      ),
                    ),
                    Expanded(
                        flex: 1,
                        child:GestureDetector(
                          child: Padding(
                              padding: EdgeInsets.only(top:0,left:16,right: 16,bottom: 0),
                              child:Text(
                                _address3,
                                textAlign:TextAlign.left,
                                style: const TextStyle(
                                  color: Colors.black87,
                                  fontSize: 16,
                                ),
                              )
                          ),
                          onTap:_openPickerDialog ,
                        )
                    )
                  ],
                ),
              ),
            ),

            _showPickerDialog?
            Positioned(
              bottom: 0,
              left: 0,
              width: MediaQuery.of(context).size.width,
              height: 220,
              child: _pickerDialog(),
            ):Container()

          ],
        ),
      ),
    );
  }


  Widget _pickerDialog() {
    return YmCascader(
      _regionData,_regionValue,
      onOkClick:(){
        closePickerDialog();
        setState(() {
          _address3 = _regionData[0].elementAt(_pickerSelected[0])['label'] + "/" + _regionData[1].elementAt(_pickerSelected[1])['label'] + "/" + _regionData[2].elementAt(_pickerSelected[2])['label'];
        });
      },
      onCancelClick:(){
        closePickerDialog();

      },
      onChanged: (position,index,value){
        if(position == 0){
          _pickerSelected[0] = index;
          _pickerSelected[1] = 0;
          _pickerSelected[2] = 0;
          _regionValue[0] = value;
          _changeData(int.parse(value),1);
        }else if(position == 1){
          _pickerSelected[1] = index;
          _pickerSelected[2] = 0;
          _regionValue[1] = value;
          _changeData(int.parse(value),2);
        }else if(position == 2){
          _pickerSelected[2] = index;
          _regionValue[2] = value;
        }
      },
    );

  }

  // 对话框式底部滑动窗口
  Future _openPickerDialog() async {
    setState(() {
      _showPickerDialog = true;
    });
  }

  void closePickerDialog(){
    setState(() {
      _showPickerDialog = false;
    });
  }

}

下面是封装好的级联组件代码:

///
/// Cascader级联选择器
///

class YmCascader extends StatefulWidget {

  //数据
  late List> data;

  //默认Value
  late List value;

  final double width;
  final double height;
  final Function onChanged;
  final Function onOkClick;
  final Function onCancelClick;
  final Color backgroundColor;
  final double itemHeight;

  YmCascader(
      this.data,
      this.value,
      {
        Key? key,
        required this.onChanged,
        required this.onOkClick,
        required this.onCancelClick,
        this.itemHeight = 36,
        this.backgroundColor = const Color(0xffffffff),
        this.height = 150.0,
        this.width = 150.0
      }
  ): super(key: key);

  @override
  _YmCascaderState createState() => _YmCascaderState();

}

class _YmCascaderState extends State {

  @override
  void initState() {
    super.initState();
  }

  Widget _getPickerWidget(int position){
    double pickerItemWidth = (MediaQuery.of(context).size.width)/widget.data.length;
    return YmPicker(
      widget.data.elementAt(position),
      widget.value[position],
      width: pickerItemWidth,
      itemHeight: 40,
      height: 180,
      onChanged: (index,val) {
        print("Picker onChanged  position=" + position.toString() + ",index=" + index.toString() + ",val=" + val);
        widget.onChanged(position,index,val);
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      height:220,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children:[
          Divider(
            height: 1,
            color: Color(0xffEFEFEF),
            thickness: 1,
          ),
          Container(
            height:38,
            child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  YmTextButton("取消", Color(0xFF666666), (){
                    widget.onCancelClick();
                  },isOutlined: true,size: Size(60,30),borderColor: Color(0x00666666),),

                  YmTextButton("确定", Color(0xFF3446F2), (){
                    widget.onOkClick();
                  },isOutlined: true,size:Size(60,30),borderColor: Color(0x00666666),),
                ]
            ),
          ),

          Row(
            children:List.generate(widget.data.length, (index) {
              return _getPickerWidget(index);
            }),
          )
        ],
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
  }

}
///
/// 单个选择器picker
///
class YmPicker extends StatefulWidget {

  //默认值
  late String value;

  //数据格式:{"label": "请选择", "value": ""}
  late List data;

  //前方文字
  final String label;
  final double width;
  final double height;
  final Function onChanged;

  final double itemHeight;
  final Color backgroundColor;


  YmPicker(
      this.data,
      this.value,
      {
        Key? key,
        this.label = "",
        this.itemHeight = 36,
        this.backgroundColor = const Color(0xffffffff),
        this.height = 150,
        this.width = 150,
        required this.onChanged,
      }
  ): super(key: key);

  @override
  _YmPickerState createState() => _YmPickerState();

}

class _YmPickerState extends State {

  int _selectedIndex = 0;
  //设置防抖周期为300毫秒
  Duration durationTime = const Duration(milliseconds: 300);
  Timer timer = Timer(Duration(milliseconds: 300), () {});

  late FixedExtentScrollController controller;

  @override
  void initState() {
    super.initState();
    getDefaultValue();
    controller = FixedExtentScrollController(initialItem: _selectedIndex);
  }

  // 获取默认选择值
  getDefaultValue() {
    for (var i = 0; i < widget.data.length; i++) {
      if (widget.data[i]["value"] == widget.value) {
        setState(() {
          _selectedIndex = i;
        });
        break;
      }
    }
  }

  // 触发值改变
  void _valueChanged(index) {
    timer.cancel();
    timer = new Timer(durationTime, () {
      // 触发回调函数
      print("values长度:" + widget.data.length.toString() + "-----" + index.toString());
      widget.onChanged(index,widget.data[index]["value"]);
    });
  }

  Widget _getItemWidget(int index,List items){
    return  Container(
        alignment: Alignment.center,
        height: widget.itemHeight,
        child: Text(
          items.elementAt(index)["label"],
          style: TextStyle(
            color: Color(0xff333333),
            fontSize: 15,
            height: 1.2,
            fontWeight: FontWeight.w400,
          ),
        ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: widget.height,
      width: widget.width,
      child: CupertinoPageScaffold(

        child: Container(
          height: widget.height,
          color: Colors.white,
          child: Stack(
              alignment: Alignment.center,
              children: [
                widget.label != "" ? Positioned(
                  top: widget.height / 2 - (widget.itemHeight / 2),
                  left: 18.0,
                  child: Container(
                    alignment: Alignment.center,
                    height: widget.itemHeight,
                    child: Text(
                      widget.label,
                      style: TextStyle(
                        color: Color(0xff333333),
                        fontSize: 15,
                        height: 1.2,
                        fontWeight: FontWeight.w500,
                      ),
                    ),
                  ),
                ) : Offstage(offstage: true,),

                CupertinoPicker.builder(
                    magnification: 1.0,                   // 整体放大率
                    scrollController:controller,
                    itemExtent: widget.itemHeight,        // 所有子节点统一高度
                    useMagnifier: true,                   // 是否使用放大效果
                    backgroundColor: Colors.transparent,
                    childCount: widget.data.length,
                    onSelectedItemChanged: (int index) {
                      // 当选项改变时的回调
                      if (mounted) {
                        print('onSelectedItemChanged:$index');
                        _valueChanged(index);
                      }
                    },
                    itemBuilder: (BuildContext context, int index) {
                      return _getItemWidget(index,widget.data);
                    },
                ),
              ],
          ),
        ),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    timer.cancel();
  }
}


本站内容来源于作者发布和网络转载,如有版权相关问题请及时与我们取得联系,我们将立即删除。

 关于作者
 热门教程
通过Git下载和提交代码的命令记录
安装好git后: 1、配置邮箱 git config --global user.name ymbok git con
2023-08-15
剑道仙尊
59
通过ADB在Android系统中快捷截屏和录屏的方法
连上ADB线后: 截图 创建一个BAT文件,用于截图,直接双击运行即可将图片保存到D:\screenshot目录,提前
2023-08-15
剑道仙尊
105
Android12 源码下载与编译
下载Android12 源码 sudo apt-get update 安装curl sudo apt install
2023-04-25
剑道仙尊
125
android生成签名文件jks并获取SHA1
打开Android Studio终端,输入: keytool -genkey -alias app -keyalg R
2022-07-13
剑道仙尊
93
Flutter开发APP更改状态栏文字颜色
void main(){ runApp(MyApp()); /// 状态栏文字黑色 SystemChrome
2022-06-09
剑道仙尊
132
Android Swicth按钮样式自定义
&lt;Switch android:id="@+id/switch_btn" android:layout_wi
2022-06-07
剑道仙尊
133
Pagging3写起来太麻烦,简单封装及其简单
Pagging3分页写起来很麻烦,这里分享一下我的简化开发的方法,思路就是把获取数据的函数分离出来 首先定义一个基础的
2022-06-07
剑道仙尊
187
在项目中使用Hilt Retrofit使用总结
直接开始,首先我们看看怎么使用Hilt编写 Retrofit 接口请求类 用@Provides注解定义可注入的实例的提
2022-06-07
剑道仙尊
197
Android Jetpack Paging 3 下拉刷新和加载更多代码示例
使用Paging3实现列表的下拉刷新和加载更多 首先定义列表布局文件 &lt;androidx.swiperefres
2022-06-07
剑道仙尊
434
StatefulBuilder实现Dialog的刷新
在Flutter中使用Dialog时,因为 showDialog返回的context与当前页面的 context不是同
2022-06-07
剑道仙尊
136