56.1 Introduction to Raw Pointer Events
This section introduces raw pointer events (Pointer Event, typically touch events on mobile devices), while the next section will cover gesture handling.
In mobile platforms, the raw pointer event model is generally consistent across various platforms or UI systems. A complete event is divided into three phases: finger press, finger movement, and finger release. Higher-level gestures (like tap, double-tap, drag, etc.) are built upon these raw events.
When a pointer is pressed, Flutter performs a hit test on the application to determine which components (widgets) are located at the point of contact on the screen. The pointer down event (and any subsequent events) is then dispatched to the innermost component identified by the hit test. From there, the event bubbles up through the component tree, similar to the event bubbling mechanism in web development. However, Flutter does not provide a mechanism to cancel or stop the "bubbling" process, whereas the browser's bubbling can be halted. Note that only components that pass the hit test can trigger events; we will delve deeper into the hit test process in the next section.
Note: The term "Hit Test" has various translations in Chinese, such as "命中测试" and "点击测试." We need not be overly concerned with the names; it's sufficient to understand that they represent "Hit Test."
56.2 Listener Component
In Flutter, the Listener
widget can be used to listen for raw touch events. According to our categorization of components in this book, Listener
is also a functional component. Below is the constructor definition for Listener
:
Listener({ Key? key, this.onPointerDown, // Pointer down callback this.onPointerMove, // Pointer move callback this.onPointerUp, // Pointer up callback this.onPointerCancel,// Pointer event cancel callback this.behavior = HitTestBehavior.deferToChild, // We'll discuss this parameter later Widget? child, })
Let's look at an example where the code allows you to see the position of the finger relative to a container while moving.
class _PointerMoveIndicatorState extends State<PointerMoveIndicator> { PointerEvent? _event; @override Widget build(BuildContext context) { return Listener( child: Container( alignment: Alignment.center, color: Colors.blue, width: 300.0, height: 150.0, child: Text( '${_event?.localPosition ?? ''}', style: TextStyle(color: Colors.white), ), ), onPointerDown: (PointerDownEvent event) => setState(() => _event = event), onPointerMove: (PointerMoveEvent event) => setState(() => _event = event), onPointerUp: (PointerUpEvent event) => setState(() => _event = event), ); } }
The effect after running the code is shown in Figure :
By moving your finger within the blue rectangular area, you can see the current pointer offset. When pointer events are triggered, the parameters PointerDownEvent
, PointerMoveEvent
, and PointerUpEvent
are all subclasses of PointerEvent
. The PointerEvent
class contains information about the current pointer. Note that "Pointer" refers to the event trigger, which can be a mouse, touchpad, or finger.
For example:
position: This indicates the pointer's offset relative to the global coordinates.
localPosition: This indicates the pointer's offset relative to the local layout coordinates.
delta: The distance between two pointer move events (
PointerMoveEvent
).pressure: The pressure level; this property is more meaningful if the phone screen supports pressure sensors (like the iPhone's 3D Touch). If not, it will always be 1.
orientation: The direction of pointer movement, represented as an angle.
These are just some common properties of PointerEvent
. Besides these, there are many more properties that readers can check in the API documentation.
There’s also a behavior
property that determines how child components respond to hit tests. We will cover this property in detail in section 8.3.
56.3 Ignoring Pointer Events
If we want to prevent a certain subtree from responding to pointer events, we can use IgnorePointer
and AbsorbPointer
. Both components can stop the subtree from receiving pointer events, but the difference is that AbsorbPointer
itself participates in hit testing, while IgnorePointer
does not. This means that AbsorbPointer
can receive pointer events (but its subtree cannot), while IgnorePointer
cannot. Here’s a simple example:
Listener( child: AbsorbPointer( child: Listener( child: Container( color: Colors.red, width: 200.0, height: 100.0, ), onPointerDown: (event) => print("in"), ), ), onPointerDown: (event) => print("up"), )
When you click the Container
, it is within the subtree of AbsorbPointer
, so it won’t respond to the pointer event, and the log won’t output "in." However, AbsorbPointer
itself can receive pointer events, so it will output "up." If you replace AbsorbPointer
with IgnorePointer
, then neither will output.