I'm trying to give focus to a FocusNode on a tap. I can do this with a GestureDetector's onTap callback but I am running into an issue of propagation in nested GestureDetectors as noted in the documents:
Given a parent GestureDetector with an onTap callback, and a child GestureDetector that also defines an onTap callback, when the inner GestureDetector is tapped, both GestureDetectors send a GestureRecognizer into the gesture arena. This is because the pointer coordinates are within the bounds of both GestureDetectors. The child GestureDetector wins in this scenario because it was the first to enter the arena, resolving as first come, first served. The child onTap is called, and the parent's is not as the gesture has been consumed.
This causes an issue for me if I have a deep section of a window to be considered "in focus" such as a row in a DataTable that will listen for certain shortcuts, like the delete key, but I also want other widgets which are higher up in the window to listen for a ESC or Enter key.
If I use a GestureDetector to request focus then it is consumed. Is there some other way to requestFocus() instead of a GestureDector so that I'm not trapping myself?
Related
In Flutter, is there a way to allow both a parent and a child to respond to a Drag Gesture and toggle between which one is receiving it mid-drag.
I have a Widget that updates its size using onVerticalDragUpdate from a GestureDetector Widget. Its child is a ListView wrapped in IgnorePointer. When the parent is the correct size I set the state to prevent the parent from responding to gestures and set ignore to false for the child to allow it to scroll.
Whilst this works, the user has to lift their finger from the screen and scroll again for the child to begin scrolling. Is there a way to achieve this with the same gesture so that if the user is still dragging and the parent reaches the correct size, the child begins to scroll instead all without having to lift the finger.
Here is a simplified example.
final ignorePointer = useState<bool>(true); // hook state
double desiredSize = 100;
GestureDetector(
onVerticalDragUpdate: ignorePointer ? (details){
if((details.globalPosition.dy / desiredSize) >= 1){
ignorePointer.value = false;
}
} : null,
child: IgnorePointer(
ignoring: ignorePointer.value,
child: ListView(
children:[for(int i = 0; i < 100; i++) Text('Boo $i')]
),
),
);
The way that Gestures and other pointer events work is that the Gesture furthest down the stack that can receive an input, will receive an input. Whilst IgnorePointer can prevent a child element receiving the input, the GestureDetector won't 'let go' of its 'win' as the receiving input until that gesture comes to an end. So changing the state of IgnorePointer mid-drag will change the state but not undo the win of GestureDetector as the receiving input until that Gesture ends.
What is needed here is a way for both the ListView and the GestureDetector to receive the user input. Then with logic decide which one (or both) is going to act on the user input.
Therefore you need a Widget that can register the user input without blocking other Widgets also receiving user input.
There's a Widget for that: Listener.
Behind the scenes GestureDetector engages in a little battle in something called the GestureArena that decides which Gesture wins in any battle of multiple gestures or multiple elements that could receive gestures. Listener does not engage in this battle.
So using Listener is the answer as you will now be able to receive all events received by any user input be it pointerDown, pointerUp, pointerMove etc.. etc.. regardless of anything else (like a ListView) that may also be directly responding to user inputs. You then need to decide what you do with those events.
Listener(
onPointerMove: (e) => // your logic
child: // your scrollable widget
)
I want to disable GestureArena which decides the preference of gestures,
is there any possible way to do this?
About Gesture disambiguation
you can try an IgnorePointer widget or an AbsorbPointer widget.
IgnorePointer prevents its children from receiving pointer events but is itself invisible to hit testing.
AbsorbPointer prevents its subtree from receiving pointer events by terminating hit testing at itself.
In Flutter, we can either use GestureDetector onTap or Button to capture user press event. Is there any difference between them for accessibility like in the case of HTML (div vs button)?
Furthermore, how is GestureDetector translated into flutter web? Is it translated into a div with a onClick handler or a button?
Difference in accessibility
If you take a closer look at each of them, they both are triggered by the underlying callbacks.
onPressed for the Button
VoidCallback onPressed
Called when the button is tapped or otherwise activated.
If this callback and onLongPress are null, then the button will be disabled.
See also:
enabled, which is true if the button is enabled.
onTap for GestureDetector
GestureTapCallback onTap
A tap with a primary button has occurred.
This triggers when the tap gesture wins. If the tap gesture did not win, onTapCancel is called instead.
You will notice that there isn't much of a difference as both GestureTapCallback and VoidCallback are typedef equal to void Function(); used for the same purpose.
onTap and onPressed difference in flutter web
As for your second question, you can check my personal website built in flutter and inspect the source code yourself, I use both GestureDetector and Button, they translate to the exact same role="button" in the html output.
Documentation:
GestureDetector Class
TextButton Class
onPressed
onTap
I'd like some elements in RerderableListView to be non-draggable. I know I can do nothing in the onReorder function, but I want to deny start dragging.
How to achieve that?
You can wrap the elements with AbsorbPointer or IgnorePointer.
AbsorbPointer class
A widget that absorbs pointers during hit testing.
When absorbing is true, this widget prevents its subtree from
receiving pointer events by terminating hit testing at itself. It
still consumes space during layout and paints its child as usual. It
just prevents its children from being the target of located events,
because it returns true from RenderBox.hitTest.
IgnorePointer class
A widget that is invisible during hit testing.
When ignoring is true, this widget (and its subtree) is invisible to
hit testing. It still consumes space during layout and paints its
child as usual. It just cannot be the target of located events,
because it returns false from RenderBox.hitTest.
I have wrapped widgets I want to be non-draggable with GestureDetector and set empty handler onLongPress: (){}.
When we want to achieve a button, we can choose raisedButton in flutter lib, also can a Container with onTap() function. So what's the difference between the two and which one should we choose in different situation?
Container function doesnot have onTap option, but you can achieve it by wrapping it in Guesture detector or Inkwell. There are no changes in the backend. But Raised button gives you the elevation, and animation on tap. You can also do it in the container, but it requires you to manually do it.
If you want a simple button quickly use RaisedButton.
If you want a more complex one, use Container or mixture of Containers, RaisedButton etc.
I am gonna offer something different... A tale of a treasure that is known to have answers to many questions, use cases and sometimes even examples. The name of this treasure is: official documentation. ;)
RaisedButton
A raised button is based on a Material widget whose Material.elevation increases when the button is pressed. Use raised buttons to add dimension to otherwise mostly flat layouts, e.g. in long busy lists of content, or in wide spaces. Avoid using raised buttons on already-raised content such as dialogs or cards.
Container
A convenience widget that combines common painting, positioning, and
sizing widgets.
GestureDetector
A widget that detects gestures. Attempts to recognize gestures that
correspond to its non-null callbacks. If this widget has a child, it
defers to that child for its sizing behavior. If it does not have a
child, it grows to fit the parent instead. By default a
GestureDetector with an invisible child ignores touches; this behavior
can be controlled with behavior. GestureDetector also listens for
accessibility events and maps them to the callbacks. To ignore
accessibility events, set excludeFromSemantics to true. See
flutter.dev/gestures/ for additional information. Material design
applications typically react to touches with ink splash effects. The
InkWell class implements this effect and can be used in place of a
GestureDetector for handling taps.
InkWell
A rectangular area of a Material that responds to touch. For a variant
of this widget that does not clip splashes, see InkResponse... The
InkWell widget must have a Material widget as an ancestor. The
Material widget is where the ink reactions are actually painted. This
matches the material design premise wherein the Material is what is
actually reacting to touches by spreading ink.
Well, technically you are right, they can both react to a click event. But a RaisedButton will have all the styles specific to the target platform taken care of for you (it inherits from MaterialButton).
note that it's the same in html: you can use a regular div with a click handler or use a button tag. The button will have a few styles already taken care of for you...