Flutter 用画面仿对话框(Dialog)点击画面外不关闭

510 阅读2分钟

Flutter 用画面仿对话框(Dialog)点击画面外不关闭

出发点

由于 Flutter 的 AlertDialog 有时候会有局限性,所以用画面仿了一下对话框。

技术点

主要是使用 Stack 将 ModalBarrier 和 Material 叠加。

  1. ModalBarrier 遮罩组件

    dismissible 属性设置为 false 时,点击不会弹出页面,即不会关闭; 设置为 true 时,点击就会弹出页面,即关闭。

  2. 主体使用 Material

    使用 Scaffold 的话,对话框画面不太方便设置圆角。
    不使用 Material 的话,是无法放置 TextFormField 这样的组件的。

  3. 需要给 Material 设置内边距

    这里是用设置画面的高宽减去对话框画面的高宽后除以 2 得到。
    这里对话框画面的高宽都设置成了 400 。

    final width = MediaQuery.of(context).size.width;
    final height = MediaQuery.of(context).size.height;
    
    final double tb = (height - 400 - 24) / 2;
    final double lr = (width - 400) / 2;
    
  4. 使用 showDialog 打开

    showDialog(
      context: context,
      builder: (BuildContext context) => const DialogPage(),
    );
    
  5. 在对话框画面点击关闭按钮关闭

    Navigator.of(context).pop();
    

画面效果

image.png

image.png

代码

dialog_page.dart

import 'package:flutter/material.dart';

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

  @override
  State<DialogPage> createState() => _DialogPageState();
}

class _DialogPageState extends State<DialogPage> {
  @override
  Widget build(BuildContext context) {
    final width = MediaQuery.of(context).size.width;
    final height = MediaQuery.of(context).size.height;

    final double tb = (height - 400 - 24) / 2;
    final double lr = (width - 400) / 2;

    return Stack(
      children: [
        const ModalBarrier(
          dismissible: false, // 点击不关闭
        ),
        Center(
          child: Padding(
            padding: EdgeInsets.only(left: lr, right: lr, top: tb, bottom: tb),
            child: Material(
              borderRadius: BorderRadius.circular(8),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.start,
                children: [
                  Row(
                    mainAxisAlignment: MainAxisAlignment.start,
                    children: [
                      TextButton(
                        child: const Text('关闭'),
                        onPressed: () {
                          Navigator.of(context).pop();
                        },
                      ),
                    ],
                  ),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.start,
                    children: [
                      Expanded(
                        child: Padding(
                          padding: const EdgeInsets.all(8.0),
                          child: TextFormField(
                            decoration: const InputDecoration(
                              label: Text('Hello'),
                            ),
                          ),
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),
        ),
      ],
    );
  }
}

main.dart

import 'package:flutter/material.dart';

import 'dialog_page.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomePage(),
    );
  }
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  void showDialogPage() {
    showDialog(
      context: context,
      builder: (BuildContext context) => const DialogPage(),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('子画面'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            TextButton(
              onPressed: showDialogPage,
              child: const Text('打开对话框画面'),
            ),
          ],
        ),
      ),
    );
  }
}