flutter手写cascader组件,级联选择器,动态加载

手写Cascader组件,动态加载


参考资料:
链接
参考了vant的cascader组件,没有写完全

效果图
在这里插入图片描述

Example 实例

// 级联选择
import 'package:flutter/material.dart';

const String _titleText = '请选择地区';
const String _tabText = '请选择';
const double _headerHeight = 40.0;
const double _itemHeight = 50.0;
const double _titleFontSize = 14.0;
const String _customField = 'text';

class Cascader extends StatefulWidget {
  const Cascader({
    super.key,
    this.title = _titleText,
    this.tabText = _tabText,
    this.clickCallBack,
    required this.asyncCallBack,
    required this.options,
    required this.onClose,
    this.customField = _customField,
  });

  final String title;
  final String? tabText;
  final Function(dynamic selectValue)? clickCallBack;
  // 多维数组
  final List options;

  // 异步加载函数
  final Function(dynamic, dynamic, dynamic) asyncCallBack;

  // 关闭
  final Function onClose;

  // 自定义显示字段
  final String? customField;

  
  State<Cascader> createState() => _CascaderState();
}

class _CascaderState extends State<Cascader> with TickerProviderStateMixin {
  TabController? _tabController;

  // TabBar 数组
  final List<Tab> _myTabs = <Tab>[];

  // 当前列表数据
  final List<List> _mList = [];

  // 多级联动选择的position
  final List<int> _positions = [];
  // 控制器组
  final List<ScrollController> _scrollList = [];

  // 索引
  final List<int> _itemIndex = [];

  int _index = 0;

  // 数据合集
  final List dataCollection = [];

  
  void initState() {
    super.initState();
    _initData();
  }

  _initScrollList(int index) {
    var scrollController = ScrollController();
    return scrollController;
  }

  void _initData() {
    _myTabs.add(Tab(text: widget.tabText));
    _mList.add(widget.options);
    _itemIndex.add(0);
    _positions.add(0);
    _scrollList.add(_initScrollList(0));
    _tabController = TabController(length: _myTabs.length, vsync: this);
    _tabController!.addListener(() {
      // print(_tabController!.index);
      setState(() {
        _setIndex(_tabController!.index);
      });
    });
    // _tabController.addListener(() {
    //   print(_tabController.index);
    // });
  }

  void _setIndex(int index) {
    _index = index;
  }

  void _indexIncrement() {
    _index++;
  }

  
  Widget build(BuildContext context) {
    return Container(
        color: Colors.white,
        child: Column(
          children: [
            Container(
              height: _headerHeight,
              color: Colors.white,
              alignment: Alignment.center,
              child: Text(widget.title,
                  style: const TextStyle(
                      fontSize: _titleFontSize,
                      color: Color(0xFF787878),
                      decoration: TextDecoration.none)),
            ),
            const Divider(
              indent: 0.0,
              color: Color.fromARGB(255, 190, 190, 190),
            ),
            Expanded(
              child: Column(children: [
                Expanded(
                  child: Scaffold(
                    appBar: TabBar(
                      tabs: _myTabs,
                      controller: _tabController,
                      isScrollable: true,
                      indicatorSize: TabBarIndicatorSize.label,
                      labelColor: const Color(0xFF409DFB),
                      unselectedLabelColor: const Color(0xFF333333),
                      indicatorColor: const Color(0xFF409DFB),
                      onTap: (index) {
                        if (index == _index) return;
                        setState(() {
                          _setIndex(index);
                        });
                        _tabController?.animateTo(_index);
                        int scrollTop = _positions[_index];
                        Future.delayed(const Duration(milliseconds: 100))
                            .then((e) {
                          _scrollList[_index].animateTo(
                            scrollTop.toDouble(),
                            duration: const Duration(milliseconds: 1),
                            curve: Curves.ease,
                          );
                        });
                      },
                    ),
                    body: _tabBarView(),
                  ),
                ),
              ]),
            )
          ],
        ));
  }

  TabBarView _tabBarView() {
    return TabBarView(controller: _tabController, children: _tabViews());
  }

  List<Widget> _tabViews() {
    List<Widget> tabViewList = [];
    for (var i = 0; i < _myTabs.length; i++) {
      final List contents = _mList[i];
      tabViewList.add(
        ListView.builder(
          shrinkWrap: true,
          controller: _scrollList[i],
          itemExtent: _itemHeight,
          scrollDirection: Axis.vertical,
          itemBuilder: (_, index) {
            return _buildItem(contents[index], index, i);
          },
          itemCount: contents.length,
        ),
      );
    }
    return tabViewList;
  }

  Widget _buildItem(data, int itemIndex, int tabIndex) {
    return Container(
        height: 50,
        decoration: BoxDecoration(
            color: getBgColor(tabIndex, itemIndex),
            border: const Border(
                bottom: BorderSide(width: 1, color: Color(0xffe5e5e5)))),
        child: ListTile(
          title: Text(data[widget.customField]),
          trailing: getIcon(tabIndex, itemIndex),
          textColor: getColor(tabIndex, itemIndex),
          onTap: () async {
            // 获取新数据
            List datas = await widget.asyncCallBack(
                _mList[tabIndex][itemIndex], tabIndex, itemIndex);

            // print(_positions);
            _positions[tabIndex] = (itemIndex * _itemHeight).toInt();
            setIndex(tabIndex, itemIndex);
            if (datas.isEmpty) {
              _onClose();
              return;
            }
            _setListAndChangeTab(datas, tabIndex);
            _indexIncrement();
            setState(() {});
            _myTabs[tabIndex] = Tab(text: data[widget.customField]);
            _tabController?.animateTo(_index);
            // Future.delayed(const Duration(milliseconds: 200)).then((e) {
            //   _scrollList[tabIndex].animateTo(0.0,
            //       duration: const Duration(milliseconds: 1),
            //       curve: Curves.ease);
            // });
          },
        ));
  }

