【Flutter入门到进阶】Flutter基础篇---按钮、弹框、页面跳转

1 Flutter 按钮组件

1.1 按钮组件的属性

1.1.1 onPressed

        必填参数,按下按钮时触发的回调,接收一个方法,传null表示按钮禁用,会显示 禁用相关样式

1.1.2 child

        子组件

1.1.3 style

        通过ButtonStyle装饰

        foregroundColor        Color         文本颜色

        backgroundColor       Color         按钮的颜色

        shadowColor             Color         阴影颜色

        elevation                   double        阴影的范围,值越大阴影范围越大

        padding                    内边距

        shape                       设置按钮的形状         

                                         shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)))

        side                          设置边框

                                         MaterialStateProperty.all(BorderSide(width:1,color: Colors.red))

1.2 按钮类型

1.2.1 ElevatedButton

        ElevatedButton  即"凸起"按钮,它默认带有阴影和灰色背景。按下后,阴影会变大

    ElevatedButton( onPressed: () {}, child: const Text("普通按钮") )

1.2.2 TextButton

        TextButton 即文本按钮,默认背景透明并不带阴影。按下后,会有背景色

    TextButton( child: Text("文本按钮"), onPressed: () {}, )

1.2.3 OutlinedButton

        OutlineButton 默认有一个边框,不带阴影且背景透明。按下后,边框颜色会变亮、同时出现背景和 阴影

    OutlinedButton( child: Text("边框按钮"), onPressed: () {}, )

1.2.4 IconButton

IconButton 是一个可点击的Icon,不包括文字,默认没有背景,点击后会出现背景

    IconButton( icon: Icon(Icons.thumb_up), onPressed: () {}, )

1.2.5 带图标的按钮

ElevatedButton、TextButton、OutlineButton 都有一个icon构造函数,通过它可以轻松创建
带图标的按钮

ElevatedButton.icon(icon: Icon(Icons.send), label: Text("发送"), onPressed: _onPressed,), 
OutlineButton.icon( icon: Icon(Icons.add), label: Text("添加"), onPressed: _onPressed, ), 
TextButton.icon( icon: Icon(Icons.info), label: Text("详情"), onPressed: _onPressed, ),

1.3示例

1.3.1 修改按钮的宽度高度

依赖于SizeBox进行处理

SizedBox( height: 80, width: 200, 
    child: ElevatedButton(
        style:ButtonStyle(
            backgroundColor:MaterialStateProperty.all(Colors.red),  
            foregroundColor:MaterialStateProperty.all(Colors.black)), 
        onPressed: () { }, 
        child: const Text('宽度高度'), 
    ), 
)

1.3.2 自适应按钮

依赖于Expanded进行处理

示例

Row( mainAxisAlignment: MainAxisAlignment.center, 
    children: <Widget>[ Expanded( 
        child: Container( height: 60, margin: const EdgeInsets.all(10), 
        child: ElevatedButton( 
            child: const Text('自适应按钮'), onPressed: () { print("自适应按钮"); }, ),), 
        ) 
    ], 
),

1.3.3 配置圆形圆角按钮

圆角按钮

ElevatedButton(style: ButtonStyle( 
        backgroundColor:MaterialStateProperty.all(Colors.blue),  
        foregroundColor: MaterialStateProperty.all(Colors.white),     
        elevation: MaterialStateProperty.all(20), 
        shape: MaterialStateProperty.all( 
            RoundedRectangleBorder( 
                borderRadius: BorderRadius.circular(10))),
            ), 
        onPressed: () { print("圆角按钮"); }, 
        child: const Text('圆角') 
)

原型按钮

Container( height: 80, 
    child: ElevatedButton( 
        style: ButtonStyle( 
            backgroundColor: MaterialStateProperty.all(Colors.blue), 
            foregroundColor: MaterialStateProperty.all(Colors.white), 
            elevation: MaterialStateProperty.all(20),   
            shape: MaterialStateProperty.all( 
                CircleBorder(side: BorderSide(color: Colors.white)),)), 
        onPressed: () { print("圆形按钮"); }, 
        child: const Text('圆形按钮')
    ), 
)

