flutter系列之:移動端的手勢基礎GestureDetector
簡介
移動的和PC端有什麼不同呢?同樣的H5可以運行在APP端,也可以運行在PC端。兩者最大的區別就是移動端可以用手勢。手勢可以做到一些比如左滑右滑,上滑下滑,縮放等操作。
原生的andorid和IOS當然可以做到這些事情,作為一個移動的的開發框架flutter,自然也能夠支持手勢。flutter中的手勢支持叫做GestureDetector,一起來看看flutter中的手勢基礎吧。
Pointers和Listener
我們先來考慮一下最簡單的手勢是什麼呢?很明顯,最簡單的手勢就是模擬鼠標的點擊操作。我們可以將其稱之為Pointer event,也就是各種點擊事件。
flutter中有四種Pointer事件,這些事件如下所示:
- PointerDownEvent –表示用手點擊了屏幕,接觸到了一個widget。
- PointerMoveEvent –表示手指從一個位置移動到另外一個位置。
- PointerUpEvent –手指從點擊屏幕變成了離開屏幕。
- PointerCancelEvent –表示手指離開了該應用程序。
那麼點擊事件的傳遞機制是什麼樣的呢?
以手指點擊屏幕的PointerDownEvent事件為例,當手指點擊屏幕的時候,flutter首先會去定位該點擊位置存在的widget,然後將該點擊事件傳遞給該位置的最小widget.
然後點擊事件從最新的widget向上開始冒泡,並將其分派到從最裏面的widget到樹根的路徑上的所有widget中。
注意,flutter中並沒有取消或停止進一步分派Pointer事件的機制。
要想監聽這寫Pointer事件,最簡單直接的辦法就是使用Listener:
class Listener extends SingleChildRenderObjectWidget {
/// Creates a widget that forwards point events to callbacks.
///
/// The [behavior] argument defaults to [HitTestBehavior.deferToChild].
const Listener({
Key? key,
this.onPointerDown,
this.onPointerMove,
this.onPointerUp,
this.onPointerHover,
this.onPointerCancel,
this.onPointerSignal,
this.behavior = HitTestBehavior.deferToChild,
Widget? child,
}) : assert(behavior != null),
super(key: key, child: child);
可以看到Listener也是一種widget,並且可以監聽多種Pointer的事件。
我們可以把要監聽Pointer的widget封裝在Listener中,這樣就可以監聽各種Pointer事件了,具體的例子如下:
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: BoxConstraints.tight(const Size(300.0, 200.0)),
child: Listener(
onPointerDown: _incrementDown,
onPointerMove: _updateLocation,
onPointerUp: _incrementUp,
child: Container(
color: Colors.lightBlueAccent,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pressed or released in this area this many times:'),
Text(
'$_downCounter presses\n$_upCounter releases',
style: Theme.of(context).textTheme.headline4,
),
Text(
'The cursor is here: (${x.toStringAsFixed(2)}, ${y.toStringAsFixed(2)})',
),
],
),
),
),
);
void _incrementDown(PointerEvent details) {
_updateLocation(details);
setState(() {
_downCounter++;
});
}
void _incrementUp(PointerEvent details) {
_updateLocation(details);
setState(() {
_upCounter++;
});
}
void _updateLocation(PointerEvent details) {
setState(() {
x = details.position.dx;
y = details.position.dy;
});
}
但是對於Lisenter來說只能監聽最原始的Pointer事件,所以如果想監聽更多類型的手勢事件的話,則可以使用GestureDetector.
GestureDetector
GestureDetector可以檢測下面這些手勢,包括:
- Tap
Tap表示的是用戶點擊的事件,Tap有下面幾種事件:
onTapDown
onTapUp
onTap
onTapCancel
- Double tap
Double tap表示的是雙擊事件,Double tap只有一種類型:
onDoubleTap
- Long press
Long press表示的是長按。也只有下面一種類型:
onLongPress
- Vertical drag
Vertical drag表示的是垂直方向的拉,它有三個事件,分別是:
onVerticalDragStart
onVerticalDragUpdate
onVerticalDragEnd
- Horizontal drag
有垂直方向的拉,就有水平方向的拉,Horizontal drag表示的是水平方向的拉,它同樣有三個事件,分別是:
onHorizontalDragStart
onHorizontalDragUpdate
onHorizontalDragEnd
- Pan
Pan這個東西可以看做是Vertical drag和Horizontal drag的合集, 因為有時候我們是希望同時可以水平或者垂直移動,在這種情況下面,我們就需要使用到Pan的事件:
onPanStart
onPanUpdate
onPanEnd
注意, Pan是和單獨的Vertical drag、Horizontal drag是相互衝突的,不能同時使用。
要想監聽上面的這些事件,我們可以使用GestureDetector,先看下GestureDetector的定義:
class GestureDetector extends StatelessWidget {
GestureDetector({
Key? key,
this.child,
this.onTapDown,
this.onTapUp,
this.onTap,
this.onTapCancel,
this.onSecondaryTap,
this.onSecondaryTapDown,
this.onSecondaryTapUp,
this.onSecondaryTapCancel,
this.onTertiaryTapDown,
this.onTertiaryTapUp,
this.onTertiaryTapCancel,
this.onDoubleTapDown,
this.onDoubleTap,
this.onDoubleTapCancel,
this.onLongPressDown,
this.onLongPressCancel,
this.onLongPress,
this.onLongPressStart,
this.onLongPressMoveUpdate,
this.onLongPressUp,
this.onLongPressEnd,
this.onSecondaryLongPressDown,
this.onSecondaryLongPressCancel,
this.onSecondaryLongPress,
this.onSecondaryLongPressStart,
this.onSecondaryLongPressMoveUpdate,
this.onSecondaryLongPressUp,
this.onSecondaryLongPressEnd,
this.onTertiaryLongPressDown,
this.onTertiaryLongPressCancel,
this.onTertiaryLongPress,
this.onTertiaryLongPressStart,
this.onTertiaryLongPressMoveUpdate,
this.onTertiaryLongPressUp,
this.onTertiaryLongPressEnd,
this.onVerticalDragDown,
this.onVerticalDragStart,
this.onVerticalDragUpdate,
this.onVerticalDragEnd,
this.onVerticalDragCancel,
this.onHorizontalDragDown,
this.onHorizontalDragStart,
this.onHorizontalDragUpdate,
this.onHorizontalDragEnd,
this.onHorizontalDragCancel,
this.onForcePressStart,
this.onForcePressPeak,
this.onForcePressUpdate,
this.onForcePressEnd,
this.onPanDown,
this.onPanStart,
this.onPanUpdate,
this.onPanEnd,
this.onPanCancel,
this.onScaleStart,
this.onScaleUpdate,
this.onScaleEnd,
this.behavior,
this.excludeFromSemantics = false,
this.dragStartBehavior = DragStartBehavior.start,
})
可以看到GestureDetector是一個無狀態的Widget,它和Listner一樣,可以接受一個child Widget,然後監聽了很多手勢的事件。
所以, 一般來說,我們這樣來使用它:
GestureDetector(
onTap: () {
setState(() {
// Toggle light when tapped.
_lightIsOn = !_lightIsOn;
});
},
child: Container(
color: Colors.yellow.shade600,
padding: const EdgeInsets.all(8),
// Change button text when light changes state.
child: Text(_lightIsOn ? 'TURN LIGHT OFF' : 'TURN LIGHT ON'),
),
),
注意, 如果GestureDetector中有child,那麼onTap的作用範圍就在子child的範圍。如果GestureDetector中並沒有child,那麼其作用範圍就是GestureDetector的父widget的範圍。
手勢衝突
因為手勢的監聽有很多種方式,但是這些方式並不是完全獨立的,有時候這些手勢可能是互相衝突的。比如前面我們提到的Pan和Vertical drag、Horizontal drag。
如果遇到這樣的情況,那麼futter會自行進行衝突解決,去選擇到底用戶執行的是哪個操作。
比如,當用戶同時進行水平和垂直拖動的時候,兩個識別器在接收到指針向下事件時都會開始觀察指針移動事件。
如果指針水平移動超過一定數量的邏輯像素,則水平識別器獲勝,然後將該手勢解釋為水平拖動。 類似地,如果用戶垂直移動超過一定數量的邏輯像素,則垂直識別器獲勝。
總結
手勢識別是移動端的優勢項目,大家可以嘗試在需要的地方使用GestureDetector,可以達到意想不到的用戶效果哦。
更多內容請參考 //www.flydean.com/05-flutter-gestures/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程序那些事」,懂技術,更懂你!