Flutter制作简单的遮罩层

2,980 阅读2分钟

「这是我参与11月更文挑战的第16天,活动详情查看:2021最后一次更文挑战」。

开发 Web 页面时,经常访问 API 的时候,整个页面会遮罩起来,等 API 处理完再正常显示画面。
那 Flutter 应用的遮罩该怎么整呢?
Flutter 提供了模态屏障组件 ModalBarrier,可以使用该组件来写。

以为自己实现了个什么小功能,结果一搜索,网上已经有很多相关文章了。
代码也折腾了半天,还是发出来吧。

本文相关代码:helloflutter/helloloading at main · bettersun/helloflutter · GitHub

模态屏障组件 ModalBarrier

ModalBarrier有四个参数:

  • color: 模态屏障组件的颜色,可改变透明度达到遮罩效果。
  • dismissible: 这个参数设置为 true 时,点击会尝试 pop 路由栈。设为 false,点击无反应。
  • semanticsLabel: 如果 dismissible 为 true, 这里定义模态屏障的语义标签。
  • barrierSemanticsDismissible: 模态屏障的语义是否包含在语义树中。

后面两个具体不太明白是什么意思。

简单要求

  1. 可控制能否显示
  2. 加载中,可只转圈(CircularProgressIndicator),也可带有加载中的提示信息。
  3. 点击时可弹出路由栈,也可设置不弹出,如果一直加载无反应,应用容易就只能一直加载中了。

效果图

loading.gif

实现思路

  1. 子组件为非加载状态的页面。
  2. 使用 Stack 将 子组件、遮罩层和转圈(加载中的信息)叠加在一起,因为 Stack 中前面的组件在最底层,后面的组件在最顶层。所以组件的 child 为第一个组件,中间是遮罩组件 ModalBarrier ,最后一个是转圈(或带有提示信息)。

实现代码

loading.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

// 加载遮罩组件
class Loading extends StatelessWidget {
  const Loading({
    Key? key,
    required this.child,
    required this.isLoading,
    this.message = "",
    this.maskOpacity = 0.2,
    this.maskColor = Colors.lightBlueAccent,
    this.maskDismissible = true,
  })  : assert(maskOpacity >= 0.0 && maskOpacity <= 1.0),
        super(key: key);

  // 子组件(非空)
  final Widget child;
  // 是否加载中(非空) true: 加载中 false:非加载中
  final bool isLoading;
  // 遮罩层显示的信息
  final String message;
  // 遮罩层的透明度(0.0 ~ 1.0) 默认0.2
  final double maskOpacity;
  // 遮罩层的颜色 默认 Colors.lightBlueAccent
  final Color maskColor;
  // 点击遮罩层 pop 路由 true: pop(默认), false: 不pop
  final bool maskDismissible;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        child,
        Visibility(
          visible: isLoading,
          child: ModalBarrier(color: maskColor.withOpacity(maskOpacity), dismissible: maskDismissible),
        ),
        isLoading
            ? Center(
                child: message == ""
                    ? const CircularProgressIndicator()
                    : Container(
                        margin: const EdgeInsets.all(16.0),
                        height: 160,
                        decoration: BoxDecoration(
                          color: const Color.fromRGBO(128, 128, 128, 0.2),
                          borderRadius: BorderRadius.circular(8.0),
                        ),
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: [
                            const CircularProgressIndicator(),
                            Center(
                              child: Container(
                                margin: const EdgeInsets.all(8.0),
                                padding: const EdgeInsets.all(8.0),
                                decoration: BoxDecoration(
                                  color: const Color.fromRGBO(196, 196, 196, 1.0),
                                  borderRadius: BorderRadius.circular(4.0),
                                ),
                                child: Text(message,
                                    overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.bodyText1),
                              ),
                            ),
                          ],
                        ),
                      ),
              )
            : Container(),
      ],
    );
  }
}

具体用法

Loading(
  child: Hello(),
  isLoading:  true,
  message: "加载中",
),

正常要表示的画面 Hello() 用 Loading 加载遮罩组件嵌套起来,然后根据标志位等参数来控制遮罩层在子画面的显示。