1.3.4 修改OutlinedButton边框

Row( mainAxisAlignment: MainAxisAlignment.center, 
    children: <Widget>[ Expanded( 
        child: Container( margin: EdgeInsets.all(20), height: 50, 
        child: OutlinedButton( 
            style: ButtonStyle( 
                foregroundColor: MaterialStateProperty.all(Colors.black), 
                side: MaterialStateProperty.all( 
                    const BorderSide(width: 1, color: Colors.red)
                )
            ), 
        onPressed: () {}, 
        child: const Text("注册 配置边框")), ), ) 
    ], 
)

2 Flutter事件处理

2.1 说明

        在Flutter中,手势有两个不同的层次:

        第一层:原始指针事件(Pointer Events):描述了屏幕上由触摸板、鼠标、指示笔等触发的指针的位置和移动。

        第二层:手势识别(Gesture Detector):这个是在原始事件上的一种封装

2.2 指针事件Pointer

2.2.1 说明

        Pointer 代表的是人机界面交互的原始数据。一共有四种指针事件:

        1)PointerDownEvent 指针在特定位置与屏幕接触 

        2)PointerMoveEvent 指针从屏幕的一个位置移动到另外一个位置 

        3)PointerUpEvent 指针与屏幕停止接触 

        4)PointerCancelEvent 指针因为一些特殊情况被取消

原始指针事件使用Listener来监听:

  Widget build(BuildContext context) {
    return Center(
      child: Listener(
        child: Container(
          width: 200,
          height: 200,
          color: Colors.red,
        ),
        onPointerDown: (event) => print("手指按下:$event"),
        onPointerMove: (event) => print("手指移动:$event"),
        onPointerUp: (event) => print("手指抬起:$event"),
      ),
    );
  }

事件对象event,可以从事件对象获取需要的信息比如点击位置,

        event.position:在屏幕上的位置

        event.localPosition:在组件内的位置

2.2.2 示例

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(title: const Text("Flutter App")),
        body: MyHomePage(),
      ),
    );
  }
} //事件基本应用,指针事件(Touch)

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Listener(
        child: Container(
          width: 200,
          height: 200,
          color: Colors.red,
        ),
        onPointerDown: (event) => print("手指按下:$event"),
        onPointerMove: (event) => print("手指移动:$event"),
        onPointerUp: (event) => print("手指抬起:$event"),
      ),
    );
  }
}

2.3 手势识别(Gesture Detector)

2.3.1 说明

GestureDetector封装了点击、双击、滑动等大量功能,使开发者可以快速使用这些基础性功能

GestureDetector({     
    Key key,     
    this.child,     
    this.onTapDown,     
    this.onTapUp,     
    this.onTap,     
    this.onTapCancel,     
    this.onDoubleTap,     
    this.onLongPress,     
    this.onLongPressUp,     
    this.onLongPressDragStart,     
    this.onLongPressDragUpdate,     
    this.onLongPressDragUp,     
    this.onVerticalDragDown,     
    this.onVerticalDragStart,     
    this.onVerticalDragUpdate,     
    this.onVerticalDragEnd,     
    this.onVerticalDragCancel,     
    this.onHorizontalDragDown,     
    this.onHorizontalDragStart,     
    this.onHorizontalDragUpdate,     
    this.onHorizontalDragEnd,     
    this.onHorizontalDragCancel,     
    this.onForcePressStart,     
    this.onForcePressPeak,     
    this.onForcePressUpdate,     
    this.onForcePressEnd,     
    this.onPanDown,     
    this.onPanStart,     
    this.onPanUpdate,     
    this.onPanEnd,     
    this.onPanCancel,     
    this.onScaleStart,     
    this.onScaleUpdate,     
    this.onScaleEnd,     
    this.behavior,     
    this.excludeFromSemantics = false,     
    this.dragStartBehavior = DragStartBehavior.down,   
})

