Flutter Widget之Overlay

Flutter Widget之Overlay,第1张

Overlay + OverlayEntry Overlay

Overlay是一个类似Stack的Widget,可以将OverlayEntry插入到Overlay中,将OverlayEntry中构建的小部件叠加悬浮在其他顶部小部件之上。可以实现类似悬浮小d窗的效果,如Toast,安卓的PopupWindow.
另外Overlay本身使用的是Stack布局,因此OverlayEntry可以使用Position或者AnimatedPosition定位自己的位置。
最常见的用例是:有WidgetsApp或MaterialApp中的Navigator创建的Overlay.

  const Overlay({
    Key? key,
    this.initialEntries = const <OverlayEntry>[],//包含在叠加层中的Widget
    this.clipBehavior = Clip.hardEdge,
  })

通常我们可以直接使用Overlay.of(context)方法获取该context最近的OverlayState,然后通过OverlayState中的insert()或者insertAll()方法插入一个或多个悬浮的Widget。如果想移除悬浮Widget,可以通过OverlayEntry的remove()方法实现。

OverlayEntry

[Overlay] 中可以包含小部件的位置。

  OverlayEntry({
    required this.builder,//构建在Overlay中显示的Widget
    bool opaque = false,//builger中构建的widget是否遮挡整个Overlay,如果opaque=true,Overlay将不在构建该widget下面的widget,除非maintainState=true
    bool maintainState = false,//该widget是否必须显示在树种。即使opaque=true
  })
Overlay的使用

该示例实现如下效果:
点击“show icon”按钮,在右上角图标下显示一个悬浮提示。
![在这里插入图片描述](http://www.kaotop.com/file/tupian/20220505/579872c28cdd4975b6e6df2a02615f56.png#pic_center

![在这里插入图片描述](http://www.kaotop.com/file/tupian/20220505/d1acf50917d143a7b2448d6044a10fee.png#pic_center

class _SimpleOverlayPageState extends State<SimpleOverlayPage> {
  final GlobalKey _noticeKey = GlobalKey();
  OverlayEntry? _overlayEntry;
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Overlay+OverlayEntry"),
        actions: [
          Padding(
            key: _noticeKey,
            padding: const EdgeInsets.all(16),
            child: const Icon(Icons.notifications),
          )
        ],
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            const Text("该示例在AppBar右上角的图标出显示一个提示信息"),
            TextButton(
              onPressed: () {
                showOverlay(context);
              },
              child: const Text(
                "show icon",
                style: TextStyle(
                  fontSize: 30,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

///显示浮层
  void showOverlay(BuildContext context) async {
    if (_overlayEntry != null) {
      _overlayEntry?.remove();
      _overlayEntry = null;
      return;
    }
    //1、获取OverlayState
    OverlayState? overlayState = Overlay.of(context);
    //2、创建OverlayEntry
    _overlayEntry = OverlayEntry(
      builder: (ctx) {
        //获取_noticeKey Widget的位置,用于设置OverlayEntry将悬浮的位置
        RenderBox? renderBox =
            _noticeKey.currentContext?.findRenderObject() as RenderBox?;
        Size size = renderBox?.size ?? Size.zero;
        Offset offset = renderBox?.localToGlobal(Offset.zero) ?? Offset.zero;
        final double _top = offset.dy + size.height;
        double radius = 20.0;
        double right = offset.dx + size.width / 2 - radius;
        return Positioned(
          top: _top,
          left: right,
          child: CircleAvatar(
            radius: radius,
            backgroundColor: Colors.orange,
            child: const Text("20"),
          ),
        );
      },
      opaque: false,
    );

    //3、将OverlayEntry插入到Overlay中。
    overlayState?.insert(_overlayEntry!);
  }
}

源码地址:

Overlay + OverlayEntry 演示用例2
实现悬浮窗,类似安卓中的PopupWindow

class _PopupDialogPageState extends State<PopupDialogPage> {
  final GlobalKey nameKey = GlobalKey();
  final TextEditingController _controller = TextEditingController();
  final FocusNode _focusNode = FocusNode();
  OverlayEntry? _overlayEntry;
  final LayerLink _layerLink = LayerLink();

  @override
  void initState() {
    super.initState();
    _focusNode.addListener(() {
      if (_focusNode.hasFocus) {
        showOverlay(context);
      } else {
        dismissOverlay();
      }
    });
  }

  @override
  void dispose() {
    // _focusNode.removeListener(() { })
    super.dispose();
    _controller.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Overlay"),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Container(
              height: 60,
              color: Colors.orange,
            ),
            Container(
              height: 40,
              color: Colors.yellow,
              child: const Text("在输入框下显示可以输入的用户名"),
            ),
            Container(
              margin: const EdgeInsets.symmetric(horizontal: 30),
              child: CompositedTransformTarget(
                link: _layerLink,
                child: TextField(
                  key: nameKey,
                  focusNode: _focusNode,
                  controller: _controller,
                  decoration: InputDecoration(
                    label: const Text("输入用户名"),
                    border: OutlineInputBorder(
                      borderRadius: BorderRadius.circular(12),
                    ),
                  ),
                ),
              ),
            ),
            Container(
              height: 100,
              color: Colors.green,
            ),
          ],
        ),
      ),
    );
  }

  void showOverlay(BuildContext context) {
    final overlay = Overlay.of(context);
    _overlayEntry = OverlayEntry(builder: (ctx) {
      final renderBox =
          nameKey?.currentContext?.findRenderObject() as RenderBox;
      final size = renderBox.size;
      final offset = renderBox.localToGlobal(Offset.zero);

      ///如果使用了CompositedTransformFollower,就不在在对Positioned 设置left,top
      return Positioned(
        // left: offset.dx,
        // top: offset.dy + size.height,
        width: size.width,
        child: CompositedTransformFollower(
          link: _layerLink,
          showWhenUnlinked: false,
          offset: Offset(0, size.height + 8),
          child: Material(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                ListTile(
                  title: Text("ZhangSan"),
                  subtitle: Text("CFO"),
                  onTap: () {
                    _controller.text = "ZhangSan";
                    dismissOverlay();
                    _focusNode.unfocus();
                  },
                ),
                ListTile(
                  title: Text("HuShuo"),
                  subtitle: Text("CEO"),
                  onTap: () {
                    _controller.text = "HuShuo";
                    dismissOverlay();
                    _focusNode.unfocus();
                  },
                ),
                ListTile(
                  title: Text("LiMing"),
                  subtitle: Text("Common"),
                  onTap: () {
                    _controller.text = "LiMing";
                    dismissOverlay();
                    _focusNode.unfocus();
                  },
                ),
              ],
            ),
          ),
        ),
      );
    });
    overlay?.insert(_overlayEntry!);
  }

  void dismissOverlay() {
    if (_overlayEntry != null) {
      _overlayEntry?.remove();
      _overlayEntry = null;
    }
  }
}

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/796494.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-06
下一篇 2022-05-06

发表评论

登录后才能评论

评论列表(0条)

保存