flutter 弹窗之系列二

自定义弹窗(含底部抽屉)Dialog

bash 复制代码
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          const SizedBox(
            height: 0,
            width: double.infinity,
          ),
          TextButton(
            child: const Text("show dialog"),
            onPressed: () => showCustomDialog(),
          ),
          TextButton(
            child: const Text("show delay dialog"),
            onPressed: () => showDelayDialog(),
          ),
          TextButton(
            child: const Text("show sheet"),
            onPressed: () => showSheetDialog(),
          ),
        ],
      ),
    );
  }

  void showCustomDialog() {
    CustomDialog.show(context, (context, dismiss) {
      return Container(
        width: 200,
        height: 100,
        color: Colors.white,
        child: Center(
          child:
              TextButton(onPressed: () => dismiss(), child: const Text("POP")),
        ),
      );
    });
  }

  void showSheetDialog() {
    CustomDialog.showBottomSheet(context, (ctx, dismiss) {
      return Container(
        height: 300,
        color: Colors.white,
        margin: const EdgeInsets.all(20),
        child: Center(
          child:
              TextButton(onPressed: () => dismiss(), child: const Text("POP")),
        ),
      );
    });
  }

  void showDelayDialog() {
    CustomDialog.show(context, (context, dismiss) {
      //延时关闭
      Timer(const Duration(seconds: 2), () => dismiss());

      return Container(
        width: 200,
        height: 100,
        color: Colors.yellow,
        child: const Center(
          child: Text("等待"),
        ),
      );
    }, cancellable: true);
  }
}

class CustomDialog {
  static void show(BuildContext context,
      Widget Function(BuildContext ctx, void Function() dismiss) builder,
      {bool cancellable = false}) {
    showDialog(
      context: context,
      barrierDismissible: cancellable,
      builder: (ctx) {
        return WillPopScope(
          child: Dialog(
            child: builder(ctx, () => Navigator.of(ctx).pop()),
            backgroundColor: Colors.transparent,
            shape: const RoundedRectangleBorder(
                borderRadius: BorderRadius.all(Radius.circular(0.0))),
            elevation: 0,
            alignment: Alignment.center,
          ),
          onWillPop: () async => cancellable,
        );
      },
    );
  }

  static void showBottomSheet(BuildContext context,
      Widget Function(BuildContext ctx, void Function() dismiss) builder,
      {bool cancellable = true}) {
    showModalBottomSheet(
      context: context,
      isDismissible: cancellable,
      enableDrag: cancellable,
      isScrollControlled: true,
      builder: (BuildContext ctx) {
        return WillPopScope(
          child: builder(ctx, () => Navigator.of(ctx).pop()),
          onWillPop: () async => cancellable,
        );
      },
      //不设置会默认使用屏幕最大宽度而不是子组件宽度
      constraints: const BoxConstraints(
          minWidth: 0,
          minHeight: 0,
          maxWidth: double.infinity,
          maxHeight: double.infinity),
      backgroundColor: Colors.transparent,
    );
  }
}

AlertDialog的属性

  • title:标题
  • titlePadding:标题内边距
  • titleTextStyle:标题样式
  • content:内容,推荐用SingleChildScrollView包裹
  • contentPadding:EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0),内容内边距
  • contentTextStyle:内容样式
  • actions:按钮集合,可以放多个
  • actionsPadding:EdgeInsets.zero,actions内边距
  • buttonPadding:按钮内边距
  • backgroundColor:背景色
  • elevation:阴影
  • shape:形状
  • scrollable = false:

SimpleDialog

  • title:标题
  • titlePadding:EdgeInsets.fromLTRB(24.0, 24.0, 24.0, 0.0),标题内边距
  • titleTextStyle:标题样式
  • children:子节点
  • contentPadding:EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 16.0),内容内边距
  • backgroundColor:背景色
  • elevation:阴影
  • shape:形状

全局弹窗

  • pubspec.yaml增加dio依赖包
bash 复制代码
dio: any # dio依赖包
bash 复制代码
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {


  void _showLoadingDialog() {
    showDialog(
      context: context,
      builder: (context) {
        // 用Scaffold返回显示的内容,能跟随主题
        return Scaffold(
          backgroundColor: Colors.transparent, // 设置透明背影
          body: Center( // 居中显示
            child: Column( // 定义垂直布局
              mainAxisAlignment: MainAxisAlignment.center, // 主轴居中布局,相关介绍可以搜下flutter-ui的内容
              children: <Widget>[
                // CircularProgressIndicator自带loading效果,需要宽高设置可在外加一层sizedbox,设置宽高即可
                CircularProgressIndicator(),
                SizedBox(
                  height: 10,
                ),
                Text('loading'), // 文字
                // 触发关闭窗口
                GestureDetector(
                  child: Text('close dialog'),
                  onTap: () {
                    print('close');
                  },
                ),
              ],
            ), // 自带loading效果,需要宽高设置可在外加一层sizedbox,设置宽高即可
          ),
        );
      },
    );
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: GestureDetector(
          onTap: () {
            _showLoadingDialog();
          },
          child: const Text(
            '\n点击显示弹窗一\n',
          ),
        ),
      ),
    );
  }
}

接入dio 网络请求显示弹窗

