前言
在Flutter开发App的过程中,我们除了需要灵活的使用各种组件之外,还需要掌握手势的识别,比如我们常常需要在操作App的时候使用到缩放,双击,放大,缩小等操作,这些Flutter都给我们提供了监听的组件GestureDetector。这篇博文将详细介绍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"),); }), ), ), ], ), ); } }
实现效果如下图所示,代码很好理解,这里就不在赘述了: