Flutter学习笔记29-手势监听
Flutter中手势有两个不同的层次:
1.原始指针事件(Pointer Events):描述了屏幕上由触摸板、鼠标、指示笔等触发的位置和指针移动。
2.手势识别(Gesture Detecor):在原始指针上的一种封装。
1.原始指针事件
在移动端,各个平台或UI系统的原始指针事件模型基本都是一致,事件分为三个阶段:手指按下、手指移动、和手指抬起,而更高级别的手势(如点击、双击、拖动等)都是基于这些原始事件的。
当指针按下时,Flutter会对应用程序执行命中测试(Hit Test),以确定指针与屏幕接触的位置存在哪些组件, 指针按下事件(以及该指针的后续事件)然后被分发到由命中测试发现的最内部的组件,然后事件会在组件树中向上冒泡,这些事件会从最内部的组件被分发到组件树根的路径上的所有组件。Flutter中没有机制取消或停止“冒泡”过程。ps:只有通过命中测试的组件才能触发事件。
原始指针事件使用Listener
来监听,Listener构造函数如下:
Listener({
Key key,
this.onPointerDown, //手指按下回调
this.onPointerMove, //手指移动回调
this.onPointerUp,//手指抬起回调
this.onPointerCancel,//触摸事件取消回调
this.behavior = HitTestBehavior.deferToChild, //在命中测试期间如何表现
Widget child
})
代码示例:
class PointerDemo extends StatefulWidget {
@override
_PointerDemoState createState() => _PointerDemoState();
}
class _PointerDemoState extends State<PointerDemo> {
PointerEvent _event;
@override
Widget build(BuildContext context) {
return Listener(
child: Center(
child: Container(
alignment: Alignment.center,
color: Colors.blue,
width: 400,
height: 400,
child: Text(
_event?.toString() ?? "",
style: TextStyle(color: Colors.white),
),
),
),
onPointerDown: (PointerDownEvent event) => setState(() => _event = event),
onPointerMove: (PointerMoveEvent event) => setState(() => _event = event),
onPointerUp: (PointerUpEvent event) => setState(() => _event = event),
);
}
}
运行效果图如下:
效果图
手指在蓝色矩形区域内移动就可看到当前指针偏移,当触发指针事件时,参数PointerDownEvent、PointerMoveEvent、PointerUpEvent都是PointerEvent的子类,PointerEvent类中包括当前指针的一些信息,如:
- position:它是鼠标相对于当对于全局坐标的偏移。
- delta:两次指针移动事件(PointerMoveEvent)的距离。
- pressure:按压力度,如果手机屏幕支持压力传感器(如iPhone的3D Touch),如果手机不支持,则始终为1。
- orientation:指针移动方向,是一个角度值。
忽略PointerEvent
假如不想让某个子树响应PointerEvent的话,可以使用IgnorePointer
和AbsorbPointer
,这两个组件都能阻止子树接收指针事件,不同的是 AbsorbPointer
本身会参与命中测试,而IgnorePointer
本身不会参与,AbsorbPointer
本身是可以接收指针事件的(但其子树不行),而IgnorePointer不可以。代码示例:
class AbsorbPointerDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Listener(
child: AbsorbPointer(
child: Listener(
child: Container(
color: Colors.blue,
width: 200.0,
height: 200.0,
),
onPointerDown: (event) => print("in"),
),
),
onPointerDown: (event) => print("up"),
);
}
}
点击Container时,由于它在AbsorbPointer
的子树上,所以不会响应指针事件,日志不会输出"in",但AbsorbPointer
本身是可以接收指针事件的,所以会输出"up"。如果将AbsorbPointer
换成IgnorePointer
,那么两个都不会输出。
2.手势识别
GestureDetector
GestureDetector
是一个用于手势识别的功能性组件,通过它可以来识别各种手势。GestureDetector
实际上是指针事件的语义化封装。
点击、双击、长按
代码示例:
class GestureDetectorDemo extends StatefulWidget {
@override
_GestureDetectorDemoState createState() => _GestureDetectorDemoState();
}
class _GestureDetectorDemoState extends State<GestureDetectorDemo> {
// 保存事件名
String _operation = "无手势触发!";
@override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
child: Container(
alignment: Alignment.center,
color: Colors.blue,
width: 200.0,
height: 200.0,
child: Text(
_operation,
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
onTap: () => updateText("点击"),
onDoubleTap: () => updateText("双击"),
onLongPress: () => updateText("长按"),
),
);
}
void updateText(String text) {
// 更新显示的事件名
setState(() {
_operation = text;
});
}
}
拖动、滑动
一次完整的手势过程是指用户手指按下到抬起的整个过程,期间,用户按下手指后可能会移动,也可能不会移动。GestureDetector
对于拖动和滑动事件是没有区分的,本质上是一样的。GestureDetector
将要监听的组件的原点(左上角)作为本次手势的原点,当用户在监听的组件上按下手指时,手势识别就会开始。代码示例:
class __DragDemoState extends State<_DragDemo> {
// 距离顶部的偏移
double _top = 0.0;
// 距离左边的偏移
double _left = 0.0;
@override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned(
top: _top,
left: _left,
child: GestureDetector(
child: CircleAvatar(child: Text('Drag')),
// 手指按下回调
onPanDown: (DragDownDetails detail) {
// 手指按下的相对于屏幕的位置
print('用户手指按下:${detail.globalPosition}');
},
// 手指滑动回调
onPanUpdate: (DragUpdateDetails detail) {
// 手指滑动更新偏移量
setState(() {
_left += detail.delta.dx;
_top += detail.delta.dy;
});
},
// 手指停止滑动回调
onPanEnd: (DragEndDetails detail) {
// 滑动结束时在x、y轴上的速度
print(detail.velocity);
},
// // 垂直方向拖动事件
// onVerticalDragUpdate: (DragUpdateDetails details) {
// setState(() {
// _top += details.delta.dy;
// });
// },
// 水平方向拖动事件
onHorizontalDragUpdate: (DragUpdateDetails details) {
setState(() {
_left += details.delta.dx;
});
},
),
),
],
);
}
}
- DragDownDetails.globalPosition
用户按下的位置,相对于屏幕原点(左上角)的偏移。 - DragUpdateDetails.delta
用户在屏幕上滑动时,会触发多次Update事件,delta指一次Update事件的滑动的偏移量。 - DragEndDetails.velocity
用户抬起手指时的滑动速度(包含x、y两个轴的) - onVerticalDragUpdate
只沿垂直方向来拖动 - onHorizontalDragUpdate
只沿水平方向来拖动
缩放
GestureDetector
可以监听缩放事件。代码示例:
clclass _ScaleDemo extends StatefulWidget {
@override
__ScaleDemoState createState() => __ScaleDemoState();
}
class __ScaleDemoState extends State<_ScaleDemo> {
double _width = 200.0;
@override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
child: Container(
color: Colors.red,
width: _width,
height: 200.0,
),
onScaleUpdate: (ScaleUpdateDetails detail) {
// 缩放倍数在0.8到10倍之间
_width = 200 * detail.scale.clamp(.8, 10.0);
},
),
);
}
}
GestureRecognizer
GestureDetector
内部是使用一个或多个GestureRecognizer
来识别各种手势的,而GestureRecognizer
的作用就是通过Listener
来将原始指针事件转换为语义手势,GestureDetector
直接可以接收一个子widget
。GestureRecognizer
是一个抽象类,一种手势的识别器对应一个GestureRecognizer
的子类,Flutter实现了丰富的手势识别器,可以直接使用。
RichText
可以给不同部分分别添加点击事件处理,但是TextSpan
并不是一个widget
,这时不能用GestureDetector
,但TextSpan
有一个recognizer
属性,它可以接收一个GestureRecognizer
。代码示例:
class _GestureRecognizerDemo extends StatefulWidget {
@override
__GestureRecognizerDemoState createState() => __GestureRecognizerDemoState();
}
class __GestureRecognizerDemoState extends State<_GestureRecognizerDemo> {
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: 20.0,
color: _toggle ? Colors.blue : Colors.red,
),
recognizer: _tapGestureRecognizer
..onTap = () {
setState(() {
_toggle = !_toggle;
});
},
),
TextSpan(text: '测试'),
],
),
),
);
}
}
效果图如下:
效果图