diff --git a/example/lib/sample/components/toast/toast_example.dart b/example/lib/sample/components/toast/toast_example.dart index d7ade587..a2c94304 100644 --- a/example/lib/sample/components/toast/toast_example.dart +++ b/example/lib/sample/components/toast/toast_example.dart @@ -6,7 +6,8 @@ class ToastExample extends StatefulWidget { _ToastExampleState createState() => _ToastExampleState(); } -class _ToastExampleState extends State with TickerProviderStateMixin { +class _ToastExampleState extends State + with TickerProviderStateMixin { @override Widget build(BuildContext context) { return Scaffold( @@ -14,54 +15,75 @@ class _ToastExampleState extends State with TickerProviderStateMix title: 'BrnToast示例', ), body: SingleChildScrollView( - child: Column( - children: [ - Padding( - padding: EdgeInsets.only(top: 50, bottom: 50), - child: Center( - child: RaisedButton( - onPressed: () { - BrnToast.show("普通长Toast", context, duration: BrnToast.LENGTH_LONG, gravity: 1); - }, - child: Text("普通长Toast"), - ), + child: Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () { + BrnToast.show( + "普通长Toast", + context, + duration: BrnDuration.long, + gravity: BrnToastGravity.center, + ); + }, + child: Text("普通长Toast"), ), - ), - Padding( - padding: EdgeInsets.only(top: 50, bottom: 50), - child: Center( - child: RaisedButton( - onPressed: () { - BrnToast.show("失败图标Toast", context, - preIcon: Image.asset( - "assets/image/icon_toast_fail.png", - width: 24, - height: 24, - ), - duration: BrnToast.LENGTH_SHORT); - }, - child: Text("失败图标Toast"), - ), + ElevatedButton( + onPressed: () { + BrnToast.show( + "失败图标Toast", + context, + preIcon: Image.asset( + "assets/image/icon_toast_fail.png", + width: 24, + height: 24, + ), + duration: BrnDuration.short, + ); + }, + child: Text("失败图标Toast"), ), - ), - Padding( - padding: EdgeInsets.only(top: 50, bottom: 50), - child: Center( - child: RaisedButton( - onPressed: () { - BrnToast.show("成功图标Toast", context, - preIcon: Image.asset( - "assets/image/icon_toast_success.png", - width: 24, - height: 24, - ), - duration: BrnToast.LENGTH_SHORT); - }, - child: Text("成功图标Toast"), - ), + ElevatedButton( + onPressed: () { + BrnToast.show( + "成功图标Toast", + context, + preIcon: Image.asset( + "assets/image/icon_toast_success.png", + width: 24, + height: 24, + ), + duration: BrnDuration.short, + ); + }, + child: Text("成功图标Toast"), ), - ) - ], + ElevatedButton( + onPressed: () { + BrnToast.show( + "自定义位置Toast", + context, + duration: BrnDuration.short, + verticalOffset: 100, + gravity: BrnToastGravity.bottom + ); + }, + child: Text("自定义位置Toast"), + ), + ElevatedButton( + onPressed: () { + BrnToast.show( + "自定义时长Toast", + context, + duration: Duration(seconds: 5), + ); + }, + child: Text("自定义时长Toast(5s)"), + ), + ], + ), ), ), ); diff --git a/lib/src/components/toast/brn_toast.dart b/lib/src/components/toast/brn_toast.dart index a6a88aa4..158edbca 100644 --- a/lib/src/components/toast/brn_toast.dart +++ b/lib/src/components/toast/brn_toast.dart @@ -1,167 +1,255 @@ -// @dart=2.9 - import 'dart:math'; import 'package:flutter/material.dart'; +/// 位置枚举 +enum BrnToastGravity { + /// 底部显示 + bottom, + + /// 居中显示 + center, + + /// 顶部显示 + top, +} + +/// toast 显示时长 +class BrnDuration { + BrnDuration._(); + + /// toast 显示较短时间(1s) + static const Duration short = Duration(seconds: 1); + + /// toast 显示较长时间(3s) + static const Duration long = Duration(seconds: 3); +} + /// 通用的Toast组件 class BrnToast { - static const LENGTH_SHORT = 1; - static const LENGTH_LONG = 2; - static const BOTTOM = 0; - static const CENTER = 1; - static const TOP = 2; + /// Toast 距离顶部默认间距 + static const int _defaultTopOffset = 50; - static _ToastView preToastView; + /// Toast 距离底部默认间距 + static const int _defaultBottomOffset = 50; + + /// _ToastView + static _ToastView? preToastView; /// 显示在中间。如不设置duration则会自动根据内容长度来计算(更友好,最长5秒) - static void showInCenter(String text, BuildContext context, {int duration}) { - show(text, context, duration: duration, gravity: CENTER); + static void showInCenter( + {required String text, + required BuildContext context, + Duration? duration}) { + show( + text, + context, + duration: duration, + gravity: BrnToastGravity.center, + ); } /// 显示Toast,如不设置duration则会自动根据内容长度来计算(更友好,最长5秒) - static void show(String text, BuildContext context, - {int duration, - int gravity = BOTTOM, - Color backgroundColor = const Color(0xFF222222), - textStyle = const TextStyle(fontSize: 16, color: Colors.white), - double backgroundRadius = 8, - Image preIcon, - double verticalOffset, - VoidCallback onDismiss}) { - OverlayState overlayState = Overlay.of(context); - if (text == null || context == null || overlayState == null) { - return; - } + static void show( + String text, + BuildContext context, { + Duration? duration, + Color? background, + TextStyle? textStyle, + double? radius, + Image? preIcon, + double? verticalOffset, + VoidCallback? onDismiss, + BrnToastGravity? gravity, + }) { + final OverlayState? overlayState = Overlay.of(context); + if (overlayState == null) return; + preToastView?._dismiss(); preToastView = null; - verticalOffset = getRealVerticalOffset(verticalOffset, gravity, context); - // 自动根据内容长度决定显示时长,更加人性化 - int aiDuration = duration ?? min(text.length * 0.06 + 0.8, 5.0).ceil(); - - _ToastView toastView = _ToastView(); - toastView.overlayState = overlayState; - OverlayEntry overlayEntry; - overlayEntry = OverlayEntry(builder: (context) { - return _buildToastLayout(context, backgroundColor, backgroundRadius, - preIcon, text, textStyle, gravity, - verticalOffset: verticalOffset); - }); - toastView._overlayEntry = overlayEntry; + final double finalVerticalOffset = getVerticalOffset( + context: context, + gravity: gravity, + verticalOffset: verticalOffset, + ); + + /// 自动根据内容长度决定显示时长,更加人性化 + final int autoDuration = min(text.length * 0.06 + 0.8, 5.0).ceil(); + final Duration finalDuration = + duration ?? Duration(milliseconds: autoDuration); + final OverlayEntry overlayEntry = OverlayEntry( + builder: (context) { + return _ToastWidget( + widget: ToastChild( + background: background, + radius: radius, + msg: text, + leading: preIcon, + textStyle: textStyle, + gravity: gravity, + verticalOffset: finalVerticalOffset, + ), + ); + }, + ); + final _ToastView toastView = + _ToastView(overlayState: overlayState, overlayEntry: overlayEntry); preToastView = toastView; - toastView._show(aiDuration, onDismiss: onDismiss); + toastView._show( + duration: finalDuration, + onDismiss: onDismiss, + ); } - static double getRealVerticalOffset( - double verticalOffset, int gravity, BuildContext context) { - if (gravity == BrnToast.TOP) { - verticalOffset = - (verticalOffset ?? 0) + MediaQuery.of(context).viewInsets.top + 50; - } else if (gravity == BrnToast.BOTTOM) { - verticalOffset = - (verticalOffset ?? 0) + MediaQuery.of(context).viewInsets.bottom + 50; - } else { - verticalOffset = 0; + /// 获取默认设置的垂直间距 + static double getVerticalOffset({ + required BuildContext context, + BrnToastGravity? gravity, + double? verticalOffset, + }) { + final double _verticalOffset = verticalOffset ?? 0; + final double defaultOffset; + switch (gravity) { + case BrnToastGravity.bottom: + final offset = verticalOffset ?? _defaultTopOffset; + defaultOffset = MediaQuery.of(context).viewInsets.bottom + offset; + break; + case BrnToastGravity.top: + final offset = verticalOffset ?? _defaultBottomOffset; + defaultOffset = MediaQuery.of(context).viewInsets.top + offset; + break; + case BrnToastGravity.center: + default: + defaultOffset = verticalOffset ?? 0; } - return verticalOffset; - } -} -_ToastWidget _buildToastLayout( - BuildContext context, - Color background, - double backgroundRadius, - Image preIcon, - String msg, - TextStyle textStyle, - int gravity, - {double verticalOffset}) { - Alignment alignment = Alignment.center; - EdgeInsets padding; - if (gravity == BrnToast.BOTTOM) { - alignment = Alignment.bottomCenter; - padding = EdgeInsets.only(bottom: verticalOffset); - } else if (gravity == BrnToast.TOP) { - alignment = Alignment.topCenter; - padding = EdgeInsets.only(top: verticalOffset); + return defaultOffset + _verticalOffset; } - - return _ToastWidget( - widget: Container( - width: MediaQuery.of(context).size.width, - child: Container( - padding: padding, - alignment: alignment, - width: MediaQuery.of(context).size.width, - child: Container( - decoration: BoxDecoration( - color: background, - borderRadius: BorderRadius.circular(backgroundRadius), - ), - margin: EdgeInsets.symmetric(horizontal: 20), - padding: EdgeInsets.fromLTRB(18, 10, 18, 10), - child: RichText( - text: TextSpan(children: [ - preIcon != null - ? WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: Padding( - padding: EdgeInsets.only(right: 6), - child: preIcon, - )) - : TextSpan(text: ""), - TextSpan(text: msg, style: textStyle), - ]), - ), - )), - ), - gravity: gravity); } class _ToastView { OverlayState overlayState; - OverlayEntry _overlayEntry; + OverlayEntry overlayEntry; bool _isVisible = false; - _show(int duration, {VoidCallback onDismiss}) async { + _ToastView({ + required this.overlayState, + required this.overlayEntry, + }); + + void _show({required Duration duration, VoidCallback? onDismiss}) async { _isVisible = true; - overlayState.insert(_overlayEntry); - await Future.delayed( - Duration(seconds: duration == null ? BrnToast.LENGTH_SHORT : duration)); - _dismiss(); - if (onDismiss != null) { - onDismiss(); - } + overlayState.insert(overlayEntry); + Future.delayed(duration, () { + _dismiss(); + onDismiss?.call(); + }); } - _dismiss() async { + void _dismiss() async { if (!_isVisible) { return; } _isVisible = false; - _overlayEntry?.remove(); + overlayEntry.remove(); } } -class _ToastWidget extends StatelessWidget { - final Widget widget; - final int gravity; +class ToastChild extends StatelessWidget { + const ToastChild({ + Key? key, + required this.msg, + required this.verticalOffset, + this.background, + this.radius, + this.leading, + this.gravity, + this.textStyle, + }) : super(key: key); + + Alignment get alignment { + switch (gravity) { + case BrnToastGravity.bottom: + return Alignment.bottomCenter; + case BrnToastGravity.top: + return Alignment.topCenter; + case BrnToastGravity.center: + default: + return Alignment.center; + } + } + + EdgeInsets get padding { + switch (gravity) { + case BrnToastGravity.bottom: + return EdgeInsets.only(bottom: verticalOffset); + case BrnToastGravity.top: + return EdgeInsets.only(top: verticalOffset); + case BrnToastGravity.center: + default: + return EdgeInsets.only(top: verticalOffset); + } + } + + final String msg; + final double verticalOffset; + final Color? background; + final double? radius; + final Image? leading; + final BrnToastGravity? gravity; + final TextStyle? textStyle; + + InlineSpan get leadingSpan { + if (leading == null) { + return const TextSpan(text: ""); + } + return WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Padding(padding: const EdgeInsets.only(right: 6), child: leading!), + ); + } - _ToastWidget({ - Key key, - @required this.widget, - @required this.gravity, + @override + Widget build(BuildContext context) { + return SizedBox( + width: MediaQuery.of(context).size.width, + child: Container( + padding: padding, + alignment: alignment, + width: MediaQuery.of(context).size.width, + child: Container( + decoration: BoxDecoration( + color: background ?? const Color(0xFF222222), + borderRadius: BorderRadius.circular(radius ?? 8), + ), + margin: const EdgeInsets.symmetric(horizontal: 20), + padding: const EdgeInsets.fromLTRB(18, 10, 18, 10), + child: RichText( + text: TextSpan(children: [ + leadingSpan, + TextSpan(text: msg, style: textStyle), + ]), + ), + ), + ), + ); + } +} + +class _ToastWidget extends StatelessWidget { + const _ToastWidget({ + Key? key, + required this.widget, }) : super(key: key); - // 使用IgnorePointer,方便手势透传过去 + final Widget widget; + + /// 使用IgnorePointer,方便手势透传过去 @override Widget build(BuildContext context) { return IgnorePointer( - child: Material( - color: Colors.transparent, - child: widget, - ), + child: Material(color: Colors.transparent, child: widget), ); } }