Wigets学习之Toast轻提示

作者:renzp94
时间:2021-07-01 07:52:31

Flutter中只有一种轻提示SnackBar,所以要实现其他效果需要自实现

SnackBar

从底部弹出一条提示信息

  • content:提示内容(必填)
  • backgroundColor:背景色
  • elevation:阴影,behavior=SnackBarBehavior.floating才能看到效果
  • margin:外边距,需要behavior=SnackBarBehavior.floating才行,否则会报错
  • padding:内边距
  • width:宽度,需要behavior=SnackBarBehavior.floating && margin == null才行,否则会报错
  • shape:裁剪,可通过此属性设置圆角,如:shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(48)))
  • behavior:设置定位,默认为SnackBarBehavior.fixed(紧贴底部)behavior=SnackBarBehavior.floating(悬停底部)
  • action:操作,可用SnackBarAction指定
  • duration:动画时长
  • animation:动画
  • onVisible:显示事件

SnackBarAction

SnackBar操作

  • label:操作的文字
  • onPressed:操作的点击事件
  • textColor:文字颜色
  • disabledTextColor:文字禁用颜色
import 'package:flutter/material.dart';

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

class App extends StatelessWidget {
  final scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      scaffoldMessengerKey: scaffoldMessengerKey,
      title: 'Toast轻提示',
      home: Scaffold(
        appBar: AppBar(
          title: Text("Toast轻提示"),
        ),
        body: Row(
          children: [
            OutlinedButton(
                onPressed: () {
                  scaffoldMessengerKey.currentState?.showSnackBar(SnackBar(
                      content: Text('这是一条提示'),
                      behavior: SnackBarBehavior.floating,
                      elevation: 12,
                      margin: EdgeInsets.all(12),
                      action: SnackBarAction(
                        label: '确定',
                        onPressed: () {},
                      ),
                      onVisible: () {
                        print('显示');
                      }));
                },
                child: Text('显示提示')),
          ],
        ),
      ),
    );
  }
}

Toast(自实现)

注意:此实现父部件一定要是一个StatefulWidget,否则使用Overlay.of(context)获取OverlayState时总是为null,等找到解决办法再更新一下代码。

toast.dart

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

class Toast {
  Toast(
      {required this.context,
      this.child,
      this.message,
      this.position = ToastPosition.center,
      this.icon,
      this.duration = 2000,
      this.color = Colors.black,
      this.textColor = Colors.white})
      : assert(child == null || message == null,
            'child != null || message != null'),
        assert(message != null && child == null,
            'message != null && child == null') {
    OverlayState overlay = Overlay.of(context)!;

    // 屏幕大小
    final offset = MediaQuery.of(context).size.height * 0.2;
    // 距顶部距离
    final double? top = position == ToastPosition.top ? offset : null;
    // 距底部距离
    final double? bottom = position == ToastPosition.bottom ? offset : null;

    final Widget iconText = icon != null
        ? Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              SizedBox(
                height: 4,
              ),
              icon!,
              SizedBox(
                height: 4,
              ),
              Text(message!)
            ],
          )
        : Text(message!);

    // 提示信息
    final text = child ?? iconText;

    if (_overlayEntry != null) {
      clear();
    }

    _overlayEntry = OverlayEntry(builder: (BuildContext context) {
      // 如果是Toast.loading则icon一定是_LoadingIcon类型
      final isDisabledTap = icon != null && icon is _LoadingIcon;

      final body = _Body(
        text,
        color: color,
      );

      final absorbPointer = AbsorbPointer(
        ignoringSemantics: true,
        child: body,
      );

      return Positioned(
          top: top,
          bottom: bottom,
          child: _MessageDefaultStyle(
            isDisabledTap ? absorbPointer : body,
            color: textColor,
          ));
    });

    overlay.insert(_overlayEntry!);

    // 展示时长为0,则为不关闭
    if (duration != 0) {
      _timer = Timer(Duration(milliseconds: duration!), clear);
    }
  }

  final BuildContext context;
  // 自定义内容
  final Widget? child;
  // 提示内容
  final String? message;
  // 位置
  final ToastPosition? position;
  // 图标
  final Widget? icon;
  // 展示时长(ms)
  final int? duration;
  // 背景色
  final Color? color;
  // 文本颜色
  final Color? textColor;

  factory Toast.loading(
      {required BuildContext context,
      Widget? child,
      String? message,
      int? duration,
      Color? color,
      Color? textColor}) = _ToastLoading;

  factory Toast.success(
      {required BuildContext context,
      Widget? child,
      String? message,
      int? duration,
      Color? color,
      Color? textColor}) = _ToastSuccess;

  factory Toast.error(
      {required BuildContext context,
      Widget? child,
      String? message,
      int? duration,
      Color? color,
      Color? textColor}) = _ToastError;

  // 清除
  static clear() {
    _timer?.cancel();
    _timer = null;
    _overlayEntry?.remove();
    _overlayEntry = null;
  }
}

