I'd like to display a tutorial overlay on a screen in my Flutter app. I even found a good candidate which almost does what I'd want: that's the https://pub.dev/packages/overlay_tutorial/ plugin. The user would press a help button on the screen, the overlay would pop up with the cut outs. I don't want anything to receive clicks though (https://github.com/TabooSun/overlay_tutorial/issues/26), however the overlay should disappear if the user clicks anywhere on the screen.
I tried to use AbsorbPointer and it successfully intercepts the clicks and there's no more click through and things happening bellow the overlay. See my fork: https://github.com/CsabaConsulting/overlay_tutorial/commit/9d809d51bcf55b9c8044d07d151c696bdb55abe6 However now there's no way to click away the overlay either.
@override
Widget build(BuildContext context) {
return Stack(
children: [
AbsorbPointer(
absorbing: widget.enabled && widget.absorbPointer,
ignoringSemantics: true,
child: _OverlayTutorialBackbone(
overlayColor: widget.overlayColor,
enabled: widget.enabled,
overlayTutorialHoles: _overlayTutorialHoles,
onEntryRectCalculated: () {
_updateChildren();
},
child: widget.child,
),
),
if (widget.enabled) ...[
..._overlayTutorialHoles.entries
.map((entry) {
What I'd need is an onTap or an onClick handler for the AbsorbPointer. I could have wrap stuff into a GestureDetector, but that would intercept clicks and gestures all the time. What's a good solution here?
*this answer follows from my comment
If you paste this code into DartPad, you can see that when one GestureDetector
is above another in the widget tree, only the onTap
of the lower one gets executed. But, if the lower GestureDetector
is wrapped in AbsorbPointer
with absorbing: true
only then does the parent GestureDetector
get called. Thus, you should be able to get away with following what I suggested in the comments. Even if this weren't true, you could still just conditionally set the onTap
of your parent GestureDetector
to null
.
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
bool absorbing = false;
@override
Widget build(BuildContext context) {
return Column(
children: [
GestureDetector(
onTap: () {
print('GestureDetector 1');
},
child: AbsorbPointer(
absorbing: absorbing,
child: GestureDetector(
onTap: () {
print('GestureDetector 2');
},
child: Container(
height: 200.0,
width: 200.0,
color: Colors.red,
alignment: Alignment.center,
child: Text(
'Press me and check console',
style: TextStyle(
color: Colors.white,
),
),
),
),
),
),
GestureDetector(
onTap: () {
setState(() {
absorbing = !absorbing;
});
},
child: Container(
height: 200.0,
width: 200.0,
color: Colors.blue,
alignment: Alignment.center,
child: Text(
'Change absorb\n(absorbing: $absorbing)',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
),
),
),
),
],
);
}
}