Gesture种类非常多:

        点击: 

                onTapDown:用户发生手指按下的操作

                onTapUp:用户发生手指抬起的操作

                onTap:用户点击事件完成

                onTapCancel:事件按下过程中被取消

        双击:

                onDoubleTap:快速点击了两次

        长按:

                onLongPress:在屏幕上保持了一段时间

        纵向拖拽:

                onVerticalDragStart:指针和屏幕产生接触并可能开始纵向移动;

                onVerticalDragUpdate:指针和屏幕产生接触,在纵向上发生移动并保持移动;

                onVerticalDragEnd:指针和屏幕产生接触结束;

        横线拖拽:

                onHorizontalDragStart:指针和屏幕产生接触并可能开始横向移动;

                onHorizontalDragUpdate:指针和屏幕产生接触,在横向上发生移动并保持移动;

                onHorizontalDragEnd:指针和屏幕产生接触结束;

                globalPosition用于获取相对于屏幕的位置信息 localPosition用于获取相对于当前Widget的位置信息

2.3.2 示例

可以通过Stack布局来解决手势冲突,避免嵌套。

import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(title: const Text("Flutter App")),
        body: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  _onPointerDown(PointerDownEvent event) {
    if (kDebugMode) {
      print("_onPointerDown:$event");
    }
  }

  _onPointerMove(PointerMoveEvent event) {
    if (kDebugMode) {
      print("_onPointerMove:$event");
    }
  }

  _onPointerUp(PointerUpEvent event) {
    if (kDebugMode) {
      print("_onPointerUp:$event");
    }
  }

  _onPointerEnter(PointerEnterEvent event) {
    if (kDebugMode) {
      print("_onPointerEnter:$event");
    }
  }

  _onPointerExit(PointerCancelEvent event) {
    if (kDebugMode) {
      print("_onPointerExit:$event");
    }
  }

  _onPointerHover(PointerHoverEvent event) {
    if (kDebugMode) {
      print("_onPointerHover:$event");
    }
  }

  _onPointerDown1(PointerDownEvent event) {
    if (kDebugMode) {
      print("_onPointerDown1:$event");
    }
  }

  _onPointerMove1(PointerMoveEvent event) {
    if (kDebugMode) {
      print("_onPointerMove1:$event");
    }
  }

  _onPointerUp1(PointerUpEvent event) {
    if (kDebugMode) {
      print("_onPointerUp1:$event");
    }
  }

  @override
  Widget build(BuildContext context) {
    return Listener(
        onPointerDown: _onPointerDown,
        onPointerMove: _onPointerMove,
        onPointerUp: _onPointerUp,
        onPointerCancel: _onPointerExit,
        onPointerHover: _onPointerHover,
        child: Stack(
          children: <Widget>[
            GestureDetector(
              //监听点击事件
              onTap: () => {print("点击事件")},
              //监听横向滑动事件
              onVerticalDragUpdate: (DragUpdateDetails details) =>
                  {print("横向滑动:$details")},
              child: Listener(
                  onPointerDown: _onPointerDown1,
                  onPointerMove: _onPointerMove1,
                  onPointerUp: _onPointerUp1,
                  child: Center(
                    child: Container(
                      color: Colors.red,
                      width: 200.0,
                      height: 100.0,
                    ),
                  )),
            ),
          ],
        ));
  }
}

2.4 事件拦截应用

2.4.1 说明

        Listener可以监听最原始的事件,通过该Widget可以拿到事件的相关信息。通过运行下面代码可以发现第二个Listener中的相关信息都不会被打印。但如果将absorbing的值改为false。则第二个Listener的信息都会打印出来。

        IgnorePointer与AbsorbPointer的用法基本一致。

2.4.2 示例

class MyHomePage extends StatelessWidget {
  _onPointerDown(PointerDownEvent event) {
    print("_onPointerDown:$event");
  }

  _onPointerMove(PointerMoveEvent event) {
    print("_onPointerMove:$event");
  }

  _onPointerUp(PointerUpEvent event) {
    print("_onPointerUp:$event");
  }

  _onPointerDown1(PointerDownEvent event) {
    print("_onPointerDown1:$event");
  }

