写点什么

harmony_flutter 自定义 toast

作者:flfljh
  • 2024-11-27
    湖南
  • 本文字数:7460 字

    阅读完需:约 24 分钟

Harmony_flutter 自定义 Toast 可让开发者在鸿蒙 Flutter 应用中灵活定制消息提示样式与显示时长,精准传递信息,增强交互体验与应用个性化。

ohos 端建立通道弹出系统层 toast

export default class FlutterToastPlugin implements FlutterPlugin {  private channel: MethodChannel | null = null;
getUniqueClassName(): string { return TAG; }
onAttachedToEngine(binding: FlutterPluginBinding): void { this.setupChannel(binding.getBinaryMessenger()); }
onDetachedFromEngine(binding: FlutterPluginBinding): void { this.teardownChannel(); }
private setupChannel(messenger: BinaryMessenger) { this.channel = new MethodChannel(messenger, "PonnamKarthik/fluttertoast", StandardMethodCodec.INSTANCE); let handler = new MethodCallHandlerImpl(); this.channel.setMethodCallHandler(handler); }
private teardownChannel() { this.channel?.setMethodCallHandler(null) this.channel = null }}
复制代码

接收 flutter 层传递过来的参数

export default class MethodCallHandlerImpl implements MethodCallHandler{  onMethodCall(call: MethodCall, result: MethodResult): void {    switch (call.method) {      case 'showToast': {        let msg: string = call.argument('msg');
/* dart层传入时间单位为 s, promptAction.showToast 传入时间单位为ms */ let time: number = call.argument('time') * 1000; let gravity: string = call.argument("gravity"); let bgcolor: number = call.argument("bgcolor"); let textcolor: number = call.argument("textcolor"); let textSize: number = call.argument("fontSize");
try { promptAction.showToast({message: msg, duration: time}); } catch (e) { Log.e(TAG, "Show toast err " + e); }
result.success(true); } case 'cancel': { result.success(true); } default: { result.notImplemented(); } } }}
复制代码

flutter 端代码


