How to get the touch position while dragging - flutter

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.

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 PointerEvent vs OnTap, one works, the other doesn't

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: ...,
)

GestureDetector: how to distinguish between single touch and multi-touch gestures?

My goal is to build a widget that allows to draw using single finger tap, and scale/move the canvas when in two-fingers mode.
At the moment I had built a ZoomView and DrawingOverlay widgets. ZoomView allows to scale and move the child passed as parameter. And DrawingOverlay allows to draw on top of the child widget passed as parameter. Right now they work well separately.
class ZoomView {
build() {
return GestureDetector(
onScaleUpdate: () { ... }
...
)
}
}
class DrawingOverlay {
build() {
return GestureDetector(
onPanUpdate: () { ... }
...
)
}
}
However when they are used together, things break apart:
build() {
ZoomView(
child: DrawingOverlay(
child: ...
)
)
}
Priority feels largely by chance. Sometimes it's starts drawing when 2 fingers are pressed and sometimes it's moving the screen with one finger while sometimes the other way round.
I would like to limit ZoomView to 2 finger gestures only and DrawingOverlay to 1 finger gestures only.
What's the best way to achieve this?
It should work setting both the listeners on the same 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.

How to use inner touch coordinate with Draggable

Here is an example of using Draggable.
Is there a possibility to get also like the touch coordinate within the Draggable widget? I illustrated what I'm searching for in the image with x1 and x2.
So I want to determine one of the inner coordinates like x1 or x2.
Here is how I use draggable so far.
draggable = new Draggable(
data: widget.text,
feedback: avatar,
child: item,
onDraggableCanceled: (velocity, offset) {
// the inner touch point is not included in offset ...
},
);
Draggable docs
If it doesn't work with Draggable. Is there an alternative which I can use?
You can wrap your draggable widget in a Listener and you can do the below code.
onPointerDown: (PointerEvent event) {
print("Touch Position Of your Widget ${event.position}");
},