enum ToastPosition { top, center, bottom }

OverlayEntry? _overlayEntry;
Timer? _timer;

class _ToastLoading extends Toast {
  _ToastLoading(
      {required this.context,
      this.child,
      this.message,
      this.duration = 2000,
      this.color = Colors.black,
      this.textColor = Colors.white,
      this.iconBackgroupColor = Colors.white,
      this.iconColor = Colors.black12})
      : super(
            context: context,
            child: child,
            message: message,
            duration: duration,
            icon: _LoadingIcon(
              backgroundColor: iconBackgroupColor!,
              color: iconColor!,
            ),
            color: color,
            textColor: textColor);

  final BuildContext context;
  // 自定义内容
  final Widget? child;
  // 提示内容
  final String? message;
  // 展示时长(ms)
  final int? duration;
  // 背景色
  final Color? color;
  // 文本颜色
  final Color? textColor;
  // loading背景色
  final Color? iconBackgroupColor;
  // loading颜色
  final Color? iconColor;
}

class _ToastSuccess extends Toast {
  _ToastSuccess(
      {required this.context,
      this.child,
      this.message,
      this.duration = 2000,
      this.color = Colors.black,
      this.textColor = Colors.white,
      this.iconColor = Colors.white})
      : super(
            context: context,
            child: child,
            message: message,
            duration: duration,
            icon: Icon(
              Icons.check_circle,
              color: iconColor,
              size: 36,
            ),
            color: color,
            textColor: textColor);

  final BuildContext context;
  // 自定义内容
  final Widget? child;
  // 提示内容
  final String? message;
  // 展示时长(ms)
  final int? duration;
  // 背景色
  final Color? color;
  // 文本颜色
  final Color? textColor;
  // icon颜色
  final Color? iconColor;
}

class _ToastError extends Toast {
  _ToastError(
      {required this.context,
      this.child,
      this.message,
      this.duration = 2000,
      this.color = Colors.black,
      this.textColor = Colors.white,
      this.iconColor = Colors.white})
      : super(
            context: context,
            child: child,
            message: message,
            duration: duration,
            icon: Icon(
              Icons.cancel,
              color: iconColor,
              size: 36,
            ),
            color: color,
            textColor: textColor);

  final BuildContext context;
  // 自定义内容
  final Widget? child;
  // 提示内容
  final String? message;
  // 展示时长(ms)
  final int? duration;
  // 背景色
  final Color? color;
  // 文本颜色
  final Color? textColor;
  // icon颜色
  final Color? iconColor;
}

// 文本默认样式
class _MessageDefaultStyle extends StatelessWidget {
  const _MessageDefaultStyle(this.child, {Key? key, this.color})
      : super(key: key);

  final Widget child;
  final Color? color;

  @override
  Widget build(BuildContext context) {
    return DefaultTextStyle(
      textAlign: TextAlign.center,
      style: TextStyle(fontSize: 14, color: color!),
      child: child,
    );
  }
}