/// Toast Length/// Only for Android Platformenum Toast { /// Show Short toast for 1 sec LENGTH_SHORT,
/// Show Long toast for 5 sec LENGTH_LONG}
/// ToastGravity/// Used to define the position of the Toast on the screenenum ToastGravity { TOP, BOTTOM, CENTER, TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, CENTER_LEFT, CENTER_RIGHT, SNACKBAR, NONE }
/// Plugin to show a toast message on screen/// Only for android, ios and Web platformsclass Fluttertoast { /// [MethodChannel] used to communicate with the platform side. static const MethodChannel _channel = const MethodChannel('PonnamKarthik/fluttertoast');
/// Let say you have an active show /// Use this method to hide the toast immediately static Future<bool?> cancel() async { bool? res = await _channel.invokeMethod("cancel"); return res; }
/// Summons the platform's showToast which will display the message /// /// Wraps the platform's native Toast for android. /// Wraps the Plugin https://github.com/scalessec/Toast for iOS /// Wraps the https://github.com/apvarun/toastify-js for Web /// /// Parameter [msg] is required and all remaining are optional static Future<bool?> showToast({ required String msg, Toast? toastLength, int timeInSecForIosWeb = 1, double? fontSize, ToastGravity? gravity, Color? backgroundColor, Color? textColor, bool webShowClose = false, webBgColor = "linear-gradient(to right, #00b09b, #96c93d)", webPosition = "right", }) async { String toast = "short"; if (toastLength == Toast.LENGTH_LONG) { toast = "long"; }
String gravityToast = "bottom"; if (gravity == ToastGravity.TOP) { gravityToast = "top"; } else if (gravity == ToastGravity.CENTER) { gravityToast = "center"; } else { gravityToast = "bottom"; }
//lines from 78 to 97 have been changed in order to solve issue #328 if (backgroundColor == null) { backgroundColor = Colors.black; } if (textColor == null) { textColor = Colors.white; } final Map<String, dynamic> params = <String, dynamic>{ 'msg': msg, 'length': toast, 'time': timeInSecForIosWeb, 'gravity': gravityToast, 'bgcolor': backgroundColor.value, 'iosBgcolor': backgroundColor.value, 'textcolor': textColor.value, 'iosTextcolor': textColor.value, 'fontSize': fontSize, 'webShowClose': webShowClose, 'webBgColor': webBgColor, 'webPosition': webPosition };
bool? res = await _channel.invokeMethod('showToast', params); return res; }}
/// Signature for a function to buildCustom Toasttypedef PositionedToastBuilder = Widget Function(BuildContext context, Widget child);
/// Runs on dart side this has no interaction with the Native Side/// Works with all platforms just in two lines of code/// final fToast = FToast().init(context)/// fToast.showToast(child)///class FToast { BuildContext? context;
static final FToast _instance = FToast._internal();
/// Prmary Constructor for FToast factory FToast() { return _instance; }
/// Take users Context and saves to avariable FToast init(BuildContext context) { _instance.context = context; return _instance; }
FToast._internal();
OverlayEntry? _entry; List<_ToastEntry> _overlayQueue = []; Timer? _timer; Timer? _fadeTimer;
/// Internal function which handles the adding /// the overlay to the screen /// _showOverlay() { if (_overlayQueue.length == 0) { _entry = null; return; } if (context == null) { /// Need to clear queue removeQueuedCustomToasts(); throw ("Error: Context is null, Please call init(context) before showing toast."); }
/// To prevent exception "Looking up a deactivated widget's ancestor is unsafe." /// which can be thrown if context was unmounted (e.g. screen with given context was popped) /// TODO: revert this change when envoirment will be Flutter >= 3.7.0 // if (context?.mounted != true) { // if (kDebugMode) { // print( // 'FToast: Context was unmuted, can not show ${_overlayQueue.length} toast.'); // }
// /// Need to clear queue // removeQueuedCustomToasts(); // return; // Or maybe thrown error too // } var _overlay; try { _overlay = Overlay.of(context!); } catch (err) { removeQueuedCustomToasts(); throw ("""Error: Overlay is null. Please don't use top of the widget tree context (such as Navigator or MaterialApp) or create overlay manually in MaterialApp builder. More information - https://github.com/ponnamkarthik/FlutterToast/issues/393 - https://github.com/ponnamkarthik/FlutterToast/issues/234"""); } if (_overlay == null) { /// Need to clear queue removeQueuedCustomToasts(); throw ("""Error: Overlay is null. Please don't use top of the widget tree context (such as Navigator or MaterialApp) or create overlay manually in MaterialApp builder. More information - https://github.com/ponnamkarthik/FlutterToast/issues/393 - https://github.com/ponnamkarthik/FlutterToast/issues/234"""); }
/// Create entry only after all checks _ToastEntry _toastEntry = _overlayQueue.removeAt(0); _entry = _toastEntry.entry; _overlay.insert(_entry!);
_timer = Timer(_toastEntry.duration, () { _fadeTimer = Timer(_toastEntry.fadeDuration, () { removeCustomToast(); }); }); }
/// If any active toast present /// call removeCustomToast to hide the toast immediately removeCustomToast() { _timer?.cancel(); _fadeTimer?.cancel(); _timer = null; _fadeTimer = null; _entry?.remove(); _entry = null; _showOverlay(); }
/// FToast maintains a queue for every toast /// if we called showToast for 3 times we all to queue /// and show them one after another /// /// call removeCustomToast to hide the toast immediately removeQueuedCustomToasts() { _timer?.cancel(); _fadeTimer?.cancel(); _timer = null; _fadeTimer = null; _overlayQueue.clear(); _entry?.remove(); _entry = null; }
/// showToast accepts all the required paramenters and prepares the child /// calls _showOverlay to display toast /// /// Paramenter [child] is requried /// toastDuration default is 2 seconds /// fadeDuration default is 350 milliseconds void showToast({ required Widget child, PositionedToastBuilder? positionedToastBuilder, Duration toastDuration = const Duration(seconds: 2), ToastGravity? gravity, Duration fadeDuration = const Duration(milliseconds: 350), bool ignorePointer = false, bool isDismissable = false, }) { if (context == null) throw ("Error: Context is null, Please call init(context) before showing toast."); Widget newChild = _ToastStateFul( child, toastDuration, fadeDuration, ignorePointer, !isDismissable ? null : () { removeCustomToast(); });
/// Check for keyboard open /// If open will ignore the gravity bottom and change it to center if (gravity == ToastGravity.BOTTOM) { if (MediaQuery.of(context!).viewInsets.bottom != 0) { gravity = ToastGravity.CENTER; } }
OverlayEntry newEntry = OverlayEntry(builder: (context) { if (positionedToastBuilder != null) return positionedToastBuilder(context, newChild); return _getPostionWidgetBasedOnGravity(newChild, gravity); }); _overlayQueue.add(_ToastEntry(entry: newEntry, duration: toastDuration, fadeDuration: fadeDuration)); if (_timer == null) _showOverlay(); }
/// _getPostionWidgetBasedOnGravity generates [Positioned] [Widget] /// based on the gravity [ToastGravity] provided by the user in /// [showToast] _getPostionWidgetBasedOnGravity(Widget child, ToastGravity? gravity) { switch (gravity) { case ToastGravity.TOP: return Positioned(top: 100.0, left: 24.0, right: 24.0, child: child); case ToastGravity.TOP_LEFT: return Positioned(top: 100.0, left: 24.0, child: child); case ToastGravity.TOP_RIGHT: return Positioned(top: 100.0, right: 24.0, child: child); case ToastGravity.CENTER: return Positioned(top: 50.0, bottom: 50.0, left: 24.0, right: 24.0, child: child); case ToastGravity.CENTER_LEFT: return Positioned(top: 50.0, bottom: 50.0, left: 24.0, child: child); case ToastGravity.CENTER_RIGHT: return Positioned(top: 50.0, bottom: 50.0, right: 24.0, child: child); case ToastGravity.BOTTOM_LEFT: return Positioned(bottom: 50.0, left: 24.0, child: child); case ToastGravity.BOTTOM_RIGHT: return Positioned(bottom: 50.0, right: 24.0, child: child); case ToastGravity.SNACKBAR: return Positioned(bottom: MediaQuery.of(context!).viewInsets.bottom, left: 0, right: 0, child: child); case ToastGravity.NONE: return Positioned.fill(child: child); case ToastGravity.BOTTOM: default: return Positioned(bottom: 50.0, left: 24.0, right: 24.0, child: child); } }}
/// Simple builder method to create a [TransitionBuilder]/// and for the use in MaterialApp builder method// ignore: non_constant_identifier_namesTransitionBuilder FToastBuilder() { return (context, child) { return _FToastHolder( child: child!, ); };}
/// Simple StatelessWidget which holds the child/// and creates an [Overlay] to display the toast/// which returns the Directionality widget with [TextDirection.ltr]/// and [Overlay] widgetclass _FToastHolder extends StatelessWidget { const _FToastHolder({Key? key, required this.child}) : super(key: key);
final Widget child;
@override Widget build(BuildContext context) { final Overlay overlay = Overlay( initialEntries: <OverlayEntry>[ OverlayEntry( builder: (BuildContext ctx) { return child; }, ), ], );
return Directionality( textDirection: TextDirection.ltr, child: overlay, ); }}
/// internal class [_ToastEntry] which maintains/// each [OverlayEntry] and [Duration] for every toast user/// triggeredclass _ToastEntry { final OverlayEntry entry; final Duration duration; final Duration fadeDuration;
_ToastEntry({ required this.entry, required this.duration, required this.fadeDuration, });}
/// internal [StatefulWidget] which handles the show and hide/// animations for [FToast]class _ToastStateFul extends StatefulWidget { _ToastStateFul(this.child, this.duration, this.fadeDuration, this.ignorePointer, this.onDismiss, {Key? key}) : super(key: key);
final Widget child; final Duration duration; final Duration fadeDuration; final bool ignorePointer; final VoidCallback? onDismiss;
@override ToastStateFulState createState() => ToastStateFulState();}
/// State for [_ToastStateFul]class ToastStateFulState extends State<_ToastStateFul> with SingleTickerProviderStateMixin { /// Start the showing animations for the toast showIt() { _animationController!.forward(); }
/// Start the hidding animations for the toast hideIt() { _animationController!.reverse(); _timer?.cancel(); }
/// Controller to start and hide the animation AnimationController? _animationController; late Animation _fadeAnimation;
Timer? _timer;
@override void initState() { _animationController = AnimationController( vsync: this, duration: widget.fadeDuration, ); _fadeAnimation = CurvedAnimation(parent: _animationController!, curve: Curves.easeIn); super.initState();
showIt(); _timer = Timer(widget.duration, () { hideIt(); }); }
@override void deactivate() { _timer?.cancel(); _animationController!.stop(); super.deactivate(); }
@override void dispose() { _timer?.cancel(); _animationController?.dispose(); super.dispose(); }
@override Widget build(BuildContext context) { return GestureDetector( onTap: widget.onDismiss == null ? null : () => widget.onDismiss!(), behavior: HitTestBehavior.translucent, child: IgnorePointer( ignoring: widget.ignorePointer, child: FadeTransition( opacity: _fadeAnimation as Animation<double>, child: Center( child: Material( color: Colors.transparent, child: widget.child, ), ), ), ), ); }}
复制代码

flutter example 引用

fToast = FToast();fToast.init(context);
fToast.showToast( child: toast, gravity: ToastGravity.BOTTOM, toastDuration: Duration(seconds: 2), positionedToastBuilder: (context, child) { return Positioned( child: child, top: 16.0, left: 16.0, ); });
复制代码


用户头像

flfljh

关注

还未添加个人签名 2024-10-29 加入

还未添加个人简介

评论

发布
暂无评论
harmony_flutter 自定义toast_HarmonyOS_flfljh_InfoQ写作社区