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

上一篇文章https://www.jianshu.com/p/29019113e54e
借鉴几篇文章实现下拉框,后面发现维护的时候很痛苦,主要是不容易看懂,尤其是间距,显示的位置,经常会对不齐
,于是想重新写 一篇简单的,通俗易懂,
实现的需求是,头部完全自定义实现,下拉框随意的自定义部件,不需要考虑显示的位置和间距,只要提供部件就行
废话少说先看效果

下拉框.gif

最主要的就是CompositedTransformTarget配合CompositedTransformFollower的使用,他们是通过LayerLink来绑定
第一篇文章的作者是吧头部的每一个item对应一个LayerLink,我觉这做法不好
我的做法是,一个头部不管有几个item,都是一个LayerLink,CompositedTransformFollower起始位置左侧就是0(offset: Offset(0, tabHeight!)),高度自行调节,
当头部onChange,CompositedTransformFollower显示不同的部件即可
这逻辑非常简单吧,至于部件的布局样式,你完全自定义,
有个问题是当你下拉部件选择之后,需要同步给头部显示高亮及箭头上下翻转,这个可以借鉴第一篇的逻辑
最后,代码如下,代码很少,直接拷贝就可以用,当做一个页面打开就可以直接测试效果

import 'package:flutter/material.dart';

class DropMenuWidget extends StatefulWidget {
  @override
  _DropMenuWidgetState createState() => _DropMenuWidgetState();
}

class _DropMenuWidgetState extends State<DropMenuWidget> {
  final LayerLink _layerLink = LayerLink();
  final GlobalKey _appBarKey = GlobalKey();
  final GlobalKey _tabKey = GlobalKey();
  OverlayEntry? _overlayEntry;

  @override
  void initState() {}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        key: _appBarKey,
        title: const Text('下拉框demo'),
      ),
      body: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          CompositedTransformTarget(
            link: _layerLink,
            child: Container(
              key: _tabKey,
              color: Colors.blue,
              padding: EdgeInsets.symmetric(horizontal: 15, vertical: 8),
              child: Row(
                children: [
                  Expanded(
                      child: TextButton(
                    onPressed: () {
                      _showOverlay(context, '销量');
                    },
                    child: Text('销量'),
                  )),
                  Expanded(
                      child: TextButton(
                    onPressed: () {
                      _showOverlay(context, '品质');
                    },
                    child: Text('品质'),
                  )),
                  Expanded(
                      child: TextButton(
                    onPressed: () {
                      _showOverlay(context, '区域');
                    },
                    child: Text('区域'),
                  )),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  void _showOverlay(BuildContext context, String content) {
    _hideOverlay();
    final overlay = Overlay.of(context);
    final screenWidth = MediaQuery.of(context).size.width;
    final screenHeight = MediaQuery.of(context).size.height;
    final appBarHeight = _appBarKey.currentContext?.size!.height;
    final statusBar = MediaQuery.of(context).padding.top;
    final tabHeight = _tabKey.currentContext?.size!.height;
    _overlayEntry = OverlayEntry(
      builder: (context) => Stack(
        children: [
          // 使用Container作为Mask,mask是第一个部件,位于最底部,这个mask的margin高度,其实可以不用算,自己写一个合适的也可以,前提是OverlayEntry距离顶部高度是固定的
          // 如果希望mask覆盖整个屏幕,可以不写margin
          GestureDetector(
            onTap: _hideOverlay,
            child: Container(
              margin:
                  EdgeInsets.only(top: appBarHeight! + statusBar + tabHeight!),
              width: screenWidth,
              height: screenHeight,
              color: Colors.black54, // Semi-transparent mask
            ),
          ),
          Positioned(
            width: screenWidth,
            height: 100, // 这个高度不写的话,就是内容的高度
            child: CompositedTransformFollower(
              link: _layerLink,
              showWhenUnlinked: false,
              offset: Offset(0, tabHeight!),
              child: Material(
                elevation: 4.0,
                child: Container(
                  //  这里完全可以根据传入的index,来显示不同的部件,甚至在参数直接传入显示的部件也可以
                  color: Colors.red,
                  child: Column(
                    children: [
                      Center(
                        child: Text('$content',style: TextStyle(color: Colors.white,fontWeight:
                        FontWeight.bold),),
                      ),
                      TextButton(
                          onPressed: () {
                            _hideOverlay();
                          },
                          child: Text('点我关闭'))
                    ],
                  ),
                ),
              ),
            ),
          )
        ],
      ),
    );
    overlay.insert(_overlayEntry!);
  }

  void _hideOverlay() {
    _overlayEntry?.remove();
    _overlayEntry = null;
  }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容