// 内容主体
class _Body extends StatelessWidget {
  const _Body(this.child, {Key? key, this.color}) : super(key: key);

  final Widget child;
  final Color? color;

  @override
  Widget build(BuildContext context) {
    // 屏幕大小
    final windowWidth = MediaQuery.of(context).size.width;
    // 最大宽度
    final maxWidth = windowWidth * 0.7;

    return Container(
        width: windowWidth,
        child: Center(
          child: Container(
              padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
              decoration: BoxDecoration(
                  color: color!.withOpacity(0.7),
                  borderRadius: BorderRadius.all(Radius.circular(4))),
              constraints: BoxConstraints(
                  minWidth: 88, minHeight: 40, maxWidth: maxWidth),
              child: child),
        ));
  }
}

// 加载Icon
class _LoadingIcon extends StatelessWidget {
  _LoadingIcon(
      {Key? key,
      this.backgroundColor = Colors.white,
      this.color = Colors.black12})
      : super(key: key);

  final Color backgroundColor;
  final Color color;

  @override
  Widget build(BuildContext context) {
    return CircularProgressIndicator(
      valueColor: AlwaysStoppedAnimation(color),
      backgroundColor: backgroundColor,
    );
  }
}
  • context:上下文(必填)
  • child:自定义内容
  • message:提示内容
  • position:显示位置,默认为ToastPosition.center
  • icon:图标
  • duration:展示时长,默认为 2000,单位:ms
  • color:背景色
  • textColor:文本颜色
import 'package:flutter/material.dart';
import 'package:learn/widgets/toast.dart';

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

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Toast轻提示",
      home: Home(),
    );
  }
}

class Home extends StatefulWidget {
  Home({Key? key}) : super(key: key);

  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Toast轻提示'),
        ),
        body: Wrap(
          children: [
            OutlinedButton(
              child: Text('显示Toast'),
              onPressed: () {
                Toast(
                  context: context,
                  message: "这是一个toast",
                );
              },
            ),
            OutlinedButton(
              child: Text('显示顶部Toast'),
              onPressed: () {
                Toast(
                    context: context,
                    message: "这是一个顶部toast",
                    position: ToastPosition.top);
              },
            ),
            OutlinedButton(
              child: Text('显示底部Toast'),
              onPressed: () {
                Toast(
                    context: context,
                    message: "这是一个底部toast",
                    position: ToastPosition.bottom);
              },
            ),
            OutlinedButton(
              child: Text('显示自定义背景色Toast'),
              onPressed: () {
                Toast(
                    context: context,
                    message: "这是一个自定义背景色toast",
                    color: Colors.yellow.shade800);
              },
            ),
            OutlinedButton(
              child: Text('显示自定义颜色Toast'),
              onPressed: () {
                Toast(
                    context: context,
                    message: "这是一个自定义颜色toast",
                    textColor: Colors.green);
              },
            ),
          ],
        ));
  }
}

Toast 还提供了三个命名构造函数:Toast.loadingToast.successToast.error

import 'package:flutter/material.dart';
import 'package:learn/widgets/toast.dart';

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

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Toast轻提示",
      home: Home(),
    );
  }
}

class Home extends StatefulWidget {
  Home({Key? key}) : super(key: key);

  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Toast轻提示'),
        ),
        body: Wrap(
          children: [
            OutlinedButton(
              child: Text('显示Loading Toast'),
              onPressed: () {
                Toast.loading(context: context, message: "这是一个Loading Toast");
              },
            ),
            OutlinedButton(
              child: Text('显示成功Toast'),
              onPressed: () {
                Toast.success(context: context, message: "这是一个成功Toast");
              },
            ),
            OutlinedButton(
              child: Text('显示失败Toast'),
              onPressed: () {
                Toast.error(context: context, message: "这是一个Loading Toast");
              },
            ),
          ],
        ));
  }
}