Gesture Detector Precedence - flutter

I have a stack containing a GestureDetector (which is the up-most layer and spans across the whole screen) and another widget (under it) that eventually has a ListView. The problem is that the global GestureDetector uses onHorizontalDrag which consumes the touch event. Instead, I want the ListView to consume that event only.
In short, is there a way to make the ListView take precedence over the GestureDetector without keeping some sort of a state? Note, the ListView is not a child/sub-child of the GestureDetector - they are on different branches within the widget tree.

I try out a stack with two GestureDetectors. It gives the highest preference to the GestureDetector which is placed last in the stack.
Example:
#override
Widget build(BuildContext context) {
return Stack(
children: [
GestureDetector(
onTap: () => {
print("gesture detector1 event fired"),
},
child: Container(
color: Colors.red,
height: 600,
width: 400,
),
),
GestureDetector(
onTap: () => {
print("gesture detector2 event fired"),
},
child: Container(
color: Colors.blue,
height: 200,
width: 400,
),
),
],
);
}
When you click on the blue color container "Gesture Detector 2 is fired", you click on the red color container then "Gesture Detector 1 is fired"
Refer this image

try adding behavior: HitTestBehavoir.translucent or HitTestBehavoir.deferToChild to the gestureture detector.

Related

AbsorbPointer doesn't absorb tap event

As far as I know, due to this question Flutter AbsorbPointer vs IgnorePointer difference , AbsorbPointer will absorb tap event and other widgets below it will not receive tap event, but when I use AbsorbPointer like this. it still pass tap event to its parent:
GestureDetector(
onTap: () {
print('123'); // This still call when I tap on red container
},
child: Container(
color: Colors.green,
width: 300,
height: 300,
child: Center(
child: AbsorbPointer(
absorbing: true,
child: GestureDetector(
onTap: () {
print('567');
},
child: Container(
color: Colors.red,
width: 100,
height: 100,
),
),
),
),
),
)
So my question is does AbsorbPointer only work with widget same level in Stack and doesn't work with its parent?
In your code absorbing property having true value. which means your red container is not clickable. so parent green container have tapped. Incase if you change absorbing property to false, red container can able to tap.

Why isn't GestureDetector working in flutter?

Gesture Detector isn't working - why not?
print or Navigator not working
GestureDetector(
onTap: () {
... ;
},
child: Container(
color: Colors.white,
width: 170,
height: 60,
child:...
),
),
Often times, this is because your GestureDetector is a child of another Widget that has an onTap property. For example, if you give an ElevatedButton a child of GestureDetector, this GestureDetector's onTap is never reached, due to the ElevatedButton overruling its onTap property.

Flutter rotate icon on button press with animation

I have a button with a icon inside of it. Right now I am using two different icons and change them onTap:
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
_isDropdownOpened = !_isDropdownOpened;
}
...
},
child:
_isDropdownOpened
? SvgPicture.asset(
'images/icons/arrow_down_primary.svg',
width: scaleWidth(12),
)
: SvgPicture.asset(
'images/icons/arrow_up_primary.svg',
width: scaleWidth(12),
),
),
);
This is working but I would like to have a RotationTransition. How can I rotate my icon onTap with animation, so I don't need two different SVGs?
use RotatedBox widget and change its rotation in your setState
you can do like this
child: RotatedBox(
quarterTurns: _isDropdownOpened? 2:0,
child: SvgPicture.asset(
'images/icons/arrow_down_primary.svg',
width: scaleWidth(12),
),
)
if you want to apply animation to the rotation as well consider looking to this

Using RaisedButton widget inside Positioned widget issue

