Flutter PointerEvent vs OnTap, one works, the other doesn't - flutter

I have a stack widget I am trying to create in which:
1. The user touches the widget button triggering a pointerDown event.
2. Pointer down causes a slider type widget to scale from 0 to 100% from behind the button
3. With finger still down, the user drags to select a value on the scale
4. The value is selected by releasing the finger from the screen i.e. pointerUp.
The widget works fine when I use onTap instead of pointerDown in step 1. But when I try to use a pointer down event, the _open method (that manages the scaleUp of the slider) isn't triggered.
I have followed this example almost exactly: https://fireship.io/lessons/flutter-radial-menu-staggered-animations/, but have tried to change the touch event on the floatingActionbuton like this:
Transform.scale(
scale: widget.scale.value,
child: Listener(
onPointerDown: (PointerDownEvent event) {
print('pointer is down');
setState(() {
_open;
});
},
child: FloatingActionButton(child: Icon(Icons.blur_circular), onPressed: () {})),
)
The print part detects and fires the event, but the _open method does not do anything and the menu part does not appear like in the tutorial link.
I am at a loss.

Since the button below the FloatingActionButton also has a listener, The Listener widget doesn't get's the PointerDown event. So to do that you have to change the behaviour to opaque so that both get the events.
Try Something like this:
Listener(
behavior: HitTestBehavior.opaque,
onPointerDown: (PointerDownEvent details){
},
child: ...,
)

Related

Flutter Tooltip on One Tap / Hold Down

My app has several textfields and I want to have a tooltip so that users know can see the definition of each field.
I came across this answer, but it wasn't helpful: Flutter Tooltip on One Tap. Therefore I decided to try and fix it myself.
Here is how to do it:
First add GestureDetector as child for Tooltip,
TooltipTriggerMode.manual for triggerMode.
add onTapDown, onTapUp, and onTapCancel as follows
Widget build(BuildContext context) {
final tooltipkey = GlobalKey<TooltipState>();
return Tooltip(
key: tooltipkey,
message: message,
triggerMode: TooltipTriggerMode.manual, // make it manual
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTapDown: (_) => _onTapDown(tooltipkey), // add this
onTapUp: (_) => _onTapUpAndCancel(tooltipkey), // add this
onTapCancel: () => _onTapUpAndCancel(tooltipkey), // add this
child: Icon(EvaIcons.questionMarkCircleOutline),
),
);
}
and the helper functions shown inside the code above:
void _onTapDown(GlobalKey<TooltipState> tooltipkey) {
tooltipkey.currentState?.ensureTooltipVisible();
}
void _onTapUpAndCancel(GlobalKey<TooltipState> tooltipkey) {
tooltipkey.currentState?.deactivate();
}
Hooray, it works. Now you can hold down the icon to display the tooltip immediately instead of holding it down for a while (the default configuration of tooltip).

Flutter. How to disable "Double tap to activate, double tap and hold to long press" hint in accessibility mode

Currently, I'm working with Accessibility in Flutter and on android devices it's pronouncing this hint: "double tap to activate, double tap and hold to long press".
As far as I know this hint pronounced if widget is clickable, so I tried wrapping child of this widget with BlockSemantics (didn't work), also wrapped this clickable widget with Semantics and set property button: false but that also didn't help.
This how my widget looks.
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => widget.onItemSelect(),
onLongPress: () => widget.onItemLongPress(),
child: childWidget,
);
How can I disable this?
GestureDetector has a excludeSemantics property, so I set it to true. It will NOT block child widget's semantics, it will just disable semantics for GestureDetector widget itself, and as a result Android accessibility hint also turn off

How to detect multiple gestures using a single instance of GestureDetector?

I want make WhatsApp voice audio record function. User tap and hold to record audio but can swipe left to cancel. But when I use GestureDetector it only register one gesture type.
For example this only register onLongPress gesture if user start with long press. Or only onHorizontalDrag if user start with horizontal drag:
onLongPressStart: _onPressStart,
onLongPressUp: _onPressEnd,
onHorizontalDragDown: _onHorizontalDragDown,
onHorizontalDragUpdate: _onHorizontalDragUpdate,
onHorizontalDragEnd: _onHorizontalDragEnd,
How to use 2 gesture one after the other?
Thanks!
Try using onLongPressMoveUpdate callback provided by GestureDetector
This provides LongPressMoveUpdateDetails which has a property called offsetFromOrigin.
offsetFromOrigin gives you an Offset object whose dx value is what you need.
GestureDetector(
child: ...
onLongPressMoveUpdate: (updateDetails) {
if (updateDetails.offsetFromOrigin.dx < 0) {
// Handle horizontal drag towards left
}
}
)
Or, if you can't use the above method, checkout this Medium article

How to get the touch position while dragging

I would like to get the touch position while dragging and perform a setState(position). (In Android: getTouchPositionFromDragEvent). I can't find anything that I could use in the Draggable class but maybe there is a work around?
You should use GestureDetector for that:
for example:
GestureDetector(
onVerticalDragDown: (DragDownDetails details) {
print(details.globalPosition);
},
child: // ...
)
see https://docs.flutter.io/flutter/widgets/GestureDetector-class.html
for more detail.
Or you could use a Listener to get raw data Pointer position.
Listener(
onPointerDown: (PointerDownEvent event) {
print(event.position);
},
child: // ...
)
See https://flutter.io/gestures/ to have a comparison between Listener and GestureDetector.

InkWell and GestureDetector, how to make them work?

