Reputation: 17291
I am trying to make an overlay such as that shown here:
https://www.didierboelens.com/2018/06/how-to-create-a-toast-or-notifications-notion-of-overlay/
using OverlayEntry
.
import 'package:flutter/material.dart';
import 'dart:async';
class ShowNotificationIcon {
void show(BuildContext context) async {
OverlayState overlayState = Overlay.of(context);
OverlayEntry overlayEntry = new OverlayEntry(builder: _build);
overlayState.insert(overlayEntry);
}
Widget _build(BuildContext context){
return new Positioned(
top: 50.0,
left: 50.0,
child: new Material(
color: Colors.transparent,
child: new Icon(Icons.warning, color: Colors.purple),
),
);
}
}
Invoked with:
ShowNotificationIcon _icon = new ShowNotificationIcon();
_icon.show(context);
However, when I try to navigate to other screens, the overlay remains in the screen.
How do I show the overlay only in the screen it is being called and not in the others?
Just in case, this is what I had tried inside my stateful widget:
ShowNotificationIcon _icon = new ShowNotificationIcon();
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) {
_icon.show(context);
});
super.initState();
}
@override
void dispose() {
_icon.remove();
super.dispose();
}
Upvotes: 10
Views: 14848
Reputation: 51
you just need CompositedTransformTarget
, CompositedTransformFollower
and LinkLayout
.
Thanks to these, if the widget to which the overlay is attached disappears, the overlay will also disappear.
final key = GlobalKey();
OverlayEntry? floatingEntry ;
final layerLink = LayerLink();
void hideEntry(){
floatingEntry ?.remove();
}
void displayOverlay() {
final overlay = Overlay.of(context);
floatingEntry = OverlayEntry(builder: _buildFloatingButton);
overlay!.insert(floatingEntry!);
}
Widget _buildFloatingButton(BuildContext context) {
final render = key.currentContext!.findRenderObject() as RenderBox;
final offset = render.localToGlobal(Offset.zero);
final size = render.size;
return Positioned(
width: floatinSize,
child: CompositedTransformFollower(
link: layerLink,
offset: Offset(0.0, -size.height / 2),
showWhenUnlinked: false,
child: Container(
width: floatinSize,
height: floatinSize,
decoration: BoxDecoration(
color: Get.theme.scaffoldBackgroundColor,
shape: BoxShape.circle,
),
padding: const EdgeInsets.all(10.0),
child: Container(
decoration: BoxDecoration(
color: Get.theme.primaryColor,
shape: BoxShape.circle,
),
child: Icon(
FontAwesomeIcons.plus,
color: Get.theme.primaryColorDark,
),
),
),
),
);
}
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return Container(
width: size.width,
height: _navigationHeight,
color: Get.theme.bottomNavigationBarTheme.backgroundColor,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
CompositedTransformTarget(
link:linkLayout,
child:Container(key:key),
)
],
),
);
}
Upvotes: 2
Reputation: 276891
This is typically performed using RouteAware
+RouteObserver
.
RouteObserver
is an object that lets objects that implements RouteAware
react to some changes related to routing, which includes:
You can then use these two events to hide/show your overlay
First, you'll need a RouteObserver
.
This can be created as a global variable and needs to be passed to your Navigator
. In a MaterialApp
based app, it'll typically look like this:
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
void main() {
runApp(MaterialApp(
home: Container(),
navigatorObservers: [routeObserver],
));
}
Then, your widget that owns the OverlayEntry
can now implement RouteAware
like so:
class RouteAwareWidget extends StatefulWidget {
State<RouteAwareWidget> createState() => RouteAwareWidgetState();
}
// Implement RouteAware in a widget's state and subscribe it to the RouteObserver.
class RouteAwareWidgetState extends State<RouteAwareWidget> with RouteAware {
@override
void didChangeDependencies() {
super.didChangeDependencies();
// routeObserver is the global variable we created before
routeObserver.subscribe(this, ModalRoute.of(context) as PageRoute);
}
@override
void dispose() {
routeObserver.unsubscribe(this);
super.dispose();
}
@override
void didPush() {
// Route was pushed onto navigator and is now topmost route.
}
@override
void didPopNext() {
// Covering route was popped off the navigator.
}
@override
Widget build(BuildContext context) => Container();
}
At this point, you can use didPush
and didPopNext
to show/hide your OverlayEntry:
OverlayEntry myOverlay;
@override
void didPush() {
myOverlay.remove();
}
@override
void didPopNext() {
Overlay.of(context).insert(myOverlay);
}
Upvotes: 18
Reputation: 54365
I would like to suggest use package flushbar. https://github.com/AndreHaueisen/flushbar
As the package said: Use this package if you need more customization when notifying your user. For Android developers, it is made to substitute toasts and snackbars.
You can also set flushbarPosition to TOP or BOTTOM
Flushbar(
title: "Hey Ninja",
message: "Lorem Ipsum is simply dummy text of the printing and typesetting industry",
flushbarPosition: FlushbarPosition.TOP,
flushbarStyle: FlushbarStyle.FLOATING,
reverseAnimationCurve: Curves.decelerate,
forwardAnimationCurve: Curves.elasticOut,
backgroundColor: Colors.red,
boxShadows: [BoxShadow(color: Colors.blue[800], offset: Offset(0.0, 2.0), blurRadius: 3.0)],
backgroundGradient: LinearGradient(colors: [Colors.blueGrey, Colors.black]),
isDismissible: false,
duration: Duration(seconds: 4),
icon: Icon(
Icons.check,
color: Colors.greenAccent,
),
mainButton: FlatButton(
onPressed: () {},
child: Text(
"CLAP",
style: TextStyle(color: Colors.amber),
),
),
showProgressIndicator: true,
progressIndicatorBackgroundColor: Colors.blueGrey,
titleText: Text(
"Hello Hero",
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 20.0, color: Colors.yellow[600], fontFamily: "ShadowsIntoLightTwo"),
),
messageText: Text(
"You killed that giant monster in the city. Congratulations!",
style: TextStyle(fontSize: 18.0, color: Colors.green, fontFamily: "ShadowsIntoLightTwo"),
),
)..show(context);
Upvotes: 0