Toast is the most commonly used prompt component on Android. Its advantages are static call, global display, and it can be called anywhere you want without affecting the layout of the interface. The call is as simple as the call of Logger. However, Flutter does not provide us with the Toast interface. There are two ways to achieve Toast effect. One is to adopt Android/iOS native engineering, and the other is not to rely on Flutter. This paper adopts the second scheme for implementation. On the one hand, native code requires a large workload and threshold for dual-end development, which is not conducive to future style expansion. On the other hand, the Toast of pure Flutter implementation is very good and the custom style is also very convenient. Compared with RN, the rendering engine of Flutter is very powerful. Basically, the effects that can be achieved with Flutter are not recommended to be native. However, RN does not have its own rendering engine.
The core component to be used in this article is Overlay. This component provides the feature of dynamically inserting layouts into the rendering tree of Flutter, thus making it possible to insert toast on top of all components, including routes.
Create Flutter project
This series of Flutter blogs will start by creating a clean Flutter project. After creating the project, place a Button in the layout to trigger the Toast call. Code: omitted.
Insert Toast layout using Overlay
Since we want to implement a global static call, we will create a utility class and create the static method show in this class:
class Toast {
static show(BuildContext context, String msg) {
// The toast pop-up logic is implemented here}}Copy the code
Toast.show(context, “your message prompt “) is called in one of your callbacks; You can display toast without considering layout nesting.
Let’s insert a toast into the layout in the show method:
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, ), ); }); }}Copy the code
In the show method we insert an OverlayEntry using Overlay, and the OverlayEntry is responsible for building the layout, buildToastLayout method which is a normal layout building method, and with that we build a ToastView in Toast style, OverlayEntry is inserted into the top layer of the layout. A toasts-style prompt is displayed on the interface by calling toasts. show. However, the ToastView will not go away, it will stay in the interface, which is obviously not what we want.
Make Toast disappear automatically
We continue to modify the Toast to make it disappear automatically. Create a class called ToastView to control which ToastView is inserted each time:
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();
}
}
Copy the code
This encapsulates ToastView’s display and disappear controls. Then call it in Toast’s show method
class Toast {
static show(BuildContext context, String msg) {
var overlayState = Overlay.of(context);
OverlayEntry overlayEntry;
overlayEntry = new OverlayEntry(builder: (context) {
return buildToastLayout(msg);
});
vartoastView = ToastView(); toastView.overlayState = overlayState; toastView.overlayEntry = overlayEntry; toastView._show(); }... }Copy the code
Using the above method, we have implemented the global static call to Toast, inserted the global layout, and displayed the Toast after 3.5 seconds, but the Toast looks strange, yes, it does not have animation, now we add animation to the Toast.
Add animation to Toast
This Toast animation is an advanced application of Flutter, which involves features such as scaling, displacement, custom differentiator, AnimatedBuilder, etc. The core of this paper is about the use of Overlay and the encapsulation of ToastView. The use of animation would be too divergent if discussed here. I’ll talk about animation separately after space is limited, but I’ll do it based on your understanding of the animation system.
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),
);
});
vartoastView = 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, _) {
returnOpacity( 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; }}Copy the code
This is a very long piece of code, I didn’t want to post so much code on it, but the animation section is too long, and it’s too empty to post without the code, so I’ll just post it. In the first section, create 3 animations in the show method, the Toast display shift and fade animation, and the Toast disappear fade animation. Then hand the controller of these three animations to ToastView to control the animation playback. In the second section, we receive three animation controllers in ToastView and control the playback of the animation in the show and Dismiss methods. In the third section, create a custom Widget and animate it using three AnimatedBuilders and wrap the Toast layout in the show method. In paragraph 4, we define an animator. There are many animators available in Flutter, but none we need, so we define an animator that bounders once to control the offset animation of the ToastView.
So far, the Toast has met its basic style: global calls, animation pops up, and fades out after a 3.5 second delay.
Prevent successive calls from causing a toast stack
However, there is still a problem, because the semi-transparent black color of Toast style, if repeatedly called, multiple toasts will pop up at the same time and stack together, which will appear very black.
Now let’s do another processing. Before show, determine whether a Toast is displayed already. If so, dismiss it immediately.
static ToastView preToast;
static show(BuildContext context, Stringmsg) { preToast? .dismiss(); preToast =null; . preToast = toastView; toastView._show(); }... }Copy the code
That will do,? The. Operator has the same effect as Kotlin, the null pointer is safe and comfortable.
More dry goods move to my personal blog www.nightfarmer.top/