bash 复制代码
import 'package:dio/dio.dart' show Dio, InterceptorsWrapper;
import 'package:flutter_custom_widget/http/Loading.dart';

Dio? dio;

class Http {
  static Dio? instance() {
    if (dio != null) {
      return dio;// 实例化dio
    }
    dio = Dio();
    // 增加拦截器
    dio?.interceptors.add(
      InterceptorsWrapper(
        // 接口请求前数据处理
        onRequest: (options,handler) {
          Loading.before("onRequest");
        },
        // 接口成功返回时处理
        onResponse: (resp,handler) {
          Loading.complete();
        },
        // 接口报错时处理
        onError: ( error,handler) {
          Loading.complete();
        },
      ),
    );
    return dio;
  }

  /// get接口请求
  /// path: 接口地址
  static get(path) {
    return instance()?.get(path);
  }
}
bash 复制代码
import 'package:flutter/material.dart';

class Loading {
  static dynamic ctx;

  static void before(text) {
    // 请求前显示弹窗
    showDialog(
      context: ctx,
      builder: (context) {
        return Index(text: text);
      },
    );
  }

  static void complete() {
    // 完成后关闭loading窗口
    Navigator.of(ctx, rootNavigator: true).pop();
  }
}

// 弹窗内容
class Index extends StatelessWidget {
  final String? text;

  Index({Key? key, @required this.text}):super(key: key);

  @override
  Widget build(BuildContext context) {
    return  Text('$text');
  }
}

实现全局存储context

bash 复制代码
@override
Widget build(BuildContext context) {
    ......
    
    Loading.ctx = context; // 注入context

    ......

}
bash 复制代码
class Loading {
  static dynamic ctx;
  static void before(text) {
    // 请求前显示弹窗
	// showDialog(context: ctx, builder: (context) {
	//   return Index(text:text);
	// );
  }

  static void complete() {
    // 完成后关闭loading窗口
	// Navigator.of(ctx, rootNavigator: true).pop();
  }
}

实现dio请求时loading

bash 复制代码
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  
  @override
  Widget build(BuildContext context) {
    Loading.ctx = context; // 注入context
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: GestureDetector(
          onTap: () {
            Http.get('https://blog.csdn.net/u013491829/article/details/137032263');
          },
          child: const Text(
            '\n点击进行网络加载\n',
          ),
        ),
      ),
    );
  }
}

并发请求时loading处理

并发请求,loading只需要保证有一个在当前运行。接口返回结束,只需要保证最后一个完成时,关闭loading。

  • 使用Set有排重作用,比较使用用来管理并发请求地址。通过Set.length控制弹窗与关闭窗口。
  • 增加LoadingStatus判断是否已经有弹窗存在
  • 修改onRequest/onResponse/onError入参
bash 复制代码
import 'package:flutter/material.dart';

Set dict = Set();
bool loadingStatus = false;
class Loading {
  static dynamic ctx;

  static void before(uri, text) {
    dict.add(uri); // 放入set变量中
    // 已有弹窗,则不再显示弹窗, dict.length >= 2 保证了有一个执行弹窗即可,
    if (loadingStatus == true || dict.length >= 2) {
      return ;
    }
    loadingStatus = true; // 修改状态
    // 请求前显示弹窗
    showDialog(
      context: ctx,
      builder: (context) {
        return Index(text: text);
      },
    );
  }

  static void complete(uri) {
    dict.remove(uri);
    // 所有接口接口返回并有弹窗
    if (dict.length == 0 && loadingStatus == true) {
      loadingStatus = false; // 修改状态
      // 完成后关闭loading窗口
      Navigator.of(ctx, rootNavigator: true).pop();
    }
  }
}

案例 切换到分支flutter_custom_widget

相关推荐
梦想的颜色9 小时前
【编程实战】AI 时代 APP 开发全栈硬核指南:技术选型 + AI 架构 + 模型落地全维度决策
python·flutter·react native·react.js·ai·桌面应用·milvus
yuanlaile1 天前
2026年全新Flutter 跨平台开发全栈学习资料整理
flutter·getx·鸿蒙 next·flutter开发鸿蒙·flutter入门实战·flutter全栈
恋猫de小郭2 天前
Genkit Dart 1.0 发布,Flutter 原生的 AI Agent 终于完整了
android·前端·flutter
第10086个小白2 天前
flutter桌面端打包方式(Inno Setup Compiler)版本
前端·flutter
陆断枫2 天前
Flutter 3.47 动画引擎盘点:Impeller 全平台,和那些"新特性"
flutter
程序员老刘2 天前
Flet 1.0 思路很好,可惜来晚了
flutter·ai编程·客户端
m0_738185823 天前
Flutter 鸿蒙化实战:qr_code_scanner_plus 适配 OpenHarmony,二维码扫描
flutter·华为·harmonyos·鸿蒙
恋猫de小郭3 天前
Flutter + EmbeddingGemma 2,谷歌发布完全端侧的 AI Edge Foresight
android·前端·flutter
君赏3 天前
基于 FlutterPatch 快速接入 Flutter 热更新
flutter
律宏阔3 天前
Dart FFI 内存管理:用 using + Arena 替代嵌套 try-finally
前端·flutter