I created an overlay widget which can be displayed and removed after 3 seconds at the click of a button.
This is my view
OverlayState? overlayState;
OverlayEntry? entry;
bool _isVisible = false;
bool btnClicked = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'Overlay Example',
),
const SizedBox(height: 12),
ElevatedButton(
onPressed: () {
setState(() {
_isVisible = true;
});
displayOverlay(_isVisible);
Future.delayed(const Duration(seconds: 3), () {
setState(() {
_isVisible = false;
displayOverlay(_isVisible);
});
});
},
child: const Text('Press Me'))
],
),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
These are the functions that are used to control the overlay widget
void displayOverlay(bool isClicked) {
if (isClicked == true) {
WidgetsBinding.instance!.addPostFrameCallback((_) => showOverlay());
} else {
WidgetsBinding.instance!.addPostFrameCallback((_) => hideOverlay());
}
}
void hideOverlay() {
entry?.remove();
entry = null;
}
This is the widget that is Overlayed on the screen
void showOverlay() {
entry = OverlayEntry(
builder: (context) => AnimatedPositioned(
right: 0,
left: 0,
top: _isVisible ? 0 : -MediaQuery.of(context).size.height,
duration: const Duration(milliseconds: 500),
child: Container(
constraints: const BoxConstraints(maxHeight: 200),
child: Material(
color: const Color(0xff2D2B36).withOpacity(0.6),
shape: ShapeBorder.lerp(
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
0.5, // Interpolation value between 0.0 and 1.0
),
child: Column(
children: [],
),
),
),
),
);
final overlay = Overlay.of(context)!;
overlay?.insert(entry!);
}
I would like to know how I can make the overlay widget slide in from top of the screen then slide out after 3 seconds without using any flutter package.
You can handle showing and hiding animation of the banner inside its widget. Can use AnimationController
to control showing/hiding the banner and animate it.
FractionalTranslation
can be used to specify the vertical position from top to bottom based on the height of the banner itself (its values can range from -1 to 1) and Franctionally translates based on widget's size itself.
The idea is that we add the OverlayBanner
widget to the overlay and itself will show with animation and hide.
To remove the overlay entry when the animation is done can call a callback onBannerDismissed
which is passed from the parent and the parent widget that added the entry can remove it.
class OverlayWidget extends StatefulWidget {
const OverlayWidget({Key? key}) : super(key: key);
@override
State<OverlayWidget> createState() => _OverlayWidgetState();
}
class _OverlayWidgetState extends State<OverlayWidget> {
OverlayEntry? entry;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(''),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'Overlay Example',
),
const SizedBox(height: 12),
ElevatedButton(
onPressed: () {
displayOverlay();
},
child: const Text('Press Me'),
)
],
),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
void displayOverlay() {
WidgetsBinding.instance!.addPostFrameCallback((_) => showOverlay());
}
void hideOverlay() {
entry?.remove();
entry = null;
}
void showOverlay() {
entry = OverlayEntry(
builder: (context) => OverlayBanner(
onBannerDismissed: () {
hideOverlay();
},
),
);
final overlay = Overlay.of(context)!;
overlay.insert(entry!);
}
}
class OverlayBanner extends StatefulWidget {
const OverlayBanner({Key? key, this.onBannerDismissed}) : super(key: key);
final VoidCallback? onBannerDismissed;
@override
State<OverlayBanner> createState() => _OverlayBannerState();
}
class _OverlayBannerState extends State<OverlayBanner>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
static const Curve curve = Curves.easeOut;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 600),
);
_playAnimation();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
builder: (context, child) {
final double animationValue = curve.transform(_controller.value);
return FractionalTranslation(
translation: Offset(0, -(1 - animationValue)),
child: child,
);
},
animation: _controller,
child: SingleChildScrollView(
child: Container(
width: double.infinity,
height: 200,
color: Colors.purple,
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _playAnimation() async {
// fist will show banner with forward.
await _controller.forward();
// wait for 3 second and then play reverse animation to hide the banner
// Duration can be passed as parameter, banner will wait this much and then will dismiss
await Future<void>.delayed(const Duration(seconds: 3));
await _controller.reverse(from: 1);
// call onDismissedCallback so OverlayWidget can remove and clear the OverlayEntry.
widget.onBannerDismissed?.call();
}
}
PS: No need to setState
as well, can use AnimatedBuilder
and pass a child to it which is the banner content, this is a more optimal way because the banner content (purple container) won't rebuild by every animation tick.