概述
- 事件監(jiān)聽
- 跨組件事件
一、事件監(jiān)聽
-
1.1、在Flutter中,手勢(shì)的兩個(gè)不同的層次
- 第一層:
原始指針事件
(Pointer Events):描述了屏幕上由觸摸板、鼠標(biāo)、指示筆等觸發(fā)的指針的位置和移動(dòng)。 - 第二層:
手勢(shì)識(shí)別
(Gesture Detector):這個(gè)是在原始事件上的一種封裝。- 比如我們要監(jiān)聽用戶長(zhǎng)按,如果自己封裝原始事件我們需要監(jiān)聽從用戶按下到抬起的時(shí)間來(lái)判斷是否是一次長(zhǎng)按事件;
- 比如我們需要監(jiān)聽用戶雙擊事件,我們需要自己封裝監(jiān)聽用戶兩次按下抬起的時(shí)間間隔;
- 幸運(yùn)的是各個(gè)平臺(tái)幾乎都對(duì)它們進(jìn)行了封裝,而Flutter中的手勢(shì)識(shí)別就是對(duì)原始指針事件的封裝;
- 包括哪些手勢(shì)呢?比如
點(diǎn)擊
、雙擊
、長(zhǎng)按
、拖動(dòng)
等
- 第一層:
-
1.2、指針事件 Pointer
Pointer 代表的是人機(jī)界面交互的原始數(shù)據(jù)。一共有四種指針事件:- PointerDownEvent 指針在特定位置與屏幕接觸
- PointerMoveEvent 指針從屏幕的一個(gè)位置移動(dòng)到另外一個(gè)位置
- PointerUpEvent 指針與屏幕停止接觸
- PointerCancelEvent 指針因?yàn)橐恍┨厥馇闆r被取消,電話等等
Pointer的原理是什么呢?
- 在指針落下時(shí),框架做了一個(gè) hit test 的操作,確定與屏幕發(fā)生接觸的位置上有哪些Widget以及分發(fā)給最內(nèi)部的組件去響應(yīng);
- 事件會(huì)沿著最內(nèi)部的組件向組件樹的根冒泡分發(fā);
- 并且不存在用于取消或者停止指針事件進(jìn)一步分發(fā)的機(jī)制;
原始指針事件使用Listener來(lái)監(jiān)聽:
Center( child: Listener( onPointerDown: (event) { print('指針按下:$event'); }, onPointerMove: (event) { print('指針移動(dòng):$event'); }, onPointerUp: (event) { print('指針?biāo)砷_:$event'); }, onPointerCancel: (event) { print('指針打斷取消:$event'); }, child: Container( width: 200, height: 200, color: Colors.green, ), ) );
提示:我們?nèi)绻肽玫近c(diǎn)擊的位置,可以如下
- 1、
event.position
在整個(gè)界面內(nèi)的位置 - 2、
event.localPosition
在自己內(nèi)部的位置
-
1.3、手勢(shì)識(shí)別Gesture
Gesture是對(duì)一系列Pointer的封裝,官方建議開發(fā)中盡可能使用Gesture,而不是Pointer。
Gesture分層非常多的種類:-
點(diǎn)擊:
- onTapDown:用戶發(fā)生手指按下的操作
- onTapUp:用戶發(fā)生手指抬起的操作
- onTap:用戶點(diǎn)擊事件完成
- onTapCancel:事件按下過(guò)程中被取消
-
雙擊:
- onDoubleTap:快速點(diǎn)擊了兩次
-
長(zhǎng)按:
- onLongPress:在屏幕上保持了一段時(shí)間
-
縱向拖拽:
- onVerticalDragStart:指針和屏幕產(chǎn)生接觸并可能開始縱向移動(dòng);
- onVerticalDragUpdate:指針和屏幕產(chǎn)生接觸,在縱向上發(fā)生移動(dòng)并保持移動(dòng);
- onVerticalDragEnd:指針和屏幕產(chǎn)生接觸結(jié)束;
-
橫線拖拽:
- onHorizontalDragStart:指針和屏幕產(chǎn)生接觸并可能開始橫向移動(dòng);
- onHorizontalDragUpdate:指針和屏幕產(chǎn)生接觸,在橫向上發(fā)生移動(dòng)并保持移動(dòng);
- onHorizontalDragEnd:指針和屏幕產(chǎn)生接觸結(jié)束;
-
移動(dòng):
- onPanStart:指針和屏幕產(chǎn)生接觸并可能開始橫向移動(dòng)或者縱向移動(dòng)。如果設(shè)置了 onHorizontalDragStart 或者 onVerticalDragStart,該回調(diào)方法會(huì)引發(fā)崩潰;
onPanUpdate:指針和屏幕產(chǎn)生接觸,在橫向或者縱向上發(fā)生移動(dòng)并保持移動(dòng)。如果設(shè)置了 onHorizontalDragUpdate 或者 onVerticalDragUpdate,該回調(diào)方法會(huì)引發(fā)崩潰。 - onPanEnd:指針先前和屏幕產(chǎn)生了接觸,并且以特定速度移動(dòng),此后不再在屏幕接觸上發(fā)生移動(dòng)。如果設(shè)置了 onHorizontalDragEnd 或者 onVerticalDragEnd,該回調(diào)方法會(huì)引發(fā)崩潰。
- onPanStart:指針和屏幕產(chǎn)生接觸并可能開始橫向移動(dòng)或者縱向移動(dòng)。如果設(shè)置了 onHorizontalDragStart 或者 onVerticalDragStart,該回調(diào)方法會(huì)引發(fā)崩潰;
從Widget的層面來(lái)監(jiān)聽手勢(shì),我們需要使用:GestureDetector
當(dāng)然,我們也可以使用RaisedButton、FlatButton、InkWell等來(lái)監(jiān)聽手勢(shì)
globalPosition用于獲取相對(duì)于屏幕的位置信息
-
localPosition用于獲取相對(duì)于當(dāng)前Widget的位置信息
Center( child: GestureDetector( onTapDown: (detail) { print('手指按下'); }, onTapUp: (detail) { print('手指抬起'); }, onTapCancel: () { print('手勢(shì)取消'); }, onTap: () { print('手勢(shì)點(diǎn)擊'); }, onDoubleTap: () { print('手勢(shì)雙擊'); }, onLongPress: () { print('長(zhǎng)按手勢(shì)'); }, child: Container( width: 200, height: 200, color: Colors.green, ), ) );
-
點(diǎn)擊:
-
1.4、看下面的一個(gè)問(wèn)題,點(diǎn)擊小的Widget,大的Widget也會(huì)響應(yīng)
Center( child: GestureDetector( onTapDown: (detail) { print('點(diǎn)擊---大---的Widget'); }, child: Container( alignment: Alignment.center, width: 200, height: 200, color: Colors.brown, child: GestureDetector( onTapDown: (detail) { print('點(diǎn)擊---小---的Widget'); }, child: Container( width: 100, height: 100, color: Colors.yellow, ), ), ), ), );
解決問(wèn)題,我們用 Stack 包括兩個(gè) GestureDetector 如下
Center( child: Stack( alignment: Alignment.center, children: [ GestureDetector( onTapDown: (detail) { print('點(diǎn)擊---大---的Widget'); }, child: Container( alignment: Alignment.center, width: 200, height: 200, color: Colors.brown, ), ), GestureDetector( onTapDown: (detail) { print('點(diǎn)擊---小---的Widget'); }, child: Container( width: 100, height: 100, color: Colors.yellow, ), ), ], ), );
假如我們想點(diǎn)擊小的沒反應(yīng),傳遞給大的 Widget 怎么辦呢?那么我們就需要阻塞小的Widget,那么我們就需要給小的 Widget 包裹一個(gè) IgnorePointer,讓它手勢(shì)事件,如下
IgnorePointer( child: GestureDetector( onTapDown: (detail) { print('點(diǎn)擊---小---的Widget'); }, child: Container( width: 100, height: 100, color: Colors.yellow, ), ), ),
二、跨組件事件(事件的傳遞)
在組件之間如果有事件需要傳遞,一方面可以一層層來(lái)傳遞,另一方面我們也可以使用一個(gè)EventBus工具來(lái)完成。
其實(shí)EventBus在Vue、React中都是一種非常常見的跨組件通信的方式:
EventBus相當(dāng)于是一種訂閱者模式,通過(guò)一個(gè)全局的對(duì)象來(lái)管理;
這個(gè)EventBus我們可以自己實(shí)現(xiàn),也可以使用第三方的EventBus;
這里我們直接選擇第三方的 Event Bus:
dependencies:
event_bus: ^2.0.0
-
第一:我們需要定義一個(gè)希望在組件之間傳遞的對(duì)象:我們可以稱之為一個(gè)事件對(duì)象,也可以是我們平時(shí)開發(fā)中用的模型對(duì)象(model)
class UserInfo { String nickname; int level; UserInfo(this.nickname, this.level); }
-
第二:創(chuàng)建一個(gè)全局的EventBus對(duì)象
final eventBus = EventBus();
-
第三:在某個(gè)Widget中,發(fā)出事件:
class HYButton extends StatelessWidget { @override Widget build(BuildContext context) { return RaisedButton( child: Text("HYButton"), onPressed: () { final info = UserInfo("why", 18); eventBus.fire(info); }, ); } }
-
第四:在某個(gè)Widget中,監(jiān)聽事件
class JKText extends StatefulWidget { @override _JKTextState createState() => _JKTextState(); } class _JKTextState extends State<JKText> { String _message = 'Hello World!'; @override void initState() { // TODO: implement initState super.initState(); eventBus.on<UserModel>().listen((data) { setState(() { _message = '${data.nickname} ${data.age}'; }); }); } @override Widget build(BuildContext context) { return Text('$_message', style: TextStyle(fontSize: 22, color: Colors.green),); } }