如何在Flutter中通过点击外部来关闭覆盖层

8
我正在创建一个点击按钮时的叠加层,以下代码可以正常工作,但我希望通过在其外部单击来关闭叠加层。

enter image description here

参考代码:

  • 我使用OverlayEntry创建覆盖层

  • 通过在六个按钮之一上点击时可用的偏移量来设置覆盖层位置。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: GesturePositionDetector(),
    );
  }
}

class GesturePositionDetector extends StatelessWidget {
  OverlayState overlayState;
  OverlayEntry _overlayEntry;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
          height: MediaQuery.of(context).size.height,
          width: MediaQuery.of(context).size.width,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: <Widget>[
              _getButtons([1, 2, 3], context),
              _getButtons([4, 5, 6], context),
              _getButtons([7, 8, 9], context)
            ],
          )),
    );
  }

  Widget _getButtons(List<int> labels, BuildContext context) {
    var listOfButtons = List<Widget>();

    labels.forEach((int label) {
      listOfButtons.add(_getButtonView('Button $label', context, label));
    });

    return Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: listOfButtons);
  }

  Widget _getButtonView(String label, BuildContext context, int index) {
    return GestureDetector(
      child: Container(
        height: 50,
        width: 150,
        margin : EdgeInsets.fromLTRB(15, 25, 15, 20),
        decoration: BoxDecoration(
            color: Colors.blueAccent,
            borderRadius: BorderRadius.all(Radius.circular(10))),
        child: Center(
          child: Text(
            label,
            style: TextStyle(fontWeight: FontWeight.bold),
          ),
        ),
      ),
      onTapDown: (details) {
        onTap(details, context, index);
      },
    );
  }

  onTap(TapDownDetails details, context, int index) {
    var size = MediaQuery.of(context).size;
    var offset = details.globalPosition;

      _overlayEntry?.remove();
    overlayState = Overlay.of(context);

    _overlayEntry = new OverlayEntry(
        builder: (BuildContext context) => Positioned(
            left: offset.dx + 300 >= size.width ? offset.dx - 300 : offset.dx,
            top: offset.dy + 200 >= size.height ? offset.dy - 200 : offset.dy,
            child: Material(
              color: Colors.transparent,
              child: Container(
                  width: 300,
                  height: 200,
                  child: Container(
                      decoration: BoxDecoration(
                          color: Colors.white70,
                          borderRadius: BorderRadius.circular(20),
                          boxShadow: [
                            BoxShadow(
                              color: Colors.grey,
                              blurRadius: 5.0,
                            ),
                          ]),
                      margin: EdgeInsets.symmetric(horizontal: 20),
                      padding: EdgeInsets.fromLTRB(16, 10, 16, 10),
                      child: Wrap(
                        crossAxisAlignment: WrapCrossAlignment.center,
                        alignment: WrapAlignment.center,
                        direction: Axis.vertical,
                        spacing: 10,
                        children: <Widget>[
                          Text(
                            'Main Text',
                            style: TextStyle(
                                fontWeight: FontWeight.bold, fontSize: 25),
                          ),
                          Text('sub text 1'),
                          Text('sub text 2'),
                          Text('sub text 3'),
                          Text('sub text 4')
                        ],
                      ))),
            )));
    overlayState.insert(_overlayEntry);
  }
}
2个回答

13

将页面主体包裹在手势检测器中,点击主体时会触发点击事件,在点击事件方法中关闭覆盖层,如下所示的代码

代码:

body: GestureDetector(
  onTap: (){
    _overlayEntry?.remove();
  },
  child : Container(
  height: MediaQuery.of(context).size.height,
  width: MediaQuery.of(context).size.width,

9
我已经看到你标记为正确答案的回答。虽然在你的情况下它可能有效,因为你已经将所有代码都写在了一个屏幕上。但是,如果你想提取覆盖图层或创建一个可以从任何地方调用的自定义对话框,那么这个答案就不适用了。为此,你需要考虑我的答案。
将从 OverlayEntry 的 builder 返回的小部件用以下方式包装起来:
    return OverlayEntry(
      builder: (context) => SizedBox(
        width: screenSize.width,
        height: screenSize.height,
        child: Stack(
          children: [
            ModalBarrier(
              onDismiss: () {
                _closeOverlay();
              },
            ),
            Positioned(
              width: width,
              child: CompositedTransformFollower(
                link: _layerLink,
                showWhenUnlinked: false,
                offset: Offset(
                  0,
                  parentSize.height,
                ),
                child: Child(),
              ),
            ),
          ],
        ),
      ),
    );

无论什么情况下,您可能需要裁剪ModalBarrier区域的一部分,以避免与ModalBarrier重叠。在这种情况下,您可以使用CustomClipper来裁剪ModalBarrier的一部分。


1
男人中的英雄。 - undefined
@jojeyh 哈哈 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接