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的话,可以使用IgnorePointerAbsorbPointer,这两个组件都能阻止子树接收指针事件,不同的是 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直接可以接收一个子widgetGestureRecognizer是一个抽象类,一种手势的识别器对应一个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: '测试'),
          ],
        ),
      ),
    );
  }
}

效果图如下:


效果图

代码传送门

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,941评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,397评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,345评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,851评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,868评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,688评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,414评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,319评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,775评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,945评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,096评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,789评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,437评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,993评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,107评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,308评论 3 372
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,037评论 2 355

推荐阅读更多精彩内容