一. 事件監(jiān)聽
在Flutter中,手勢有兩個不同的層次:
第一層:原始指針事件(Pointer Events):描述了屏幕上由觸摸板、鼠標(biāo)、指示筆等觸發(fā)的指針的位置和移動。
-
第二層:手勢識別(Gesture Detector):這個是在原始事件上的一種封裝。
- 比如我們要監(jiān)聽用戶長按,如果自己封裝原始事件我們需要監(jiān)聽從用戶按下到抬起的時間來判斷是否是一次長按事件;
- 比如我們需要監(jiān)聽用戶雙擊事件,我們需要自己封裝監(jiān)聽用戶兩次按下抬起的時間間隔;
- 幸運的是各個平臺幾乎都對它們進(jìn)行了封裝,而Flutter中的手勢識別就是對原始指針事件的封裝;
- 包括哪些手勢呢?比如點擊、雙擊、長按、拖動等
2.1. 指針事件Pointer
Pointer 代表的是人機(jī)界面交互的原始數(shù)據(jù)。一共有四種指針事件:
-
PointerDownEvent
指針在特定位置與屏幕接觸 -
PointerMoveEvent
指針從屏幕的一個位置移動到另外一個位置 -
PointerUpEvent
指針與屏幕停止接觸 -
PointerCancelEvent
指針因為一些特殊情況被取消
Pointer的原理是什么呢?
在指針落下時,框架做了一個 hit test 的操作,確定與屏幕發(fā)生接觸的位置上有哪些Widget以及分發(fā)給最內(nèi)部的組件去響應(yīng);
事件會沿著最內(nèi)部的組件向組件樹的根冒泡分發(fā);
并且不存在用于取消或者停止指針事件進(jìn)一步分發(fā)的機(jī)制;
原始指針事件使用Listener來監(jiān)聽:
class HomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Listener(
child: Container(
width: 200,
height: 200,
color: Colors.red,
),
onPointerDown: (event) => print("手指按下:$event"),
onPointerMove: (event) => print("手指移動:$event"),
onPointerUp: (event) => print("手指抬起:$event"),
),
);
}
}
2.2. 手勢識別Gesture
Gesture是對一系列Pointer的封裝,官方建議開發(fā)中盡可能使用Gesture,而不是Pointer
Gesture分層非常多的種類:
點擊:
- onTapDown:用戶發(fā)生手指按下的操作
- onTapUp:用戶發(fā)生手指抬起的操作
- onTap:用戶點擊事件完成
- onTapCancel:事件按下過程中被取消
雙擊:
- onDoubleTap:快速點擊了兩次
長按:
- onLongPress:在屏幕上保持了一段時間
縱向拖拽:
- onVerticalDragStart:指針和屏幕產(chǎn)生接觸并可能開始縱向移動;
- onVerticalDragUpdate:指針和屏幕產(chǎn)生接觸,在縱向上發(fā)生移動并保持移動;
- onVerticalDragEnd:指針和屏幕產(chǎn)生接觸結(jié)束;
橫線拖拽:
- onHorizontalDragStart:指針和屏幕產(chǎn)生接觸并可能開始橫向移動;
- onHorizontalDragUpdate:指針和屏幕產(chǎn)生接觸,在橫向上發(fā)生移動并保持移動;
- onHorizontalDragEnd:指針和屏幕產(chǎn)生接觸結(jié)束;
移動:
- onPanStart:指針和屏幕產(chǎn)生接觸并可能開始橫向移動或者縱向移動。如果設(shè)置了
onHorizontalDragStart
或者onVerticalDragStart
,該回調(diào)方法會引發(fā)崩潰; - onPanUpdate:指針和屏幕產(chǎn)生接觸,在橫向或者縱向上發(fā)生移動并保持移動。如果設(shè)置了
onHorizontalDragUpdate
或者onVerticalDragUpdate
,該回調(diào)方法會引發(fā)崩潰。 - onPanEnd:指針先前和屏幕產(chǎn)生了接觸,并且以特定速度移動,此后不再在屏幕接觸上發(fā)生移動。如果設(shè)置了
onHorizontalDragEnd
或者onVerticalDragEnd
,該回調(diào)方法會引發(fā)崩潰。
從Widget的層面來監(jiān)聽手勢,我們需要使用:GestureDetector
- 當(dāng)然,我們也可以使用RaisedButton、FlatButton、InkWell等來監(jiān)聽手勢
- globalPosition用于獲取相對于屏幕的位置信息
- localPosition用于獲取相對于當(dāng)前Widget的位置信息
class HYHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("手勢測試"),
),
body: GestureDetector(
child: Container(
width: 200,
height: 200,
color: Colors.red,
),
onTap: () {
},
onTapDown: (detail) {
print(detail.globalPosition);
print(detail.localPosition);
},
onTapUp: (detail) {
print(detail.globalPosition);
print(detail.localPosition);
}
),
);
}
}
二. 跨組件事件
在組件之間如果有事件需要傳遞,一方面可以一層層來傳遞,另一方面我們也可以使用一個EventBus工具來完成。
其實EventBus在Vue、React中都是一種非常常見的跨組件通信的方式:
- EventBus相當(dāng)于是一種訂閱者模式,通過一個全局的對象來管理;
- 這個EventBus我們可以自己實現(xiàn),也可以使用第三方的EventBus;
這里我們直接選擇第三方的EventBus:
dependencies:
event_bus: ^1.1.1
第一:我們需要定義一個希望在組件之間傳遞的對象:
- 我們可以稱之為一個時間對象,也可以是我們平時開發(fā)中用的模型對象(model)
class UserInfo {
String nickname;
int level;
UserInfo(this.nickname, this.level);
}
第二:創(chuàng)建一個全局的EventBus對象
final eventBus = EventBus();
第三:在某個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);
},
);
}
}
第四:在某個Widget中,監(jiān)聽事件
class HYText extends StatefulWidget {
@override
_HYTextState createState() => _HYTextState();
}
class _HYTextState extends State<HYText> {
String message = "Hello Coderwhy";
@override
void initState() {
super.initState();
eventBus.on<UserInfo>().listen((data) {
setState(() {
message = "${data.nickname}-${data.level}";
});
});
}
@override
Widget build(BuildContext context) {
return Text(message, style: TextStyle(fontSize: 30),);
}
}