一. 事件監聽
在大前端的開發中,必然存在各種各樣和使用者互動的情況:比如手指點選、手指滑動、雙擊、長按等等。
所有內容首發於公眾號:coderwhy
在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),);
}
}
複製程式碼
備註:所有內容首發於公眾號,之後除了Flutter也會更新其他技術文章,TypeScript、React、Node、uniapp、mpvue、資料結構與演算法等等,也會更新一些自己的學習心得等,歡迎大家關注