在大前端的开发中,必然存在着各种各样和用户交互的情况: 比如手指点击,手指滑动、双击、长按等
在Flutter中,手势有两个不同的层次:
Pointer 代表的是人机界面交互的原始数据。一共有四种指针事件:
PointerDownEvent
指针在特定位置与屏幕接触PointerMoveEvent
指针从屏幕的一个位置移动到另外一个位置PointerUpEvent
指针与屏幕停止接触PointerCancelEvent
指针因为一些特殊情况被取消Pointer的原理是什么呢?
hit test
的操作,确定与屏幕发生接触的位置上有哪些Widget
以及分发给最内部的组件去响应;原始指针事件使用Listener来监听:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Listener(
onPointerDown: (event) {
print("指针按下:event=====${event}");
},
onPointerMove: (event) {
print("指针移动:event=======$event");
},
onPointerUp: (event) {
print("指针抬起:event=====$event");
},
child: Container(
width: 200,
height: 200,
color: Colors.red,
),
),
)
)
);
}
}
Gesture是对一系列Pointer的封装,官方建议开发中尽可能使用Gesture,而不是Pointer
Gesture分层非常多的种类:
点击:
onTapDown
:用户手指按下操作onTapUp
: 用户手指抬起操作onTap
:用户点击事件完成onTapCancel
:事件按下发现过程中被取消onDoubleTap
:快速点击了两次长按:
onLongPress
:在屏幕上保持了一段时间纵向拖拽:
onVerticalDragStart
:指针和屏幕产生接触并可能开始纵向移动onVerticalDragUpdate
:指针和屏幕产生接触,在纵向上发生移动并保持移动onVerticalDragEnd
:指针和屏幕产生接触结束横向拖拽:
onHorizontalDragStart
:指针和屏幕产生接触并可能开始横向移动onHorizontalDragUpdate
:指针和屏幕产生接触,在横向上发生移动并保持移动onHorizontalDragEnd
:指针和屏幕产生接触结束移动:
onPanStart
:指针和屏幕产生接触并可能开始横向移动或者纵向移动,如果设置了onHorizontalDragStart
或则onVerticalDragStart
,指针移动具体方向,回调具体方向的监听方法,如果没有设置监听,就回直接回调onPanStart
这个方法onPanUpdate
:指针和屏幕产生接触,在横向或者纵向上发生移动并保持移动。如果设置了onHorizontalDragUpdate
或则onVerticalDragUpdate
,指针移动具体方向,回调具体方向的监听方法,如果没有设置监听,就回直接回调onPanUpdate
这个方法onPanEnd
:指针先前和屏幕产生了接触,并且以特定速度移动,此后不再在屏幕接触上发生移动。如果设置了onHorizontalDragEnd
或则onVerticalDragEnd
,指针移动具体方向,回调具体方向的监听方法,如果没有设置监听,就回直接回调onPanEnd
这个方法从Widget的层面来监听手势,我们需要使用:GestureDetector
globalPosition
用于获取相对于屏幕的位置信息localPosition
:用于获取相对于当前Widget的位置信息如果手势存在嵌套关系的时候, 手势时间可能会传递到外面,而且没有办法拦截这种传递
案例: 手势具有一个穿透效果, 现在我只想监听黄色区域的手势, 不想监听红色区域的手势
当我们直接使用两个Contain嵌套发现,第二个Contain的大小设置不管作用,会直接扩充到第一个Contain的大小,这个时候我们有两种方式来解决:
第一种:设置外层Contain的alignment
属性,该属性会给里层的widget自动包括一层Align
d widget,这样就不是Contain的直接嵌套,里层的Contain设置的宽高就起作用了
class GYConten extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return GestureDetector(
onTapDown: (details){
print("外层Contain手势点击事件=======");
},
child: Container(
width: 200,
height: 200,
color: Colors.red,
//获则设置alignment属性
alignment: Alignment.center,
child: GestureDetector(
onTapDown: (details) {
print("里层Contain手势点击事件--------");
},
child: Container(
//如果外层包裹的是一层Contain并且设置了大小,那么当前Contain设置大小没有作用,会直接扩从到跟外层的Contain一样的大小
width: 100,
height: 100,
color: Colors.orange,
),
)
),
);
}
}
由执行结果可知, 不段点击里层的Contain,调用里层的onTapDown
方法,偶尔会传递到外层Contain的onTapDown
的方法调用, 如何解决这个问题了 , 我们可以使用IgnorePointer
来包裹里层的GestureDetector
对象,忽略该层手势的事件,间接的组织了消息往外传递
第二种: 使用stack来解决这种问题, 因为stack 也可以重叠 ,可以比较好点 解决这种问题
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: GestureDetector(
child: Stack(
alignment: Alignment.center,
children: [
Container(
width: 200,
height: 200,
color: Colors.red,
),
Container(
width: 100,
height: 100,
color: Colors.orange,
)
]),
),
)));
}
在组件之间如果有事件需要传递,一方面可以一层层传递,另一方面我们也可以使用一个EventBus
工具来完成
其实EventBus在Vue、React中都是一种非常常见的跨组件通信的方式:
这里我们直接选择第三方的EventBus:
dependencies:
event_bus: ^2.0.0
如何传递消息, 该三方库建议我们在传递消息的时候,见一个事件模型:
event_bus
的使用// 1.创建一个事件对象
class UserInfoEvent {
String name;
String level;
UserInfoEvent(this.name, this.level);
}
//2.创建一个全局的eventBus对象
final eventBus = EventBus();
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child:Column(
mainAxisAlignment: MainAxisAlignment.center ,
children: [
GYButton(),
GYText()
],
),
)));
}
}
class GYButton extends StatelessWidget {
const GYButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child: ElevatedButton(
child: Text("发出事件"),
onPressed: (){
print("按钮点击事件------");
//3. 发出消息
final userInfo = UserInfoEvent("zhangsan", "事件传递");
eventBus.fire(userInfo);
},
),
);
}
}
class GYText extends StatefulWidget {
const GYText({Key? key}) : super(key: key);
@override
_GYTextState createState() => _GYTextState();
}
class _GYTextState extends State<GYText> {
String message = "初始化信息";
@override
void initState() {
// TODO: implement initState
super.initState();
//监听事件,收取消息
eventBus.on<UserInfoEvent>().listen((event) {
setState(() {
message = "${event.name}-${event.level}";
});
});
}
@override
Widget build(BuildContext context) {
return Container(
child: Text(message),
);
}
}