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

相关推荐
AiFlutter19 小时前
Flutter之Package教程
flutter
Mingyueyixi1 天前
Flutter Spacer引发的The ParentDataWidget Expanded(flex: 1) 惨案
前端·flutter
crasowas1 天前
Flutter问题记录 - 适配Xcode 16和iOS 18
flutter·ios·xcode
老田低代码2 天前
Dart自从引入null check后写Flutter App总有一种难受的感觉
前端·flutter
AiFlutter3 天前
Flutter Web首次加载时添加动画
前端·flutter
ZemanZhang4 天前
Flutter启动无法运行热重载
flutter
AiFlutter5 天前
Flutter-底部选择弹窗(showModalBottomSheet)
flutter
帅次5 天前
Android Studio:驱动高效开发的全方位智能平台
android·ide·flutter·kotlin·gradle·android studio·android jetpack
程序者王大川5 天前
【前端】Flutter vs uni-app:性能对比分析
前端·flutter·uni-app·安卓·全栈·性能分析·原生