I got an issue with RaisedButton when using it inside a Positioned widget.
The problem is when I used RaisedButton inside Positioned widget, onPressed event didn't trigger when clicking on RaisedButton child, but when I clicked on another space of RaisedButton It worked.
Note that It works fine in normal situations and It happened when using RaisedButton inside Positioned widget.
here is my widget :
Positioned(
child: Center(
child: SizedBox(
width: 80,
height: 65,
child: RaisedButton(
padding: EdgeInsets.all(0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
color: Colors.green,
child: Icon(Icons.message, size: 50, color: Colors.white,),
// When I clicked on this icon, onPressed didn't triggered. but when I click on another space of button it triggered.
onPressed: () {
print('Hello world from onPressed');
},
),
),
),
top: -30,
left: 0,
right: 0,
)
what's your idea to fix this problem ?
The short answer is:
Flutter doesn't trigger push events for items that overlap the bounds of the stack (this is what you do when you set top to -30).
The reason behind this could be found here: Document that widgets in the overflow of stack do not respond to gestures
A possible solution for you is to move all the other items for 30.0 lower, so you can place the button inside the stack.
A solution to this problem is by wrapping the stack in a gesture detector and add what do you want to add in onpressed of the raised button in the ontap property of the gesture detector

How do I prevent onTapDown from being triggered on a parent widgets GestureDetector?

I have a Stack in which several widget can be dragged around. In addition, the container that the Stack is in has a GestureDetector to trigger on onTapDown and onTapUp. I want those onTap events only to be triggered when the user taps outside of the widget in the Stack. I've tried the following code:
class Gestures extends StatefulWidget {
#override
State<StatefulWidget> createState() => _GesturesState();
}
class _GesturesState extends State<Gestures> {
Color background;
Offset pos;
#override
void initState() {
super.initState();
pos = Offset(10.0, 10.0);
}
#override
Widget build(BuildContext context) => GestureDetector(
onTapDown: (_) => setState(() => background = Colors.green),
onTapUp: (_) => setState(() => background = Colors.grey),
onTapCancel: () => setState(() => background = Colors.grey),
child: Container(
color: background,
child: Stack(
children: <Widget>[
Positioned(
top: pos.dy,
left: pos.dx,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onPanUpdate: _onPanUpdate,
// onTapDown: (_) {}, Doesn't affect the problem
child: Container(
width: 30.0,
height: 30.0,
color: Colors.red,
),
),
)
],
),
),
);
void _onPanUpdate(DragUpdateDetails details) {
RenderBox renderBox = context.findRenderObject();
setState(() {
pos = renderBox.globalToLocal(details.globalPosition);
});
}
}
However, when starting to drag the widget, the onTap of the outermost container is triggered as well, making the background momentarily go green in this case. Settings HitTestBehavior.opaque doesn't seem to work like I'd expect. Neither does adding a handler for onTapDown to the widget in the Stack.
So, how do I prevent onTapDown from being triggered on the outermost GestureDetector when the user interacts with the widget inside of the Stack?
Update:
An even simpler example of the problem I'm encountering:
GestureDetector(
onTapDown: (_) {
print("Green");
},
child: Container(
color: Colors.green,
width: 300.0,
height: 300.0,
child: Center(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTapDown: (_) {
print("Red");
},
child: Container(
color: Colors.red,
width: 50.0,
height: 50.0,
),
),
),
),
);
When I tap and hold the red container, both "Red" and "Green" are printed even though the inner GestureDetector has HitTestBehavior.opaque.
In this answer, I'll solve the simpler example you have given. You are creating the following Widget hierarchy:
- GestureDetector // green
- Container
- Center
- GestureDetector // red
- Container
Therefore the red GestureDetector is a child Widget of the green GestureDetector. The green GestureDetector has the default HitTestBehavior: HitTestBehavior.deferToChild. That is why onTapDown is fired for both containers.
Targets that defer to their children receive events within their
bounds only if one of their children is hit by the hit test.
Instead, you can use a Stack to build your UI:
- Stack
- GestureDetector // green
- Container
- GestureDetector // red
- Container
This structure would result in the follwing sourcecode. It looks the same, but the behavior is the one you desired:
Stack(
alignment: Alignment.center,
children: <Widget>[
GestureDetector(
onTapDown: (_) {
print("Green");
},
child: Container(
color: Colors.green,
width: 300.0,
height: 300.0,
),
),
GestureDetector(
onTapDown: (_) {
print("Red");
},
child: Container(
color: Colors.red,
width: 50.0,
height: 50.0,
),
)
],
)
I found that I could get the behavior that I wanted (making a widget appear transparent to it parent, while still responding to pointer events) by creating a render object with hit test behavior like this:
#override
bool hitTest(BoxHitTestResult result, {#required Offset position}) {
// forward hits to our child:
final hit = super.hitTest(result, position: position);
// but report to our parent that we are not hit when `transparent` is true:
return false;
}
I've published a package with a widget having this behavior here: https://pub.dev/packages/transparent_pointer.
I use RiverPod, and have succeeded with these steps. This is a general process, so should work for all use cases (and with your state manager)
(The use case here is to prevent a ListView from scrolling, when I select text on a widget using the mouse).
Create a notifier
final textSelectionInProgress = StateProvider<bool>((ref) {
return false;
});
When there is an action (in my case onTap) on the widget, wrap widget with a Focus, or use the focusNode of the widget, and the following code in initState
#override
initState() {
super.initState();
focusN = FocusNode();
focusN.addListener(() {
if (focusN.hasFocus) {
widget.ref.read(textSelectionInProgress.notifier).state = true;
} else {
widget.ref.read(textSelectionInProgress.notifier).state = false;
}
});
}
Ensure you add this in the onDispose:
#override
void dispose() {
widget.ref.read(textSelectionInProgress.notifier).state = false;
super.Dispose();
}
Add listener in the build of the widget you want to stop scrolling
bool textSelectionOn = ref.watch(textSelectionInProgress);
Set ScrollPhysics appropriately
physics: textSelectionOn
? NeverScrollableScrollPhysics()
: <you choice>