Flutter知识库程序员

Flutter学习笔记29-手势监听

2020-12-13  本文已影响0人  zombie

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类中包括当前指针的一些信息,如:

忽略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;
              });
            },
          ),
        ),
      ],
    );
  }
}

缩放

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: '测试'),
          ],
        ),
      ),
    );
  }
}

效果图如下:


效果图

代码传送门

上一篇下一篇

猜你喜欢

热点阅读