一. 事件監聽
在大前端的開發中,必然存在各種各樣和用戶交互的情況:比如手指點擊、手指滑動、雙擊、長按等等。
在Flutter中,手勢有兩個不同的層次:
????第一層:原始指針事件(Pointer Events):描述了屏幕上由觸摸板、鼠標、指示筆等觸發的指針的位置和移動。
????第二層:手勢識別(Gesture Detector):這個是在原始事件上的一種封裝。????
????????比如我們要監聽用戶長按,如果自己封裝原始事件我們需要監聽從用戶按下到抬起的時間來判斷是否是一次長按事件;
????????比如我們需要監聽用戶雙擊事件,我們需要自己封裝監聽用戶兩次按下抬起的時間間隔;
????????幸運的是各個平臺幾乎都對它們進行了封裝,而Flutter中的手勢識別就是對原始指針事件的封裝;
????????包括哪些手勢呢?比如點擊、雙擊、長按、拖動等
2.1. 指針事件Pointer
Pointer 代表的是人機界面交互的原始數據。一共有四種指針事件:
????PointerDownEvent 指針在特定位置與屏幕接觸
????PointerMoveEvent 指針從屏幕的一個位置移動到另外一個位置
????PointerUpEvent 指針與屏幕停止接觸
????PointerCancelEvent 指針因為一些特殊情況被取消
Pointer的原理是什么呢?
????在指針落下時,框架做了一個 hit test 的操作,確定與屏幕發生接觸的位置上有哪些Widget以及分發給最內部的組件去響應;
????事件會沿著最內部的組件向組件樹的根冒泡分發;
????并且不存在用于取消或者停止指針事件進一步分發的機制;
原始指針事件使用Listener來監聽:
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的封裝,官方建議開發中盡可能使用Gesture,而不是Pointer
Gesture分層非常多的種類:
點擊:
????onTapDown:用戶發生手指按下的操作
????onTapUp:用戶發生手指抬起的操作
????onTap:用戶點擊事件完成
????onTapCancel:事件按下過程中被取消
雙擊:
????onDoubleTap:快速點擊了兩次
長按:
????onLongPress:在屏幕上保持了一段時間
縱向拖拽:
????onVerticalDragStart:指針和屏幕產生接觸并可能開始縱向移動;
????onVerticalDragUpdate:指針和屏幕產生接觸,在縱向上發生移動并保持移動;
????onVerticalDragEnd:指針和屏幕產生接觸結束;
橫線拖拽:
????onHorizontalDragStart:指針和屏幕產生接觸并可能開始橫向移動;
????onHorizontalDragUpdate:指針和屏幕產生接觸,在橫向上發生移動并保持移動;
????onHorizontalDragEnd:指針和屏幕產生接觸結束;
移動:
????onPanStart:指針和屏幕產生接觸并可能開始橫向移動或者縱向移動。如果設置了 onHorizontalDragStart 或者 onVerticalDragStart,該回調方法會引發崩潰;
????onPanUpdate:指針和屏幕產生接觸,在橫向或者縱向上發生移動并保持移動。如果設置了 onHorizontalDragUpdate 或者 onVerticalDragUpdate,該回調方法會引發崩潰。
????onPanEnd:指針先前和屏幕產生了接觸,并且以特定速度移動,此后不再在屏幕接觸上發生移動。如果設置了 onHorizontalDragEnd 或者 onVerticalDragEnd,該回調方法會引發崩潰。
從Widget的層面來監聽手勢,我們需要使用:GestureDetector
????當然,我們也可以使用RaisedButton、FlatButton、InkWell等來監聽手勢
????globalPosition用于獲取相對于屏幕的位置信息
????localPosition用于獲取相對于當前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相當于是一種訂閱者模式,通過一個全局的對象來管理;
????這個EventBus我們可以自己實現,也可以使用第三方的EventBus;
這里我們直接選擇第三方的EventBus:
dependencies:
? event_bus:^1.1.1
第一:我們需要定義一個希望在組件之間傳遞的對象:
????我們可以稱之為一個時間對象,也可以是我們平時開發中用的模型對象(model)
class UserInfo {
? String nickname;
? int level;
? UserInfo(this.nickname, this.level);
}
第二:創建一個全局的EventBus對象
final eventBus = EventBus();
第三:在某個Widget中,發出事件:
class HYButton extends StatelessWidget {
? @override
? Widget build(BuildContext context) {
? ? return RaisedButton(
? ? ? child: Text("HYButton"),
? ? ? onPressed: () {
? ? ? ? final info = UserInfo("why", 18);
? ? ? ? eventBus.fire(info);
? ? ? },
? ? );
? }
}
第四:在某個Widget中,監聽事件
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),);
? }
}