How do I detect if a GestureDetector is hovered in Flutter?
Asked Answered
M

8

26

So I have a widget GestureDetector in Flutter and I want it to do something on "hover", like this (dragged in any direction):

example

Is it possible to do it with GestureDetector? Any help appreciated.

Mcdonough answered 26/11, 2020 at 16:24 Comment(0)
G
27

The MouseRegion

The MouseRegion Widget MouseRegion exposes a few events related to, you guessed it, your mouse. These events are:

OnEnter, which will trigger when your mouse enters the Widget.
OnExit, which will trigger when your mouse leaves the Widget.
OnHover, which will trigger both when your mouse enters the Widget and on every subsequent move inside the Widget.

All of these actually are PointerEvents which return all sorts of informations regarding the user’s pointer. Here, we’re going to be looking at the current position of the user’s mouse inside the containing Widget which is stored under the guise of an Offset.

 class _MyHoverableWidgetState extends State<MyHoverableWidget>{
        // Our state
        bool amIHovering = false;
        Offset exitFrom = Offset(0,0);
    
        return MouseRegion(
            onEnter: (PointerDetails details) => setState(() => amIHovering = true),
            onExit: (PointerDetails details) => setState(() { 
                amIHovering = false;
                exitFrom = details.localPosition; // You can use details.position if you are interested in the global position of your pointer.
            }),
            child: Container(
                width: 200,
                height: 200,
                child: Center(
                    child:Text(amIHovering ? "Look mom, I'm hovering" : "I exited from: $exitFrom"),
                ), 
            ),
        );

    }
Gorges answered 4/6, 2021 at 18:16 Comment(1)
Is MouseRegion's events triggered by the gesture also ? (in iOS and Android for example , not in the web or desktop)Phiona
R
7

For anyone looking to do this on mobile, note that a touch interface doesn't actually handle the concept of "hover" unlike your desktop browser, so solutions proposing MouseRegion.onHover|Enter|Exit, Inkwell.onHover, or Listener.onPointerHover that look ok fiddling in e.g. DartPad will not be responsive or otherwise behave oddly in your device/simulators (i.e. MouseRegion.onHover will fire on a tap for mobile, which may be confusing if you didn't read the property closely).

Solutions for mobile that involve using a Listener.onPointerMove and manually hitTesting the RenderBox for the selection area has been discussed here and again here. (The latter also has an answer that explicitly calculates it yourself using Rects representing the bounding boxes in the global coordinate frame for whatever you want to hit. Presumably Path.contains could be used to do the same for non-rectangular hit zones.)

Reptant answered 23/2, 2022 at 9:23 Comment(0)
U
3

The Inkwell widget can do this. You only need to wrap your widget around it. You can replace Gesture Detector with the Inkwell. Sample code looks like:

InkWell(
      onHover: (value)
      {
        //Do something
      },
        onTap: ()
      {
        //Do something
      },
      child: Container(),

)

Upbear answered 11/12, 2021 at 0:7 Comment(0)
B
2

You can create a really small box and move it along when dragging, then implement some collision (this package: https://pub.dev/packages/slowly_moving_widgets_field), and check whether your small box is collide with GestureDetector's box or not.

If it's collide -> return hovered If it's not -> not hovered

Bryce answered 26/11, 2020 at 16:38 Comment(0)
T
2

Unfortunately you can't use GestureDetector for that, instead you have to use MouseRegion.

I'm using the idea of this answer but I will correct it, because it doesn't work (for me). The answer used PointerDetails which does not work for me. Instead I will use PointerEvent.

// inside your current widget
class _MyHoverableWidgetState extends State<MyHoverableWidget>{

        // some previous code of your class //


        // define a class variable to store the current state of your mouse pointer
        bool amIHovering = false;

        // store the position where your mouse pointer left the widget
        Offset exitFrom = Offset(0,0);
    
        return MouseRegion(
            // callback when your mouse pointer enters the underlying widget
            // here you have to use 'PointerEvent'
            onEnter: (PointerEvent details) => setState(() => amIHovering = true),
            
            // callback when your mouse pointer leaves the underlying widget
            onExit: (PointerEvent details) => setState(() { 
                amIHovering = false;
                // storing the exit position
                exitFrom = details.localPosition; // You can use details.position if you are interested in the global position of your pointer.
            }),

            // your underlying widget, can be anything
            child: Container(
                width: 200,
                height: 200,
                child: Center(
                    child:Text(amIHovering ? "Look mom, I'm hovering" : "I exited from: $exitFrom"),
                ), 
            ),
        );

    }
Tip answered 9/6, 2021 at 21:48 Comment(1)
As I found out today, you can also use the InkWell Widget which has a built-in hover function!Tip
B
1

I'm not familiar with a method for doing this with GestureDetector since I believe all gestures need to start inside the GestureDetector widget. It may be possible to wrap a container inside a GestureDetector and determine when the pointer is over top of the container.

If you're trying to do this on a Web or desktop device, you can use MouseRegion widget. If you go to the link, you can see how easily it detects mouse entries and exits.

There's also the Draggable widget that can be used in conjunction with the DraggableTarget widget to do some cool things.

Borries answered 26/11, 2020 at 16:36 Comment(1)
Thanks! I'll try it. Will it work for mobile devices, when the user will swipe a finger on the screen?Mcdonough
C
0

You can wrap your Gesturedetector or any widget with Listener class and listen to the position of the drag and based on those values you can perform some tasks.

Caelum answered 2/6, 2021 at 6:19 Comment(1)
If the touch has been started from outside of my widget there is no event which fires on Listener widget. For example , in the Listener documentation , If you start your gesture from outside of the blue widget, when hovoring the blue widget there is nothing happened.Phiona
P
0
  1. Wrap the parent widget with a Gesture detector. This is to satisfy the case where the hover event is triggered outside the bounds of the target widget.
  2. Get the position/area of the widget that should respond to the "hover" event. Ensure that you do this before the widget tree is built i.e initState for Stateful widgets. Also, this should be absolute to the viewport. You dont want a case where when the screen is scrolled the condition(below) is satisfied
  3. Add a listener to the gesture detector with a callback function that listens and updates when the "hover" is detected within the bounds of the target widget .

So, in a nutshell if the target bounds lie within the coordinates x1(0), x2(10), y1(0) , y2(8)

// Sample callbackCode to check if within
// x and y are the positions returned from the gesture detector
bool isWithinTarget(x, y)
{
 if (x > x1 && x < x2 && y > y1 && y < y2) {
   // The pointer is within the target widget
   // Perform required action
   // Rebuild widget tree if necessary
   return true;
 }
 return false;
 }
Putative answered 4/6, 2021 at 20:59 Comment(1)
Yes . But when you have a lot of child widgets in the parent widget , this may be not efficient to check which one is hovored.Phiona

© 2022 - 2024 — McMap. All rights reserved.