在Flutter开发App的过程中,我们除了需要灵活的使用各种组件之外,还需要掌握手势的识别,比如我们常常需要在操作App的时候使用到缩放,双击,放大,缩小等操作,这些Flutter都给我们提供了监听的组件GestureDetector。这篇博文将详细介绍GestureDetector手势识别的使用规则。(拖动手势监听)
我们前面提到过,在Flutter开发中,一切皆是组件,所以GestureDetector同样是一个组件,我们使用它,通常是作为一个父Widget包裹一个子Widget外面(也就是你需要捕捉那个组件的手势,就把GestureDetector套在外外面),而内部我们通过onTap回调来实现其点击的效果,代码如下:
GGestureDetector(
onTap:(){
print("tap");
},
child:Container{
padding:EdgeInsets.all(20),
decoration:BoxDecoration(
color:Theme.of(context).buttonColor,
borderRadius:BorderRadius.circular(8.0),
),
child:new Text("文本"),
}
);
比如上面的代码就是改造Text组件成为按钮的方式,这里捕捉了点击事件。
GestureDetector手势识别不仅仅只有onTap事件,还有很多很多的常用事件,博主通过一张表格将它们全部列举了出来,方便大家查阅:
属性 | 取值意义 |
---|---|
onTapDwon | 当按下屏幕时触发 |
onTap | 当与屏幕短暂地触碰时触发,最常用 |
onTapUp | 当用户停止触碰屏幕时触发 |
onTapCancel | 当用户触摸屏幕,但没有完成Tap事件时触发 |
onDoubleTap | 快速双击屏幕时触发 |
onLongPress | 当长按屏幕时触发(与屏幕接触事件必须超过500ms) |
onPanUpdate | 当在屏幕上移动时触发 |
onVerticalDragDown | 当手指触碰屏幕且准备往屏幕垂直方向移动时触发 |
onVerticalDragStart | 当手指触碰屏幕且开始往屏幕垂直方向移动时触发 |
onVerticalDragUpdate | 当手指触碰屏幕且开始往屏幕垂直方向移动并发生位移时触发 |
onVerticalDragEnd | 当用户完成垂直方向触摸屏幕时触发 |
onVerticalDragCancel | 当用户中断了onVerticalDragDown时触发 |
onHorizontalDragDown | 当手指触摸屏幕且准备往屏幕水平方向移动时触发 |
onHorizontalDragStart | 当手指触摸屏幕且开始往屏幕水平方向移动时触发 |
onHorizontalDragUpdate | 当手指触摸屏幕且开始往屏幕水平方向移动并发生位移时触发 |
onHorizontalDragEnd | 当用户完成水平方向触摸屏幕时触发 |
onHorizontalDragCancel | 当用户中断了onHorizontalDragDown时触发 |
onPanDown | 当用户触摸屏幕时触发 |
onPanStart | 当用户触摸屏幕并开始移动时触发 |
onPanUpdate | 当用户触摸屏幕并产生移动时触发 |
onPanEnd | 当用户完成触摸屏幕时触发 |
onScaleStart | 当用户触摸屏幕并开始缩放时触发 |
onScaleUpdate | 当用户触摸屏幕并产生缩放时触发 |
onScaleEnd | 当用户完成缩放时触发 |
虽然说上面表格非常详细,但其中有些事件是互斥的,并不能同时存在,比如onVerticalUpdate,onHorizontalUpdate,onPanUpdate这些三个事件都不能同时存在,否则会报错。
另外,onPanUpdate和onScaleUpdate也不能同时存在,这是因为在Gesture识别器里,Scale操作是Pan操作的超集。
既然我们了解了如何使用这些事件,那么,我们就应该实践起来,这里小编将用上面的事件实现一个缩放效果,代码如下:
class _MyHomePageState extends State<MyHomePage>{
double _top=0.0;
double _left=0.0;
double _size=100.0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: new Text("忽略事件"),),
body: Stack(
children: <Widget>[
Positioned(
top: this._top,
left: this._left,
child: GestureDetector(
child: FlutterLogo(
size: this._size,
),
onScaleUpdate: (e){
setState(() {
this._size=300*e.scale.clamp(.5, 10.0);缩放倍数在0.5到10倍之间
});
},
),
),
],
),
);
}
}
代码非常的简单,就是缩放FlutterLogo的大小,实现的效果如下图所示:
既然我们已经了解这么多事件,不妨多来一个事件,也就是App中常用的拖拽操作,代码如下(略微改改上上面的代码就行):
class _MyHomePageState extends State<MyHomePage>{
double _top=0.0;
double _left=0.0;
double _size=100.0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: new Text("忽略事件"),),
body: Stack(
children: <Widget>[
Positioned(
top: this._top,
left: this._left,
child: GestureDetector(
child: FlutterLogo(
size: this._size,
),
onPanUpdate: (e){
setState(() {
this._left+=e.delta.dx;
this._top+=e.delta.dy;
});
},
),
),
],
),
);
}
}
仅仅改变了事件的代码,前面说过onPanUpdate与onScaleUpdate不能同时存在,所以不能直接添加事件,需要删除onScaleUpdate后在添加。
Notification是“通知”的意思,这和Android中不一样。在Flutter里,Notification会沿着当前的context节点从下往上传递,所有父节点都可以通过NotificationListener来监听通知,这种由子向父的传递方式,我们称为“通知冒泡”,并继承至Notification,而父Widget使用NotificationListener进行监听并捕获通知。常用的NotificatioListener有LayoutChangeNotification,SizeChangedLayoutNotifier,ScrollNotification等。比如本篇将监听ListView滚动状态:是通过NotificationListener里的onNotification回调方法来判断状态。代码如下:
class _MyHomePageState extends State<MyHomePage> {
String _message = "我是通知";
void _onScrollStart(ScrollMetrics scrollMetrics){
print(scrollMetrics.pixels);
setState(() {
this._message="滚动开始";
});
}
void _onScrollEnd(ScrollMetrics scrollMetrics){
print(scrollMetrics.pixels);
setState(() {
this._message="滚动结束";
});
}
void _onScrollUpdate(ScrollMetrics scrollMetrics){
print(scrollMetrics.pixels);
setState(() {
this._message="滚动进行时";
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: new Text("事件通知"),
),
body: Column(
children: <Widget>[
Container(
height: 50.0,
color: Colors.green,
child: Center(
child: new Text(this._message),
),
),
Expanded(
child: NotificationListener<ScrollNotification>(
// ignore: missing_return
onNotification: (scrollNotification) {
if (scrollNotification is ScrollStartNotification) {
this._onScrollStart(scrollNotification.metrics);
} else if (scrollNotification is ScrollUpdateNotification) {
this._onScrollUpdate(scrollNotification.metrics);
} else if (scrollNotification is ScrollEndNotification) {
this._onScrollEnd(scrollNotification.metrics);
}
},
child: ListView.builder(
itemCount: 30,
itemBuilder: (context, index) {
return ListTile(title: Text("索引:$index"),);
}),
),
),
],
),
);
}
}