I'd like to use a GestureDetector for it's onTapDown callback but also have a nice InkWell splash effect.
Is it possible to use these two together?
If you want to unconditionally handle the pointer down event with no gesture disambiguation, you can make the InkWell a child of a Listener, and set the onPointerDown handler.
For example:
new Listener(
onPointerDown: (e) { print('onPointerDown'); },
child: new InkWell(
child: new Text('Tap me'),
onTap: () { print('onTap'); }
),
),
It might make sense to add an onTapDown handler to InkWell.
You can pass a HitTestBehavior to GestureDetector to make it "non-blocking" by setting the value to "translucent"
Or you can also trigger it yourself using Material.of(context)
InkWell uses a GestureDetector under it's hood:
https://github.com/flutter/flutter/blob/79b5e5bc8af7d9df3374dfe6141653848d1c03ac/packages/flutter/lib/src/material/ink_well.dart#L560
The reason why a GestureDetector isn't able to work well if the InkWell-Widget is, that it sets the behavior on it's internal GestureDetector to "HitTestBehavior.opaque". This prevents "event bubbling" / event capturing on parent widgets (if my understanding is correct). And because the "behavior" is final, we can't change / fix that by our own.
https://github.com/flutter/flutter/blob/79b5e5bc8af7d9df3374dfe6141653848d1c03ac/packages/flutter/lib/src/material/ink_well.dart#L566
As I mentioned in a comment above, I wrapped the InkWell within a widget (which handles other stuff too) and provided a way to pass a callback into it which is executed on the desired event.
This is my example solution:
import 'package:flutter/material.dart';
class CardPreview extends StatefulWidget {
final dynamic data;
final VoidCallback onTap;
const CardPreview(this.data, this.onTap);
CardPreview._(this.data, this.onTap);
factory CardPreview.fromData(dynamic data, VoidCallback onTap) {
return new CardPreview(data, onTap);
}
CardPreview createState() => new CardPreview(this.data, this.onTap);
}
class CardPreviewState extends State<CardPreview> {
final dynamic data;
final VoidCallback onTap;
CardPreviewState(this.data, this.onTap);
Widget buildCard() {
return Material(
color: Colors.transparent,
child: Ink(
child: InkWell(
child: null,
onTap: () {
if (this.onTap == null) {
return;
}
this.onTap();
},
),
),
);
}
#override
Widget build(BuildContext context) {
return Container(
child: buildCard(),
);
}
}
Building on RĂ©mi's suggestion of adding a HitTestBehavior.translucent behavior to your GestureDetector, this is how I would solve your issue:
Material(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTapDown: (details) {
//Do something
},
child: InkWell(
onTap: () {},
child: Container(height: 20.0, width: 20.0, color: Colors.red),
),
),
),
I was able to use something similar to essentially add a onLongPressStart and onLongPressEnd to my InkWell.
InkWell has onTapDown callback now
https://api.flutter.dev/flutter/material/InkResponse/onTapDown.html
https://api.flutter.dev/flutter/material/InkWell/InkWell.html
But, GestureDetector still has more functionality than InkWell, e.g. onSecondaryTap. Thus nesting InkWell and GestureDetector is useful today.
HitTestBehavior.opaque has no impact on child-parent relationship
https://github.com/flutter/flutter/issues/18450#issuecomment-397372078
https://github.com/flutter/flutter/issues/74733#issuecomment-767859584
In short, HitTestBehavior.opaque only prevent sibling behind it from receiving events by return true in HitTest so that its direct parent won't pass on the event to its next child(in reversed order) and return true in HitTest.(Thus, sibling behind its ancestors won't receive events as well. Just like that its ancestors' behaviors are overwritten to opaque.) BUT its ancestors always DO receive events!
Remark: If A is inside B(i.e. A is a descendant of B), then B will never be behind A.
Related source:
https://api.flutter.dev/flutter/rendering/RenderBox/hitTest.html
https://api.flutter.dev/flutter/rendering/RenderProxyBoxWithHitTestBehavior/hitTest.html (RenderProxyBoxWithHitTestBehavior is used by _RenderColoredBox, which is the "color" of Container)
https://api.flutter.dev/flutter/rendering/RenderProxyBoxWithHitTestBehavior/hitTestSelf.html
https://api.flutter.dev/flutter/rendering/RenderBoxContainerDefaultsMixin/defaultHitTestChildren.html
https://api.flutter.dev/flutter/rendering/RenderStack/hitTestChildren.html (Use defaultHitTestChildren)
Acknowledgment: heavily refer to https://github.com/flutter/flutter/issues/18450#issuecomment-601865975
For onTap, only the callback of the "winner" can fire
See https://api.flutter.dev/flutter/widgets/GestureDetector-class.html
Setting GestureDetector.behavior to HitTestBehavior.opaque or HitTestBehavior.translucent has no impact on parent-child relationships: both GestureDetectors send a GestureRecognizer into the gesture arena, only one wins.
Some callbacks (e.g. onTapDown) can fire before a recognizer wins the arena, and others (e.g. onTapCancel) fire even when it loses the arena. Therefore, the parent detector in the example above may call some of its callbacks even though it loses in the arena.
This is tricky:
If winner has onTap or onTapDown, then loser's onTap will never fire, and onTapDown won't fire if the tap is too short such that gesture arena sweeping happens before onTapDown firing.
InkWell has onTap and onTapDown
https://github.com/flutter/flutter/blob/468166c713984941c0e5432b7499a1a05a0a0f61/packages/flutter/lib/src/material/ink_well.dart#L1209-L1219
Thus, to achieve best experience, all primary tap callback should be registered on InkWell. Otherwise, if GestureDetector wins, short tap won't trigger splash, else if InkWell wins, short tap won't trigger Gesture's onTapDown.