  _onPointerMove1(PointerMoveEvent event) {
    print("_onPointerMove1:$event");
  }

  _onPointerUp1(PointerUpEvent event) {
    print("_onPointerUp1:$event");
  }

  @override
  Widget build(BuildContext context) {
    return Listener(
        onPointerDown: _onPointerDown,
        onPointerMove: _onPointerMove,
        onPointerUp: _onPointerUp,
        child: AbsorbPointer(
            //该值为false则下面的Listener不会打印任何信息
            absorbing: false,
            child: Listener(
              onPointerDown: _onPointerDown1,
              onPointerMove: _onPointerMove1,
              onPointerUp: _onPointerUp1,
              child: Container(
                color: Colors.red,
                width: 200.0,
                height: 100.0,
              ),
            )));
  }
}

3 页面跳转与路由

3.1 说明

        在安卓里,界面(Activity)的跳转是通过startActivity来完成。而在Flutter里,把界面称作为一个个Route,他们的跳转是通过一个叫做路由管理(Navigator)的工具,负责页面之间的跳转,包括参数的传递接受和返回。Navigator的方法总的来说就两个,push(进栈),pop(出栈),跟Activity一样也会生成一个存放页面的栈。

        Flutter中的路由通俗的讲就是页面跳转。在Flutter中通过Navigator组件管理路由导航。 并提供了管理堆栈的方法。如:  Navigator.push和Navigator.pop

Flutter中给我们提供了两种配置路由跳转的方式: 

        1、基本路由 

        2、命名路由

3.2 普通路由

3.2.1 普通跳转:Navigator.push

TextButton(
    child: const Text("通过实例跳转"),   
    onPressed: ()  {
        Navigator.push(context,MaterialPageRoute(
            builder: (context) {       
                return const TwoPageRoute();
            }
        ));   
    }, 
)

注意事项

提示1: 关于MaterialPageRoute可以理解为对路由界面的一个封装,继承于PageRoute,定义了页面之前跳转的动画,可以针对不同平台,实现与平台页面切换动画风格一致的路由切换动画。

提示2: 在Flutter中,如果代码是固定的,也就是写死的,编译器会建议加上const。如果你是通过值传递的,那自然就不能加const了

3.2.2 参数跳转

界面的跳转中,经常都需要传递参数。下面我们传递一个String参数和一个Bean类

class UserBean{
   String name;
   int age;
   UserBean(this.name, this.age);
}

class TwoPageRoute extends StatelessWidget {
  const TwoPageRoute({Key? key,
    required this.text, // 接收一个text参数
    required this.userBean //接收一个userBean类
  }) : super(key: key);
  final String text;
  final UserBean userBean;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("第二个页面"),
      ),
      body: Center(
        child: Column(
          children: [
            Text("接受text值:$text"),
            Text("接受userBean值:${userBean.toString()}"),
          ],
        ),
      ),
    );
  }
}

TextButton(
  child: const Text("通过实例跳转"),
  onPressed: ()  {
    Navigator.push(context,MaterialPageRoute(
        builder: (context) {
            return TwoPageRoute(text: "我是第一个页面", userBean: UserBean("小小虫", 18));
    }));
  },
)

3.2.3 接收参数返回

在Flutter,接受页面的返回参数,相比安卓会比较方便,如下

示例

TextButton(
    child: const Text("通过实例跳转"),
    onPressed: () async { 
        var result = await Navigator.push(context,
            MaterialPageRoute(builder: (context) {
                return TwoPageRoute(text: "我是第一个页面", userBean: UserBean("小小虫", 18));     
            })
        );     
        print("路由返回值: $result");   
    }, 
)

修改TwoPageRoute页面,添加一个按钮返回参数

TextButton(
    onPressed: () {Navigator.pop(context, "我是返回值");},
    child: const Text("关闭页面并返回值") 
)

注意事项

        注意: 关键词async和await,不然不会等待页面结束接受返回值。

