美文网首页
Flutter OverlayPortal创建自定义下拉菜单(一

Flutter OverlayPortal创建自定义下拉菜单(一

作者: Pino | 来源:发表于2024-05-15 15:27 被阅读0次

一开始看的掘进的这篇 https://article.juejin.cn/post/7227012644506435642
使用的时候,问题有点多,修改自定义下拉布局太过于麻烦
后面看到这篇 https://zhuanlan.zhihu.com/p/660081224 比较通俗易懂
不过有几个bug
1.打开下拉布局的时候箭头没有翻转
2.选择内容后,下拉框没有消失,选择的文本没有显示和高亮
3.点击空白处,下拉框没有收起
4.点击返回键,下拉框没有收起
5.去掉了一些不必要的嵌套部件,去掉不必要的变量和判断
最终代码 如下
使用

Stack(
        children: [
          Container(
            width: double.infinity,
            height: double.infinity,
            decoration: BoxDecoration(
              color: Colors.black.withOpacity(0.5),
            ),
          ),
          Container(
            color: Colors.white,
            // height: 44,
            child: const DropDownMenu(),
          ),
        ],
      )
import 'package:flutter/material.dart';
import 'package:flutter_haiguan_pro/utils/color_util.dart';

enum FilterTypes { category, slots, areas }

class CategoryModel {
  int? id;
  String? name;
  String? icon;
  bool? check;

  CategoryModel({this.id, this.name, this.icon, this.check});

  CategoryModel.fromJson(Map<String, dynamic> json) {
    id = json['ID'];
    name = json['Name'];
    icon = json['Icon'];
    check = json['Check'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = <String, dynamic>{};
    data['ID'] = id;
    data['Name'] = name;
    data['Icon'] = icon;
    data['Check'] = check;
    return data;
  }
}

class DropDownMenuModel {
  LayerLink layerLink;
  String name;
  FilterTypes type;
  List<CategoryModel> list = [];

  String get selectText {
    if (list == null || list.isEmpty) return "";
    try {
      return list.firstWhere((element) => element.id != 0 && element.check == true).name!;
    } catch (e) {}
    return "";
  }

  DropDownMenuModel({
    required this.name,
    required this.layerLink,
    required this.type,
    required this.list,
  });
}

class DropDownMenu extends StatefulWidget {
  const DropDownMenu({super.key});

  @override
  State<DropDownMenu> createState() => _DropDownMenuState();
}

class _DropDownMenuState extends State<DropDownMenu> with SingleTickerProviderStateMixin {
  int _curFilterIndex = -1;
  OverlayEntry? _overlayEntry;
  final GlobalKey _buttonRowKey = GlobalKey();
  final List<DropDownMenuModel> _filterList = [
    DropDownMenuModel(name: '类别', type: FilterTypes.category, list: [], layerLink: LayerLink()),
    DropDownMenuModel(name: '排序', type: FilterTypes.slots, list: [], layerLink: LayerLink()),
    DropDownMenuModel(name: '区域', type: FilterTypes.areas, list: [], layerLink: LayerLink()),
  ];
  late AnimationController _animationController;
  late Animation<double> _animation;
  Color _maskColor = Colors.black26;

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

    _animationController = AnimationController(
      duration: const Duration(milliseconds: 300), // 设置动画持续时间
      vsync: this,
    );
    _animation = Tween<double>(begin: 0, end: 1).animate(_animationController); // 定义动画的值范围
  }

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

  init() async {
    // 模拟接口获取数据
    getCateList();
    getSlotList();
  }

  void getCateList() {
    setState(() {
      _filterList[0].list = [
        CategoryModel(id: 0, name: '不限', check: false),
        CategoryModel(id: 1, name: '火锅', check: false),
        CategoryModel(id: 2, name: '自助餐', check: false),
        CategoryModel(id: 3, name: '西餐', check: false),
        CategoryModel(id: 4, name: '烤肉', check: false),
        CategoryModel(id: 5, name: '甜品', check: false),
        CategoryModel(id: 6, name: '饮品', check: false),
        CategoryModel(id: 7, name: '蛋糕', check: false),
      ];
    });
  }

  void getSlotList() {
    setState(() {
      _filterList[1].list = [
        CategoryModel(id: 0, name: '不限', check: false),
        CategoryModel(id: 1, name: '离我最近', check: false),
        CategoryModel(id: 2, name: '综合排序', check: false),
        CategoryModel(id: 3, name: '价格排序', check: false),
      ];
    });
  }

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
        onWillPop: () async {
          hideOverlay();
          return true;
        },
        child: Row(
          key: _buttonRowKey,
          children: List.generate(_filterList.length, (index) {
            DropDownMenuModel item=_filterList[index];
            bool isSelect = item.selectText.isNotEmpty;
            return Expanded(
              child: CompositedTransformTarget(
                link: item.layerLink,
                child: GestureDetector(
                  onTap: () => onTabClick(index),
                  child: SizedBox(
                    height: 30,
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text(
                          isSelect ? item.selectText : item.name!,
                          style: TextStyle(fontSize: 14, color: isSelect ? Colors.red : Colors.black87),
                        ),
                        Icon(
                          _curFilterIndex == index
                              ? Icons.keyboard_arrow_up_rounded
                              : Icons.keyboard_arrow_down_rounded,
                          size: 20,
                          color: isSelect ? Colors.red : Colors.black26,
                        ),
                      ],
                    ),
                  ),
                ),
              ),
            );
          }),
        ));
  }

  onTabClick(int index){
    // 点击一样的就关闭
    if (_curFilterIndex == index) {
      hideOverlay();
      return;
    }
    setState(() {
      _curFilterIndex = index;
      _animationController.forward();
      _maskColor = Colors.black26;
    });
    changeOverlay(index: index, reset: true);
  }

  void changeOverlay({required int index, bool reset = false}) {
    // TAG: 更新OverlayEntry数据需要清空之后重新构建
    if (reset && _overlayEntry != null) {
      _overlayEntry!.remove();
      _overlayEntry = null;
    }

    RenderBox? renderBox;
    if (_buttonRowKey.currentContext != null) {
      renderBox = _buttonRowKey.currentContext?.findRenderObject() as RenderBox;
    }

    double left = -(renderBox!.size.width / _filterList.length) * index;
    _overlayEntry = OverlayEntry(
      builder: (context) {
        return CompositedTransformFollower(
          link: _filterList[index].layerLink,
          offset: Offset(left, renderBox!.size.height),
          child: Stack(
            children: [
              AnimatedBuilder(
                animation: _animationController,
                builder: (context, child) {
                  return FadeTransition(
                    opacity: _animation,
                    child: InkWell(
                      onTap: hideOverlay,
                      child: Container(color: _maskColor),
                    ),
                  );
                },
              ),
              MediaQuery.removePadding(
                context: context,
                removeTop: true,
                removeBottom: true,
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    AnimatedBuilder(
                      animation: _animationController,
                      builder: (context, child) {
                        return SizeTransition(
                          sizeFactor: _animation,
                          child: SlideTransition(
                            position: Tween<Offset>(
                              begin: const Offset(0, -1), // 从顶部开始
                              end: Offset.zero,
                            ).animate(_animation),
                            child: Container(
                              decoration: const BoxDecoration(
                                color: ColorUtil.primaryBg,
                                borderRadius: BorderRadius.only(
                                  bottomLeft: Radius.circular(8),
                                  bottomRight: Radius.circular(8),
                                ),
                              ),
                              child: ListView(
                                shrinkWrap: true,
                                children: _filterList[index].list.asMap().entries.map((e) {
                                  int itemIndex = e.key;
                                  CategoryModel item = e.value;
                                  return _menuItem(cate: item, index: itemIndex, rootIndex: index);
                                }).toList(),
                              ),
                            ),
                          ),
                        );
                      },
                    ),
                  ],
                ),
              ),
            ],
          ),
        );
      },
    );

    Overlay.of(context).insert(_overlayEntry!);
  }

  Widget _menuItem({required int index, required int rootIndex, required CategoryModel cate}) {
    return GestureDetector(
      onTap: () {
        for (CategoryModel item in _filterList[rootIndex].list) {
          item.check = false;
        }
        _filterList[rootIndex].list[index].check = true;
        changeOverlay(index: rootIndex, reset: true);
        hideOverlay();
      },
      child: Container(
        color: Colors.white,
        padding: const EdgeInsets.only(left: 16, right: 16, top: 8, bottom: 8),
        height: 40,
        alignment: Alignment.topLeft,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text('${cate.name}',
                style: TextStyle(
                  fontSize: 14,
                  color: _filterList[rootIndex].list[index].check == true ? ColorUtil.primary : Colors.black87,
                )),
            if (_filterList[rootIndex].list[index].check == true) Icon(Icons.check, size: 16, color: ColorUtil.primary),
          ],
        ),
      ),
    );
  }

  hideOverlay() {
    _overlayEntry?.remove();
    _overlayEntry = null;
    setState(() {
      _curFilterIndex = -1;
      _animationController.reverse();
      _maskColor = Colors.transparent;
    });
    print("_curFilterIndex: ${_curFilterIndex}");
  }
}

相关文章

网友评论

      本文标题:Flutter OverlayPortal创建自定义下拉菜单(一

      本文链接:https://www.haomeiwen.com/subject/qlnvfjtx.html