Reputation: 1590
I have two containers in a stack and both containers have GestureDetector.The OnTap for the first container is working fine but it's not working with another container. The first container is the image and the second one is the green background aligned partially over the first container.
new Stack(
alignment: Alignment(0.0, 1.44),
children: <Widget>[
GestureDetector(
onTap: () => _openImage(context),
child: Container(
width: 340.0,
foregroundDecoration: new BoxDecoration(
color: Color.fromRGBO(155, 85, 250, 0.55)),
height: 240.0,
child: FadeInImage.assetNetwork(
placeholder: 'assets/dimlight.png',
image: post.imageUrl,
fit: BoxFit.cover,
),
),
),
new GestureDetector(
child: new Container(
color: Colors.green,
child: Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
SizedBox(width: 7.0),
CircleAvatar(
backgroundImage:
new AssetImage("assets/boy.png")
radius: 30.0,
),
SizedBox(
width: 7.0,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new SizedBox(
height: 20.0,
),
Text(
post.user.name,
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(
getTimeString(post.timestamp.toString()),
style: TextStyle(
color: Colors.grey, fontSize: 10.0),
),
],
),
SizedBox(
width: 20.0,
),
],
),
),
onTap: () => _navigateToDetails(context),
)
],
)
Layout Screenshot
Upvotes: 147
Views: 118817
Reputation: 4474
For this use behavior: HitTestBehavior.translucent
on your GestureDetector
.
For example:
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
print("taped");
},
child: <your Widget>
)
Upvotes: 1
Reputation: 836
Just make sure anything above the Tap Gesture is in IgnorePointer
https://api.flutter.dev/flutter/widgets/IgnorePointer-class.html
Stack(
children:[
(...your down widget which should handle the tap...)
IgnorePointer(
child: (... your top widget which must be transparent to any tap ...)
)
]
)
Example
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Stack(
children: <Widget>[
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
print('tap');
},
child: Container(color: Colors.blue),
),
IgnorePointer(ignoring:true,child:Container(color: Colors.white)),
],
),
),
);
}
Upvotes: 2
Reputation: 27
@Michel Fortes' AbsorbPointer()
solution worked for me, even with Stack()
as one of the descendants.
Prior to this I had tried modifying the GestureDetector.behavior
to HitTestBehavior.transluscent
, using Inkwell()
instead, and wrapping different widgets in the tree. I also tried using RawGestureDetector()
.
Nothing worked until AbsorbPointer()
:
GestureDetector(
child: AbsorbPointer(
child: Container(...) // or any Material widget, even if descendants include a Stack()
)
)
Upvotes: -1
Reputation: 1090
The click is invalid because your widget is beyond the scope of Stack
Use ConstrainedBox to fill the stack with screen
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({Key? key, required this.title}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ConstrainedBox(
constraints: BoxConstraints.expand(),
child: Stack(
children: [
Positioned(
right: 100,
bottom: 100,
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
debugPrint("hello");
showDialog(context: context, builder: ((context) => Text("dialog")));
},
child: Container(
child: Text("123"),
),
),
),
],
),
),
);
}
}
Upvotes: 0
Reputation: 71
OnTap does not work outside the first childrens area in stack. So put everything inside a container and give it a height and width and make it a first child.
Upvotes: 0
Reputation: 21
Keep your gesture detector as the last child in the stack that means it will be on the top front like below;
Stack Parent └ Child One [Far Back] └ Child Two └ Child Three [Top Front]
if its at the back of another child then the overlapping child prevents it from being clicked somehow
Upvotes: 2
Reputation: 6938
For my case, I placed the GestureDetector
inside the Stack
which created the same problem. I solved it by making the parent Stack
into a child of the GestureDetector
.
Before -
Stack(
children: [
GestureDetector()
])
After -
GestureDetector(
child:
Stack()
)
Upvotes: 0
Reputation: 939
You should try wrap your Container with an AbsorbPointer.
GestureDetector(
child: AbsorbPointer(
child: Container(...)
)
)
Upvotes: 54
Reputation: 1408
Okay here's the deal, I just changed the position of the widgets. In Stack Everything is like a Tree index.
Stack Parent
└ Child One [Far Back]
└ Child Two
└ Child Three [Top Front]
My Code:
return Stack(
children: [
accountInfo(), // this is not clickable
chatMessages(),
typingArea(),
],
),
I just changes the sequence
return Stack(
children: [
chatMessages(),
typingArea(),
accountInfo(), // Now this is clickable by any of onTap Event ad
],
),
Upvotes: 1
Reputation: 1563
Also consider Listener
widget. It helped me.
https://api.flutter.dev/flutter/widgets/Listener-class.html
Example:
Listener(
onPointerDown: (_) {}, // onTapDown
onPointerUp: (_) => {}, // onTapUp
child: Container(),
)
Upvotes: 10
Reputation: 2520
If your are using GoogleMap widget and want to add a searchfield/textfield you should be aware that the map widget is consuming your gestures. I do something like this:
final FocusNode _focusNode = new FocusNode();
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
print("tap");
if (_focusNode.hasPrimaryFocus) {
_focusNode.unfocus();
rebuildAfterFocusChange();
}
},
child: Stack(children: [
AbsorbPointer(absorbing: _focusNode.hasPrimaryFocus, child: StoreMapView()),
showSearchField(model),
]),
);
}
Widget showSearchField(StoreFinderViewModel model) {
return SafeArea(
child: Padding(
padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 12.0),
child: TextField(
onTap: () {
rebuildAfterFocusChange();
},
onSubmitted: (String input) {
rebuildAfterFocusChange();
// do other stuff
});
},
focusNode: _focusNode,
),
),
);
}
void rebuildAfterFocusChange() {
setState(() {});
}
I am using the AbsorbPointer widget to absorb the clicks/taps, but ONLY if my textfield has the focus. I call my rebuildAfterFocusChange(); if I tap on my "Searchfield", use the onsubmit method of the textfield to trigger a search in my backend and after I unfocus my textfield.
Upvotes: 0
Reputation: 6362
For me it was an issue with items overflowing outside a Stack
, with clipBehavior: Clip.none
.
In that case the item is visible but not touchable. So I updated my layout to remove the clipBehavior: Clip.none
on my Stack
and the GestureDetector
started working. 🎉
Upvotes: 15
Reputation: 11219
Ok on my side it was quite stupid but since it may happens to many of us I will explain:
My build context (simplified for the example) tree was this way:
Widget build(BuildContext context) {
return (...) (
child: GestureDetector(
onTap: () => _myFunction(),
child: Container(
height: 64.0,
width: 128.0,
child: (...)
Seems legit right? But then I inspected into more details the bottom of my tree:
(...)
onTap: () => _myFunction(),
child: Container(
height: 64.0,
width: 128.0,
child: TheSourceOfEvil() // <-- whoopsie!
Quickly inspected TheSourceOfEvil to find out that the culprit was in fact ANOTHER GestureDetector has a child, that I had not yet implemented onTap: () => {},
:
Widget TheSourceOfEvil( {
return (...) (
child: GestureDetector(
onTap: () => {}, // override previous GestureDetector
child: Container(
child: (...);
Got rid of it and it instantly fixed my puzzling issue. I hope it will help!
Upvotes: 2
Reputation: 239
In my case I had a CustomPainter inside the stack. Nothing of the above was working, until I gave the CustomerPainter a size. It was showing without explicitly giving a size, but no tap events were triggered:
Container(
child: CustomPaint(
size: Size(30, 30),
painter: RecordingButton(30),
))
Upvotes: 3
Reputation: 652
For others coming to this post, another thing that can cause this issue is an AbsorbPointer higher is the hierarchy.
Upvotes: 0
Reputation: 19434
Because of stack
..There are some widget
on your GestureDetector
.
Solution:
Stack(
children: <Widget>[
...//Other widgets,
GestureDetector()
]
Upvotes: 7
Reputation: 3807
Try setting the behavior
property of GestureDetector
to HitTestBehavior.translucent
.
Upvotes: 365
Reputation: 32
I see that you are calling the callback method instead of assigning it to onTap property of GestureDetector widget. Pass the name of the method don't call it there.
Upvotes: -2
Reputation: 27137
i think that your widgets are overlapping each other and that the causing a problem. you can check it by wrapping your GestureDetector with container and provide color to get better understanding.
your code is not enough that's why i added following example may help you to understand more clearly.
swap the position of GestureDetector in example and you can found that in first case it prints only second and in other case if you click in above part then it prints first to.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Firebase Auth Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return new Scaffold(
body: new Card(
margin: EdgeInsets.all(40.0),
child: new Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
GestureDetector(
onTap: () => print("first container"),
child: Container(
width: 340.0,
foregroundDecoration: new BoxDecoration(
color: Color.fromRGBO(155, 85, 250, 0.0)),
height: 240.0,
child: FadeInImage.assetNetwork(
placeholder: 'images/p1.png',
image:
"https://www.straitstimes.com/sites/default/files/styles/article_pictrure_780x520_/public/articles/2016/06/15/ST_20160615_LLIMH_2368135.jpg?itok=8Dggu2PM×tamp=1465926004",
fit: BoxFit.cover,
),
),
),
new GestureDetector(
child: new Container(
foregroundDecoration: BoxDecoration(
color: Color.fromRGBO(155, 85, 250, 0.4)),
child: Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
SizedBox(width: 7.0),
CircleAvatar(
backgroundImage: new AssetImage("images/p2.jpg"),
radius: 30.0,
),
SizedBox(
width: 7.0,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new SizedBox(
height: 20.0,
),
Text(
"sfvgefbv",
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(
"sfvmsfkv",
style: TextStyle(
color: Colors.grey, fontSize: 10.0),
),
],
),
),
new Container(
alignment: AlignmentDirectional.centerEnd,
// todo add here check if not logged in then ask to
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
IconButton(
icon: Icon(
Icons.comment,
color: Colors.green,
),
onPressed: () => print("message click")),
Text(
"2",
style: TextStyle(
color: Colors.green,
),
),
SizedBox(
width: 10.0,
)
],
),
),
],
),
),
onTap: () => print("this is second container"),
),
new Expanded(
child: Container(
padding: EdgeInsets.all(10.0),
child: Column(
children: <Widget>[
Text(
"fsvkmfskbnmkffvberk",
style: TextStyle(
color: Colors.green, fontWeight: FontWeight.bold),
),
new Text(
"svklmfslkbnernkjrnvkrwjnvrw",
maxLines: 6,
overflow: TextOverflow.ellipsis,
),
],
),
),
),
],
),
),
);
}
}
Upvotes: 1