3.3 路由命名

        在Flutter中,页面的跳转除了通过上面的方法外,还有一种通过路由命名跳转,类似于安卓的隐式跳转

android

<action android:name="com.example.SplashActivity" />

路由注册

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
      //注册路由表
      routes: {
        "custom_page": (context) => const CustomRoute(),
      },
    );
  }
}

跳转示例

TextButton(
    child: const Text("通过自定义命名路由跳转"),
    onPressed: () async {
        var result = await Navigator.pushNamed(
            context, 
            "custom_page",         
            arguments: "hello,我是通过自定义命名路由跳转");     
        print("通过自定义命名路由跳转路由返回值: $result");   
    }, 
)

在build中获取参数

@override 
Widget build(BuildContext context) {   
    //获取路由参数   
    var args=ModalRoute.of(context)?.settings.arguments;   
}

3.4 路由钩子

3.4.1 说明

        所谓的路由钩子,其实就是一些路由相关的监听方法

例如

        onGenerateRoute:通过命名路由打开是会被调用。注意!重点!重点!是对应的name没有在routes中有映射关系,那么就会执行onGenerateRoute钩子函数,也就是没有在routes中注册路由。

        onUnknowRoute:在打开一个不存在的命名路由时会被调用

        navigatorObservers:监听所有路由跳转动作

3.4.2 onGenerateRoute使用,在MaterialApp中添加onGenerateRoute

@override
Widget build(BuildContext context) {
  return MaterialApp(onGenerateRoute: (RouteSettings settings) {
    var routeName = settings.name;
    var isLogin = true;
    if (routeName == "two_page_route" && isLogin) {
      return MaterialPageRoute(builder: (context) {
        return TwoPageRoute(text: "我是第一个页面", userBean: UserBean("小小虫", 18));
      });
    } else { //登录逻辑。。。         
    }
  },);
}}

3.4.3 navigatorObservers使用,在MaterialApp中添加navigatorObservers

@override
Widget build(BuildContext context) {
  return MaterialApp( //监听所有路由跳转动作,       
    navigatorObservers: [ MyNavigator()],);
}

import 'package:flutter/widgets.dart';

///导航栈的变化监听 
class MyNavigator extends NavigatorObserver {
  @override void didPush(Route route, Route? previousRoute) {
    super.didPush(route,
        previousRoute); //监听每个路由进栈   
  }

  @override void didPop(Route route, Route? previousRoute) {
    super.didPop(route, previousRoute); //监听每个路由出栈   
  }
}

4 Flutter Dialog

4.1 AlertDialog

4.1.1 说明

        最简单的方案是利用AlertDialog组件构建一个弹框

示例

void alertDialog(BuildContext context) async {
  var result = await showDialog(
      barrierDismissible: false, //表示点击灰色背景的时候是否消失弹出框
      context: context,
      builder: (context) {
        return AlertDialog(
          title: const Text("提示信息!"),
          content: const Text("您确定要删除吗"),
          actions: [
            TextButton(
                onPressed: () {
                  print("ok");
                  Navigator.of(context).pop("ok"); //点击按钮让AlertDialog消失
                },
                child: const Text("确定")),
            TextButton(
                onPressed: () {
                  print("cancel");
                  Navigator.of(context).pop("取消");
                },
                child: const Text("取消"))
          ],
        );
      });

  print("-----------");
  print(result);
}

4.2 SimpleDialog

        通过SimpleDialog构建一个选择框

示例

void simpleDialog(BuildContext context) async {
  var result = await showDialog(
      barrierDismissible: false, //表示点击灰色背景的时候是否消失弹出框
      context: context,
      builder: (context) {
        return SimpleDialog(
          title: const Text("请选择语言"),
          children: [
            SimpleDialogOption(
              onPressed: () {
                print("汉语");
                Navigator.pop(context, "汉语");
              },
              child: const Text("汉语"),
            ),
            const Divider(),
            SimpleDialogOption(
              onPressed: () {
                print("英语");
                Navigator.pop(context, "英语");
              },
              child: const Text("英语"),
            ),
            const Divider(),
            SimpleDialogOption(
              onPressed: () {
                print("日语");
                Navigator.pop(context, "日语");
              },
              child: const Text("日语"),
            ),
            const Divider(),
          ],
        );
      });
  print("-----------");
  print(result);
}

4.3 自定义Flutter Dialog 

示例

// 自定义dialog

class MyDialog extends Dialog {
  String title;
  String content;
  Function()? onClosed;

  MyDialog(
      {Key? key,
      required this.title,
      required this.onClosed,
      this.content = ""})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Material(
      type: MaterialType.transparency,
      child: Center(
          child: Container(
        height: 300,
        width: 300,
        color: Colors.white,
        child: Column(
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(10),
              child: Stack(
                children: <Widget>[
                  Align(
                    alignment: Alignment.center,
                    child: Text(title),
                  ),
                  Align(
                    alignment: Alignment.centerRight,
                    child: InkWell(
                      onTap: onClosed,
                      child: const Icon(Icons.close),
                    ),
                  )
                ],
              ),
            ),
            const Divider(),
            Container(
              padding: const EdgeInsets.all(10),
              width: double.infinity,
              child: Text(content, textAlign: TextAlign.left),
            )
          ],
        ),
      )),
    );
  }
}

4.4 Flutter Toast

4.4.1 第三方框架

        https://pub.dev/packages/fluttertoast

使用示例

Fluttertoast.showToast(
    msg: "提示信息",                   
    toastLength: Toast.LENGTH_LONG,   //值针对 android 平台                   
    timeInSecForIosWeb: 1,  //提示时间 针对ios 和 web                   
    backgroundColor: Colors.black26, //背景颜色                   
    textColor: Colors.white,   //文本颜色                   
    fontSize: 16.0  //文本字体大小               
);

4.4.2 自定义

说明

        所谓toast框其实就是在图层的最上面一层插入一个显示图层,在Flutter中利用OverLayEntry构建图层,然后通过Overlay进行插入

示例

import 'package:flutter/material.dart';

class Toast {
  static var _lastMsg;
  static int _lastShowms = 0;

  static Future _show(BuildContext context, String msg, int duration) {
    OverlayEntry entry = OverlayEntry(
        builder: (BuildContext context) => Positioned(
              //top值,可以改变这个值来改变toast在屏幕中的位置
              top: MediaQuery.of(context).size.height.toDouble() * 0.5,
              child: Container(
                  alignment: Alignment.center,
                  width: MediaQuery.of(context).size.width,
                  child: Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 10.0),
                    child: _buildToastWidget(context, msg),
                  )),
            ));

    ///往Overlay中插入插入OverlayEntry
    Overlay.of(context)?.insert(entry);

    ///两秒后,移除Toast
    Future result =
        Future.delayed(Duration(milliseconds: duration)).then((value) {
      entry.remove();
    });
    return result;
  } //toast UI绘制

  static _buildToastWidget(context, String msg) {
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Container(
            alignment: Alignment.center,
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(12.0),
              shape: BoxShape.rectangle,
              color: Colors.black45,
            ),
            child: Padding(
              padding: const EdgeInsets.all(10),
              child: Text(msg,
                  style: const TextStyle(
                      color: Colors.white,
                      decoration: TextDecoration.none,
                      fontSize: 14)),
            ))
      ],
    );
  } //处理重复多次点击

  static void _handleDuplicateAndShow(
      String message, BuildContext context, int duration) {
    if (_lastMsg == message) {
      //相同信息内容
      int currentms = DateTime.now().millisecondsSinceEpoch;
      int interval = currentms - _lastShowms;
      if (interval > duration) {
        //大于时间间隔 可以显示
        _show(context, message, duration);
        _lastShowms = currentms;
      }
    } else {
      _show(context, message, duration);
      _lastMsg = message;
    }
  }

  /// 提示
  static void showInfo(String message,
      {required BuildContext context, int duration = 2000}) {
    _handleDuplicateAndShow(message, context, duration);
  }
}

猜你喜欢

转载自blog.csdn.net/u010687761/article/details/129290809