Toast在Android上是最经常使用的提示组件了,它的优点在于静态调用、全局显示,能够在任意你想要的地方调用他而丝绝不影响界面的布局,调用简单程度与Logger的调用不相上下。
然而在Flutter中并无给咱们提供Toast的接口,想要实现Toast的效果有两种途径,一种是接Android/iOS原生工程,第二种是不依托于使用Flutter来实现。
本篇选用第二种方案来实现,接原生代码一方面要求双端开发工做量和门槛都较大,并且不利于之后的样式扩展,二是纯Flutter实现的Toast确实效果很是好,自定义样式也很是的方便。使用Flutter相对于RN来讲,Flutter的渲染引擎是很是强大的,基本上能用Flutter实现的效果都不建议接原生,而RN则没有本身的渲染引擎,性能的限制形成RN须要频繁的接入原生模块,这也是我倾心Flutter的缘由。安全
本篇要用的核心组件是Overlay
,这个组件提供了动态的在Flutter的渲染树上插入布局的特性,从而让咱们有了在包括路由在内的全部组件的上层插入toast的可能性。less
本品系列的Flutter博客都会以建立纯净的Flutter工程开篇,建立工程后,放一个Button在布局中,便于触发Toast调用。
代码:略。async
由于咱们要实现全局的静态调用,因此这里先建立一个工具类,并在这个类中建立静态方法show:ide
class Toast {
static show(BuildContext context, String msg) {
//这里实现toast的弹出逻辑
}
}
复制代码
这是一种很常见的静态调用方式,是须要在你的某个回调中调用Toast.show(context, "你的消息提示");便可完成toast的显示,而不用考虑布局嵌套问题。工具
下面咱们就在show方法中向布局中插入一个toast:布局
class Toast {
static show(BuildContext context, String msg) {
var overlayState = Overlay.of(context);
OverlayEntry overlayEntry;
overlayEntry = new OverlayEntry(builder: (context) {
return buildToastLayout(msg);
});
overlayState.insert(overlayEntry);
}
static LayoutBuilder buildToastLayout(String msg) {
return LayoutBuilder(builder: (context, constraints) {
return IgnorePointer(
ignoring: true,
child: Container(
child: Material(
color: Colors.white.withOpacity(0),
child: Container(
child: Container(
child: Text(
"${msg}",
style: TextStyle(color: Colors.white),
),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.6),
borderRadius: BorderRadius.all(
Radius.circular(5),
),
),
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
),
margin: EdgeInsets.only(
bottom: constraints.biggest.height * 0.15,
left: constraints.biggest.width * 0.2,
right: constraints.biggest.width * 0.2,
),
),
),
alignment: Alignment.bottomCenter,
),
);
});
}
}
复制代码
在show方法中使用Overlay插入了一个OverlayEntry,而OverlayEntry负责构建布局,buildToastLayout方法这是一个正常的布局构建方法,经过这个方法咱们构建了一个Toast样式的ToastView,并经过OverlayEntry插入到了整个布局的最上层。
这时候经过调用Toast.show方法就能在界面上看到一个Toast样式的提示了。
可是,这个ToastView是不会消失的,它会一直呆在界面上,这显然不是咱们想要的。性能
咱们继续改造这个Toast,让它可以自动消失。
建立一个叫作ToastView的类,便于控制每次插入的ToastView:动画
class ToastView {
OverlayEntry overlayEntry;
OverlayState overlayState;
bool dismissed = false;
_show() async {
overlayState.insert(overlayEntry);
await Future.delayed(Duration(milliseconds: 3500));
this.dismiss();
}
dismiss() async {
if (dismissed) {
return;
}
this.dismissed = true;
overlayEntry?.remove();
}
}
复制代码
这样,就把ToastView的显示和消失的控制封装起来了。而后在Toast的show方法中对他进行调用ui
class Toast {
static show(BuildContext context, String msg) {
var overlayState = Overlay.of(context);
OverlayEntry overlayEntry;
overlayEntry = new OverlayEntry(builder: (context) {
return buildToastLayout(msg);
});
var toastView = ToastView();
toastView.overlayState = overlayState;
toastView.overlayEntry = overlayEntry;
toastView._show();
}
...
}
复制代码
经过上面的方法,已经实现了Toast的全局静态调用,并插入全局布局,并在显示3.5秒后自动消失的Toast,可是这个toast好像怪怪的,没错,他没有动画,下面来给这个toast增长动画。this
这个Toast的动画算是Flutter的高级应用了,它涉及到了缩放,位移,自定义差值器,AnimatedBuilder等特性,本篇的核心在介绍Overlay的使用和ToastView的封装,关于动画的使用若是在这里讲就发散的太多了,篇幅限制之后单独来说动画吧,这里以你对动画系统了解的前提来说解。
class Toast {
static show(BuildContext context, String msg) {
var overlayState = Overlay.of(context);
var controllerShowAnim = new AnimationController(
vsync: overlayState,
duration: Duration(milliseconds: 250),
);
var controllerShowOffset = new AnimationController(
vsync: overlayState,
duration: Duration(milliseconds: 350),
);
var controllerHide = new AnimationController(
vsync: overlayState,
duration: Duration(milliseconds: 250),
);
var opacityAnim1 =
new Tween(begin: 0.0, end: 1.0).animate(controllerShowAnim);
var controllerCurvedShowOffset = new CurvedAnimation(
parent: controllerShowOffset, curve: _BounceOutCurve._());
var offsetAnim =
new Tween(begin: 30.0, end: 0.0).animate(controllerCurvedShowOffset);
var opacityAnim2 = new Tween(begin: 1.0, end: 0.0).animate(controllerHide);
OverlayEntry overlayEntry;
overlayEntry = new OverlayEntry(builder: (context) {
return ToastWidget(
opacityAnim1: opacityAnim1,
opacityAnim2: opacityAnim2,
offsetAnim: offsetAnim,
child: buildToastLayout(msg),
);
});
var toastView = ToastView();
toastView.overlayEntry = overlayEntry;
toastView.controllerShowAnim = controllerShowAnim;
toastView.controllerShowOffset = controllerShowOffset;
toastView.controllerHide = controllerHide;
toastView.overlayState = overlayState;
preToast = toastView;
toastView._show();
}
...
}
class ToastView {
OverlayEntry overlayEntry;
AnimationController controllerShowAnim;
AnimationController controllerShowOffset;
AnimationController controllerHide;
OverlayState overlayState;
bool dismissed = false;
_show() async {
overlayState.insert(overlayEntry);
controllerShowAnim.forward();
controllerShowOffset.forward();
await Future.delayed(Duration(milliseconds: 3500));
this.dismiss();
}
dismiss() async {
if (dismissed) {
return;
}
this.dismissed = true;
controllerHide.forward();
await Future.delayed(Duration(milliseconds: 250));
overlayEntry?.remove();
}
}
class ToastWidget extends StatelessWidget {
final Widget child;
final Animation<double> opacityAnim1;
final Animation<double> opacityAnim2;
final Animation<double> offsetAnim;
ToastWidget(
{this.child, this.offsetAnim, this.opacityAnim1, this.opacityAnim2});
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: opacityAnim1,
child: child,
builder: (context, child_to_build) {
return Opacity(
opacity: opacityAnim1.value,
child: AnimatedBuilder(
animation: offsetAnim,
builder: (context, _) {
return Transform.translate(
offset: Offset(0, offsetAnim.value),
child: AnimatedBuilder(
animation: opacityAnim2,
builder: (context, _) {
return Opacity(
opacity: opacityAnim2.value,
child: child_to_build,
);
},
),
);
},
),
);
},
);
}
}
class _BounceOutCurve extends Curve {
const _BounceOutCurve._();
@override
double transform(double t) {
t -= 1.0;
return t * t * ((2 + 1) * t + 2) + 1.0;
}
}
复制代码
这是段很是长的代码,原本是不想往上面贴这么多代码的,可是动画这块儿讲的话篇幅又太长,不贴代码的话讲起来又太空洞,只能贴了,大概说一下。
上面代码分为四段:
第一段,在show方法中建立3个动画,Toast显示的位移和渐显动画,Toast消失的渐隐动画,而后把这三个动画的controller交给ToastView来控制动画播放。
第二段,在ToastView中接收三个动画controller,并在show和dismiss方法中控制动画的播放。
第三段,建立一个自定义Widget,并使用三个AnimatedBuilder来实现动画,并在show方法中把Toast的布局包裹起来。
第四段,定义了一个动画差值器,Flutter中提供了不少动画差值器,可是并无咱们须要的,因此这里定义一个弹跳一次后回弹的动画差值器用来控制ToastView的偏移动画效果。
到目前为止,这个Toast已经知足了最基本的样式,全局调用,动画弹出,延迟3.5秒后自动渐隐消失。
可是还存在一个问题,由于Toast的样式的半透明的黑色,若是连续调用屡次的话,会有多个Toast同时弹出,并堆叠在一块儿,会显得很是的黑。
下面再作一个处理,在show以前,判断是否已经有一个Toast在显示了,若是有,即刻把它dismiss了。
static ToastView preToast;
static show(BuildContext context, String msg) {
preToast?.dismiss();
preToast = null;
...
preToast = toastView;
toastView._show();
}
...
}
复制代码
这样就能够了,?.
操做符和kotlin的效果是同样的,空指针安全,很舒服。
更多干货移步个人我的博客 www.nightfarmer.top/