  /// 选项点击后设置下一级数据并改变tabBar
  void _setListAndChangeTab(datas, tabIndex) {
    // print(tabIndex);

    if ((_index + 1) < _myTabs.length) {
      _mList[_index + 1] = datas;
      _itemIndex[_index + 1] = 0;
      _positions[_index + 1] = 0;
      _myTabs[_index + 1] = const Tab(text: '请选择');
      // print('tabIndex $tabIndex  _myTabs ${_myTabs.length}');
      if ((_index + 2) == _myTabs.length) return;
      var deleteIndex = _index + 2;
      // if (_index == 0) {
      //   deleteIndex += 3;
      // } else {
      //   deleteIndex += 2;
      // }
      _itemIndex.removeRange(deleteIndex, _itemIndex.length);
      _positions.removeRange(deleteIndex, _positions.length);
      _positions.removeRange(deleteIndex, _positions.length);
      _mList.removeRange(deleteIndex, _mList.length);
      _scrollList.removeRange(deleteIndex, _scrollList.length);
      _tabController = TabController(
          length: _myTabs.length, vsync: this, initialIndex: _index);
      _tabController!.addListener(() {
        setState(() {
          _setIndex(_tabController!.index);
        });
      });
    } else {
      _mList.add(datas);
      _myTabs.add(const Tab(text: '请选择'));
      _itemIndex.add(0);
      _positions.add(0);
      _scrollList.add(_initScrollList(_positions.length - 1));
      _tabController = TabController(
          length: _myTabs.length, vsync: this, initialIndex: _index);
      _tabController!.addListener(() {
        setState(() {
          _setIndex(_tabController!.index);
        });
      });
    }
  }

  _onClose() {
    List data = [];
    for (var i = 0; i < _mList.length; i++) {
      var index = _itemIndex[i];
      data.add(_mList[i][index]);
    }
    widget.onClose(data);
  }

  setIndex(tabIndex, itemIndex) {
    if (_itemIndex.isEmpty) {
      _itemIndex.add(itemIndex);
    }
    if (_itemIndex.length >= tabIndex) {
      _itemIndex[tabIndex] = itemIndex;
    } else {
      _itemIndex.add(itemIndex);
    }
  }

  getBgColor(tabIndex, itemIndex) {
    if (_itemIndex.isEmpty) {
      return const Color.fromARGB(255, 247, 247, 247);
    }
    var color;
    if (_itemIndex.length >= tabIndex) {
      color = _itemIndex[tabIndex] == itemIndex
          ? const Color.fromARGB(255, 235, 235, 235)
          : const Color.fromARGB(255, 247, 247, 247);
    } else {
      color = const Color.fromARGB(255, 247, 247, 247);
    }
    return color;
  }

  getColor(tabIndex, itemIndex) {
    if (_itemIndex.isEmpty) {
      return const Color.fromARGB(255, 0, 0, 0);
    }
    var color = _itemIndex[tabIndex] == itemIndex
        ? const Color(0xFF409DFB)
        : const Color.fromARGB(255, 0, 0, 0);
    return color;
  }

  getIcon(tabIndex, itemIndex) {
    double size = 12;
    if (_itemIndex.isEmpty) {
      size = 0;
    } else {
      size = _itemIndex[tabIndex] == itemIndex ? 24 : 0;
    }
    return Icon(
      Icons.check,
      color: getColor(tabIndex, itemIndex),
      size: size,
    );
  }
}


使用方法

这里是采用showCupertinoModalPopup 弹窗显示,理论上可以在任意地方显示

showCupertinoModalPopup(
                    context: context,
                    builder: (BuildContext context) {
                      return Container(
                        height: 400,
                        width: MediaQuery.of(context).size.width,
                        decoration: BoxDecoration(
                            borderRadius: const BorderRadius.only(
                                topLeft: Radius.circular(10),
                                topRight: Radius.circular(10)),
                            color: Colors.grey[200]),
                        child: Cascader(
                          title: '请选择位置',
                          customField: 'name',
                          asyncCallBack: (item, tabIndex, itemIndex) async {
                            if (item['type'] == 'unit') {
                              var data = await _getBuilding(item['unitId']);
                              return data;
                            }

                            if (item['type'] == 'build') {
                              var data = await _getBuildingFloors(item['id']);
                              return data;
                            }
                            if (item['type'] == 'floor') {
                              var data = await _getuildingRooms(
                                  item['buildingId'], item['id']);
                              return data;
                            }
                            return [];
                          },
                          onClose: (todo) {
                            print('object  $todo');
                            Navigator.pop(context);
                          },
                          options: data,
                        ),
                      );
                    });

options 是初始的第一列数据
asyncCallBack 会点击获取下一列的新数据
customField 自定义显示字段,默认是 text
当asyncCallBack返回空数组是会触发,返回选择的数据

总结

已完成部分功能,可以正常使用
数据回显还没有思路,探索中(我只是个新手)
如有想法可告知,完善中!

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

如一一

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值