在 Flutter 中处理手势
在创建应用程序时,您必须处理用户手势,例如触摸和拖动。这使您的应用程序具有交互性。
为了有效地处理手势,您需要倾听手势并做出响应。Flutter 提供了各种小部件,有助于为您的应用程序添加交互性。
在本文中,我们将使用GestureDetector小部件来处理手势。
介绍
一些小部件,如Container
和Card
小部件,没有检测手势的内置方式。这些小部件被包裹在小部件中,GestureDetector
小部件纯粹用于检测手势,不会像涟漪效应那样给出任何视觉响应。
该GestureDetector
小部件通过识别具有回调定义的手势并相应地响应事件来工作。如果要禁用手势,则会将null
值传递给回调。
以下是GestureDetector
小部件捕获的常见手势、它们对应的事件和可能的应用程序(所有插图均归功于Luke Wroblewski 的触摸手势参考指南):
轻敲
用户用指尖短暂地触摸了屏幕。
onTapDown
— 当用户接触屏幕时触发,可能是点击onTapUp
— 当用户停止接触屏幕时触发onTap
— 当用户短暂触摸屏幕时触发onTapCancel
— 当触发的事件onTapDown
不是点击时触发
点击手势的可能应用包括:
- 选择
- 取消
- 提交
双击
用户快速连续两次在同一位置点击屏幕。
onDoubleTapDown
— 当用户接触屏幕时触发,可能是双击onDoubleTap
— 当用户快速连续两次点击同一位置的屏幕时触发onDoubleTapCancel
— 当触发的事件onDoubleTapDown
不是双击时触发
双击手势的可能应用包括:
- 喜欢不喜欢
- 屏幕开/关
- 调整图像大小
长按
用户在同一位置长时间接触屏幕。
onLongPressDown
— 当用户接触屏幕时触发,可能是长按onLongPressStart
— 检测到长按开始时触发onLongPress
— 检测到长按时触发onLongPressMoveUpdate
- 当检测到长按并且用户拖动手指时触发onLongPressEnd
— 检测到长按结束时触发onLongPressUp
— 检测到长按结束时触发;长按后联系人已删除onLongPressCancel
— 当触发的事件onLongPressDown
不是长按时触发
长按手势的可能应用包括:
- 显示更多选项
- 移动图标
捏拉
用户捏住或展开屏幕。
onScaleStart
— 当与屏幕接触已建立焦点且初始比例为 1.0 时触发onScaleUpdate
— 当与屏幕的接触指示新的焦点和/或比例时触发onScaleEnd
— 当用户不再接触screenPossible
缩放手势的应用程序时触发
缩放手势的用途包括:
- 放大/缩小
- 回转
垂直拖动
用户接触屏幕并以稳定的方式垂直移动指尖。
onVerticalDragDown
— 当用户接触屏幕时触发,可能会垂直移动onVerticalDragStart
— 当用户接触屏幕并开始垂直移动时触发onVerticalDragUpdate
— 当垂直移动的接触再次沿垂直方向移动时触发onVerticalDragEnd
— 在检测到垂直拖动结束时触发onVerticalDragCancel
— 当触发的事件onVerticalDragDown
不是垂直拖动时触发
垂直拖动手势的可能应用包括:
- 滚动
水平拖动
用户接触屏幕并以稳定的方式水平移动指尖。
onHorizontalDragDown
— 当用户接触屏幕时触发,可能会水平移动onHorizontalDragStart
— 当用户接触屏幕并开始水平移动时触发onHorizontalDragUpdate
— 当水平移动的接触再次在水平方向移动时触发onHorizontalDragEnd
— 当检测到水平拖动结束时触发onHorizontalDragCancel
— 当触发的事件onHorizontalDragDown
不是水平拖动时触发
水平拖动手势的可能应用包括:
- 删除
- Archive
- 导航到不同的页面
这不是检测到的手势的完整列表。查看官方文档以获取完整列表。
好的,上面介绍了手势相关的使用场景等,接下来
让我们试试吧!
入门
要使用GestureDetector
小部件:
- 用
GestureDetector
小部件包装所需的小部件。 - 为您希望检测的手势传递回调。
- 相应地更新应用程序
我们将构建一个简单的演示应用程序来处理单击、双击、长按和缩放手势。
创建一个新的 Flutter 应用
创建一个新的 Flutter 应用程序并清除文件中的默认代码。main.dart
更新用户界面
我们将创建下面的四个文件。您可以在此处查看文件夹结构。
main.dart
import 'package:flutter/material.dart'; import 'presentation/my_app_widget.dart'; void main() { runApp(const MyApp()); }
my_app_widget.dart
import 'package:flutter/material.dart'; import 'home_page.dart'; class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Gesture Detector Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const HomePage(), ); } }
home_page.dart
import 'package:flutter/material.dart'; import 'widgets/widgets.dart'; class HomePage extends StatelessWidget { const HomePage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { final height = MediaQuery.of(context).size.height; final width = MediaQuery.of(context).size.width; return Scaffold( body: Padding( padding: EdgeInsets.symmetric( horizontal: width * 0.1, vertical: height * 0.2), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: const [ MyCardWidget(), MyFavoriteIconWidget() ], ), ), ); } }
my_card_widget.dart
import 'dart:math'; import 'package:flutter/material.dart'; class MyCardWidget extends StatefulWidget { const MyCardWidget({ Key? key, }) : super(key: key); @override State<MyCardWidget> createState() => _MyCardWidgetState(); } class _MyCardWidgetState extends State<MyCardWidget> { @override Widget build(BuildContext context) { return const Card( child: SizedBox( height: 300, width: 300, ), color: Colors.yellow, ); } }
my_favorite_icon_widget.dart
import 'package:flutter/material.dart'; class MyFavoriteIconWidget extends StatefulWidget { const MyFavoriteIconWidget({ Key? key, }) : super(key: key); @override State<MyFavoriteIconWidget> createState() => _MyFavoriteIconWidgetState(); } class _MyFavoriteIconWidgetState extends State<MyFavoriteIconWidget> { @override Widget build(BuildContext context) { return const Icon( Icons.favorite_border, size: 40, ); } }
您的最终应用程序应如下所示:
现在我们已经准备好了 UI,让我们处理一些手势。
处理点击手势
在您的文件中:my_favorite_icon_widget.dart
- 将选定的标志属性添加到
StatefulWidget
bool isSelected = false;
Icon
用GestureDetector
小部件包裹小部件- 为
onTap
属性提供非空回调 - 根据 flag 属性值改变图标和图标颜色
class _MyFavoriteIconWidgetState extends State<MyFavoriteIconWidget> { bool isSelected = false; @override Widget build(BuildContext context) { return GestureDetector( onTap: (){ setState(() { isSelected = !isSelected; }); }, child: Icon( isSelected ? Icons.favorite: Icons.favorite_border, size: 40, color: isSelected? Colors.red: Colors.black , )); } }
处理双击手势
在您的文件中:my_card_widget.dart
- 添加颜色属性
- 用
Card
小部件包装小GestureDetector
部件 - 为
onDoubleTap
属性提供非空回调 - 根据颜色属性的值改变卡片的颜色
class _MyCardWidgetState extends State<MyCardWidget> { Color bgColor = Colors.yellow; @override Widget build(BuildContext context) { return GestureDetector( onDoubleTap: (){ setState(() { bgColor = Colors.primaries[Random().nextInt(Colors.primaries.length)]; }); }, child: Card( child: const SizedBox( height: 300, width: 300, ), color: bgColor, ), ); } }
处理长按手势
在您的文件: 1.添加标记属性 2.提供一个非空回调的财产 基础上的价值 3.更改卡的形状属性my_card_widget.dart
makeCircular
onLongPress
makeCircular
class _MyCardWidgetState extends State<MyCardWidget> { Color bgColor = Colors.yellow; bool makeCircular = false; @override Widget build(BuildContext context) { return GestureDetector( onLongPress: (){ setState(() { makeCircular = !makeCircular; }); }, child: Card( shape: makeCircular? const CircleBorder(): const RoundedRectangleBorder(), child: const SizedBox( height: 300, width: 300, ), color: bgColor, ), ); } }
处理缩放手势
在您的文件中: 1. 添加一个属性 2. 添加一个属性 3. 为该属性提供一个非空回调——建立一个初始比例 4.为该属性提供一个非空回调——建立一个新的比例 5. 提供一个属性的非空回调— 返回初始比例 6.用小部件包裹小部件 7. 根据my_card_widget.dart
_scaleFactor
_baseFactor
onScaleStart
onScaleUpdate
onScaleEnd
Card``Transorm.scale
_scaleFactor
class _MyCardWidgetState extends State<MyCardWidget> { Color bgColor = Colors.yellow; bool makeCircular = false; double _scaleFactor = 0.5; double _baseScaleFactor = 0.5; @override Widget build(BuildContext context) { return GestureDetector( onScaleStart: (details){ _baseScaleFactor = _scaleFactor; }, onScaleUpdate: (details){ setState(() { _scaleFactor = _baseScaleFactor * details.scale; }); }, onScaleEnd: (details){ // return to initial scale _scaleFactor = _baseScaleFactor; }, child: Transform.scale( scale: _scaleFactor, child: Card( shape: makeCircular? const CircleBorder(): const RoundedRectangleBorde(), child: const SizedBox( height: 300, width: 300, ), color: bgColor, ), ); } }
<img src="https://luckly007.oss-cn-beijing.aliyuncs.com/images/1.jpg" alt="1" style="zoom:25%;" />
<img src="https://luckly007.oss-cn-beijing.aliyuncs.com/images/2.jpg" alt="2" style="zoom:25%;" />
手势消歧
那么当我们onGestureDown
为单击和双击提供 事件回调,并且发生两个延迟的、短暂的触摸事件时会发生什么?
考虑下图:
当识别出两个或多个具有非空回调的手势事件时,Flutter 通过让每个识别器加入手势领域来消除用户想要的手势的歧义。在手势竞技场中, “战斗” 事件和获胜事件生效,而失败事件被取消。
手势领域考虑了以下因素:
- 用户触摸屏幕的时间长度
- 每个方向移动的像素数
- 哪个手势在竞技场
- 哪个手势宣告胜利
这些是竞争状态:
- 也许——也许是手势
- 保持——如果它以特定的方式发展,可能是一种姿态;对于我们的案例,如果第二次点击发生在预期时间内,则发生了一次点击并且可能是两次点击
- 是——获得优先
- 取消——退出竞争
例如,假设发生以下情况:
1.onTapDown
和onDoubleTapDown
被触发
2 两个手势竞争 3.单击手势获胜并执行onTap
回调(回调)
4.单击手势失败并取消(onDoubleTapCancel
触发)
对于我们的案例,点击手势竞争是因为:
- 两次敲击之间的持续时间被延迟
- 点击手势以“是”宣布胜利
- 点击手势是取消双击后剩下的手势,没有其他竞争对手
结论
我们已经浏览了GestureDetector
小部件并了解了它的工作原理。我们已经学会了如何使用它为我们的应用程序添加交互性,并且我们已经实现了一些常见的手势,比如点击、长按、双击和缩放。我们最后还研究了手势消歧。
有了这些知识,我们现在对GestureDetector
小部件有了更好的理解,并且可以轻松地使用它的任何属性来识别手势。随意玩弄不同的手势。您可以在 GitHub 上找到演示应用程序。