02.Flutter事件
Flutter 事件基础
Flutter 原始事件
Listener 组件
Flutter 中可以使用 Listener 来监听原始触摸事件
1
2
3
4
5
6
7
8
9
Listener({
Key key,
this.onPointerDown, // 手指按下回调
this.onPointerMove, // 手指移动回调
this.onPointerUp, // 手指抬起回调
this.onPointerCancel, // 触摸事件取消回调
this.behavior = HitTestBehavior.deferToChild, // 先忽略此参数,后面小节会专门介绍
Widget child
})
参数 PointerDownEvent、 PointerMoveEvent、 PointerUpEvent 都是 PointerEvent 的子类,PointerEvent 类中包括当前指针的一些信息,注意 Pointer,即 “ 指针 “, 指事件的触发者,可以是鼠标、触摸板、手指:
- PointerEvent 属性
- position:它是指针相对于当对于全局坐标的偏移。
- localPosition: 它是指针相对于当对于本身布局坐标的偏移。
- delta:两次指针移动事件(PointerMoveEvent)的距离。
- pressure:按压力度,如果手机屏幕支持压力传感器 (如 iPhone 的 3D Touch),此属性会更有意义,如果手机不支持,则始终为 1。
- orientation:指针移动方向,是一个角度值。
- behavior 属性,它决定子组件如何响应命中测试
忽略指针事件
不想让某个子树响应 PointerEvent 的话,我们可以使用 IgnorePointer
和 AbsorbPointer
,这两个组件都能阻止子树接收指针事件,不同之处在于 AbsorbPointer 本身会参与命中测试,而 IgnorePointer 本身不会参与,这就意味着 AbsorbPointer 本身是可以接收指针事件的 (但其子树不行),而 IgnorePointer 不可以
1
2
3
4
5
6
7
8
9
10
11
12
13
Listener(
child: AbsorbPointer(
child: Listener(
child: Container(
color: Colors.red,
width: 200.0,
height: 100.0,
),
onPointerDown: (event) => print("in ${DateTime.timestamp()}"),
),
),
onPointerDown: (event) => print("up ${DateTime.timestamp()}"),
)
点击 Container 时,由于它在 AbsorbPointer 的子树上,所以不会响应指针事件,所以日志不会输出 “in”,但 AbsorbPointer 本身是可以接收指针事件的,所以会输出 “up”。如果将 AbsorbPointer 换成 IgnorePointer,那么两个都不会输出。
手势识别
Flutter 中用于处理手势的 GestureDetector
和 GestureRecognizer
GestureDetector
GestureDetector 是一个用于手势识别的功能性组件,我们通过它可以来识别各种手势。GestureDetector 内部封装了 Listener,用以识别语义化的手势。
点击、双击、长按
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class GestureTest extends StatefulWidget {
const GestureTest({super.key});
@override
State<StatefulWidget> createState() {
return _GestureTestState();
}
}
class _GestureTestState extends State<GestureTest> {
String _operation = "No Gesture detected!"; //保存事件名
@override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
child: Container(
alignment: Alignment.center,
color: Colors.blue,
width: 200.0,
height: 100.0,
child: Text(
_operation,
style: TextStyle(color: Colors.white),
),
),
onTap: () => updateText("Tap"), // 点击
onDoubleTap: () => updateText("DoubleTap"), // 双击
onLongPress: () => updateText("LongPress"), // 长按
),
);
}
void updateText(String text) {
//更新显示的事件名
setState(() {
_operation = text;
});
}
}
- onTap 点击
- onDoubleTap 双击
- onLongPress 长按
- 当同时监听 onTap 和 onDoubleTap 事件时,当用户触发 tap 事件时,会有 200 毫秒左右的延时,这是因为当用户点击完之后很可能会再次点击以触发双击事件,所以 GestureDetector 会等一段时间来确定是否为双击事件。如果用户只监听了 onTap(没有监听 onDoubleTap)事件时,则没有延时
拖动、滑动
GestureDetector 对于拖动和滑动事件是没有区分的,他们本质上是一样的。GestureDetector 会将要监听的组件的原点(左上角)作为本次手势的原点,当用户在监听的组件上按下手指时,手势识别就会开始
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class _Drag extends StatefulWidget {
@override
_DragState createState() => _DragState();
}
class _DragState extends State<_Drag> with SingleTickerProviderStateMixin {
double _top = 0.0; //距顶部的偏移
double _left = 0.0;//距左边的偏移
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Positioned(
top: _top,
left: _left,
child: GestureDetector(
child: CircleAvatar(child: Text("A")),
//手指按下时会触发此回调
onPanDown: (DragDownDetails e) {
//打印手指按下的位置(相对于屏幕)
print("用户手指按下:${e.globalPosition}");
},
//手指滑动时会触发此回调
onPanUpdate: (DragUpdateDetails e) {
//用户手指滑动时,更新偏移,重新构建
setState(() {
_left += e.delta.dx;
_top += e.delta.dy;
});
},
onPanEnd: (DragEndDetails e){
//打印滑动结束时在x、y轴上的速度
print(e.velocity);
},
),
)
],
);
}
}
- DragDownDetails.globalPosition:当用户按下时,此属性为用户按下的位置相对于屏幕(而非父组件)原点 (左上角) 的偏移。
- DragUpdateDetails.delta:当用户在屏幕上滑动时,会触发多次 Update 事件,delta 指一次 Update 事件的滑动的偏移量
- DragEndDetails.velocity:该属性代表用户抬起手指时的滑动速度 (包含 x、y 两个轴的),示例中并没有处理手指抬起时的速度,常见的效果是根据用户抬起手指时的速度做一个减速动画
单一方向拖动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class _DragVertical extends StatefulWidget {
@override
_DragVerticalState createState() => _DragVerticalState();
}
class _DragVerticalState extends State<_DragVertical> {
double _top = 0.0;
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Positioned(
top: _top,
child: GestureDetector(
child: CircleAvatar(child: Text("A")),
//垂直方向拖动事件
onVerticalDragUpdate: (DragUpdateDetails details) {
setState(() {
_top += details.delta.dy;
});
},
),
)
],
);
}
}
- onVerticalDragUpdate
缩放
GestureDetector 可以监听缩放事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class _Scale extends StatefulWidget {
const _Scale({Key? key}) : super(key: key);
@override
_ScaleState createState() => _ScaleState();
}
class _ScaleState extends State<_Scale> {
double _width = 200.0; //通过修改图片宽度来达到缩放效果
@override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
//指定宽度,高度自适应
child: Image.asset("./images/sea.png", width: _width),
onScaleUpdate: (ScaleUpdateDetails details) {
setState(() {
//缩放倍数在0.8到10倍之间
_width=200*details.scale.clamp(.8, 10.0);
});
},
),
);
}
}
GestureRecognizer
GestureDetector 内部是使用一个或多个 GestureRecognizer 来识别各种手势的,而 GestureRecognizer 的作用就是通过 Listener 来将原始指针事件转换为语义手势,GestureDetector 直接可以接收一个子 widget。GestureRecognizer 是一个抽象类,一种手势的识别器对应一个 GestureRecognizer 的子类,Flutter 实现了丰富的手势识别器,我们可以直接使用。
示例:给一段富文本(RichText)的不同部分分别添加点击事件处理器,但是 TextSpan 并不是一个 widget,这时我们不能用 GestureDetector,但 TextSpan 有一个 recognizer 属性,它可以接收一个 GestureRecognizer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class _GestureRecognizer extends StatefulWidget {
const _GestureRecognizer({Key? key}) : super(key: key);
@override
_GestureRecognizerState createState() => _GestureRecognizerState();
}
class _GestureRecognizerState extends State<_GestureRecognizer> {
TapGestureRecognizer _tapGestureRecognizer = TapGestureRecognizer();
bool _toggle = false; //变色开关
@override
void dispose() {
//用到GestureRecognizer的话一定要调用其dispose方法释放资源
_tapGestureRecognizer.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: Text.rich(
TextSpan(
children: [
TextSpan(text: "你好世界"),
TextSpan(
text: "点我变色",
style: TextStyle(
fontSize: 30.0,
color: _toggle ? Colors.blue : Colors.red,
),
recognizer: _tapGestureRecognizer
..onTap = () {
setState(() {
_toggle = !_toggle;
});
},
),
TextSpan(text: "你好世界"),
],
),
),
);
}
}
注意:使用 GestureRecognizer
后一定要调用其 dispose()
方法来释放资源(主要是取消内部的计时器)。
手势和触摸事件处理
widget 添加点击事件
- widget 支持事件监听,直接传递给它一个函数,并在这个函数里实现响应方法
1
2
3
4
5
6
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () { print('click') },
child: Text('Button')
);
}
- widget 本身不支持事件监听,则在这个 widget 外面包裹一个
GestureDetector
,并给它的 onTap 属性传递一个参数
处理 widget 上的其他手势
使用 GestureDetector
,可以监听多种手势:
- 点击
- onTapDown 在特定位置轻触了屏幕
- onTapUp 在特定位置产生了一个轻触手机,并停止接触屏幕
- onTap 产生了一个轻触手势
- onTapCancel 触发了 onTapDown 但没能触发 tap
- 双击
- onDoubleTap 用户在同一个位置快速点击了两下屏幕
- 长按
- onLongPress 用户在同一个位置长时间接触屏幕
- 垂直拖动
- onVerticalDragStart 接触了屏幕,并且可能会垂直移动
- onVerticalDragUpdate 接触了屏幕,并继续在垂直方向移动
- onVerticalDragEnd 之前接触了屏幕并垂直移动,并在停止接触屏幕前以某个垂直的速度移动
- 水平拖动
- onHorizontalDragStart 接触了屏幕,并且可能会水平移动
- onHorizontalDragUpdate 接触了屏幕,并继续在水平方向移动
- onHorizontalDragEnd 之前接触了屏幕并水平移动,并在停止接触屏幕前以某个水平的速度移动