《Flutter》-- 7.事件处理

2022-04-07 16:23:51 浏览数 (2)

参阅书籍:

《Flutter跨平台开发入门与实践》-- 向治洪(著)

7. 事件处理

7.1 原始指针事件

7.1.1 基本概念

一个完整的原始指针事件主要由手指按下、手指移动、手指抬起以及触摸取消构成,更高基本的手势都基于这些原始事件。

在Flutter的原始指针事件模型中,在手指接触屏幕发起触摸事件时,Flutter会首先确定手指与屏幕发生接触的位置上究竟有哪些组件,然后通过命中测试(Hit Test)交给最内层的组件去响应。

Flutter无法像浏览器冒泡那样取消或者停止事件的进一步分发,只能通过执行命中测试去调整组件的事件触发时机。

PointerDownEvent、PointerMoveEvent和PointerUpEvent是Flutter的原始指针事件的基本组成部分,分别对应手指按下、移动和抬起事件,它们都是PointerEvent的子类。

在Flutter的事件模型中PointerEvent是Flutter原始指针事件的基础类,可以用它获取当前指针的一些信息:

1)position:全局坐标的偏移量;

2)delta:两次指针移动事件的距离;

3)pressure:按压力度,如果手机屏幕支持压力传感器,此属性会返回压力值,如果手机不支持则始终返回1;

4)orientation:指针移动方向,是一个角度值。

对于组件层面的原始指针事件的监听,Flutter提供了一个Listener,可以用它监听包裹的子组件的原始指针事件。

代码语言:javascript复制
Listener(
  onPointerDown: (downPointEvent) {
    //按下回调
    ...
  },
  onPointerMove: (movePointEvent) {
    //移动回调
    ...
  },
  onPointerUp: (upPointEvent) {
    //抬起回调
    ...
  },
  child: Container(
    child: Text(‘Listener事件监听’);
  )
)

原始指针事件还提供了behavior属性,它决定子组件如何响应命中测试,它的值类型为HitTestBehavior,是一个枚举类型,有3个枚举值:

1)deferToChild:子组件一个接一个地进行命中测试,如果子组件中有通过命中测试的,则当前组件会收到指针事件,并且其父组件会收到指针事件;

2)opaque:在进行命中测试时,当前组件会被当成不透明进行处理,单击的响应区域即为单击区域;

3)translucent:设置此属性后,组件自身和底部可视区域都能够响应命中测试,即点击顶部组件时,顶部组件和底部组件都可以接收到指针事件。

7.1.2 忽略事件

如果不想让某个子组件响应原始指针事件,可以使用AbsorbPointer或IgnorePointer组件包裹子组件来阻止子组件接收指针事件。

AbsorbPointer组件会参与命中测试,它本身可以接收指针事件,其包裹的子组件不能;而IgnorePointer组件不会参与命中测试,它完全不能接收指针事件。

代码语言:javascript复制
import 'package:flutter/material.dart';

void main() => runApp(PointerWidget());

class PointerWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '事件处理',
      home: Scaffold(
        appBar: AppBar(title: Text('事件处理')),
        body: Listener(
          child: AbsorbPointer(
            child: Listener(
              child: Container(
                color: Colors.red,
                width: 200,
                height: 200,
              ),
              onPointerDown: (e) => print('point in'),
            )
          ),
          onPointerDown: (e) => print('point up'),
        ) 
      )
    );
  }
}

7.2 手势识别组件

7.2.1 基本用法

在Flutter开发中,Gesture API代表手势语义的抽象,从组件层面监听手势可以使用GestureDetector等手势响应组件。

GestureDetector组件是一个处理各种高级用户触摸行为的组件,使用时只需要将它作为父组件包裹在其他子组件外面即可。

7.2.2 常用事件

GestureDetector常用事件:

如果同时监听onTap和onDoubleTap事件时,onTap事件会有200ms左右的延迟。

示例代码:

代码语言:javascript复制
import 'package:flutter/material.dart';

void main() => runApp(GestureDetectorPage());

class GestureDetectorPage extends StatefulWidget {
  State<StatefulWidget> createState() {
    return GestureDetectorPageState();
  }
}

class GestureDetectorPageState extends State<GestureDetectorPage> {
  String operation = 'No Gesture';

  void updateGesture(String text) {
    setState(() => operation = text);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '事件处理',
      home: Scaffold(
        appBar: AppBar(title: Text('事件处理 -- $operation')),
        body: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            GestureDetector(
              child: Container(
                margin: EdgeInsets.only(top: 20),
                alignment: Alignment.center,
                color: Colors.blue,
                width: 150,
                height: 80,
                child: Text(
                  operation,
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 24,
                  ),
                ),
              ),
              onTap: () => updateGesture('Tap'),
              onDoubleTap: () => updateGesture('DoubleTap'),
              onLongPress: () => updateGesture('LongPress'),
            )
          ],
        )
      )
    );
  }
}

示例效果:

7.2.3 拖拽与缩放

在处理拖拽事件时,GestureDetector会将需要监听组件的原点作为本次手势的起点,当用户在监听组件上按下手指时手势识别就开始运行。

示例代码:

代码语言:javascript复制
import 'package:flutter/material.dart';

void main() => runApp(DragPage());

class DragPage extends StatefulWidget {
  State<StatefulWidget> createState() {
    return DragState();
  }
}

class DragState extends State<DragPage> {
  String operation = 'No Gesture';

  void updateGesture(String text) {
    setState(() => operation = text);
  }
  double _top = 0.0;
  double _left = 0.0;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '事件处理--拖拽',
      home: Scaffold(
        appBar: AppBar(title: Text('事件处理--拖拽')),
        body: Stack(
          children: <Widget>[
            Positioned(
              top: _top,
              left: _left,
              child: GestureDetector(
                child: CircleAvatar(
                  radius: 30,
                  backgroundColor: Colors.blue,
                  child: Text(
                    'Drag',
                    style: TextStyle(color: Colors.white),
                  ),
                ),
                onPanDown: (DragDownDetails e) => print('onPanDown: ${e.globalPosition}'),
                onPanUpdate: (DragUpdateDetails e) {
                  setState(() {
                     _left  = e.delta.dx;
                     _top  = e.delta.dy;
                  });
                },
                onPanEnd: (DragEndDetails e) => print('onPanEnd: ${e.velocity.toString()}'),
              ),
            )
          ],
        )
      )
    );
  }
}

示例效果:

如果只需要沿一个方向拖动,可以将onPanUpdate改为onVerticalDragUpdate或者onHorizontalDragUpdate。

可以使用GestureDetector组件的onScaleUpdate实现缩放效果。

示例代码:

代码语言:javascript复制
import 'package:flutter/material.dart';

void main() => runApp(ScalePage());

class ScalePage extends StatefulWidget {
  State<StatefulWidget> createState() {
    return ScaleState();
  }
}

class ScaleState extends State<ScalePage> {
  double width = 100.0;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '事件处理--缩放',
      home: Scaffold(
        appBar: AppBar(title: Text('事件处理--缩放')),
        body: Center(
          child: GestureDetector(
            child: Image.asset('images/qq.png', width: width),
            onScaleUpdate: (ScaleUpdateDetails details) {
              setState(() {
                width = 200 * details.scale.clamp(0.8, 10.0);
              });
            },
          ),
        )
      )
    );
  }
}

7.2.4 手势识别器

GestureDetector封装了Listener的原始指针事件,可以很容易地对各种手势进行识别。GestureDetector是一个抽象类,有多个实现子类,通常一种手势识别器即对应一个GestureDetector的实现类。

示例代码:动态改变富文本文字大小

代码语言:javascript复制
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

void main() => runApp(GestureRecognizerPage());

class GestureRecognizerPage extends StatefulWidget {
  State<StatefulWidget> createState() {
    return GestureRecognizerState();
  }
}

class GestureRecognizerState extends State<GestureRecognizerPage> {
  TapGestureRecognizer recognizer = TapGestureRecognizer();
  bool toggle = false;

  @override
  void dispose() {
    recognizer.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '事件处理--手势识别器',
      home: Scaffold(
        appBar: AppBar(title: Text('事件处理--手势识别器')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('点击文字放大'),
              Text.rich(
                TextSpan(
                  text: '你好,Flutter',
                  style: TextStyle(fontSize: toggle ? 30 : 60),
                  recognizer: recognizer..onTap = () {
                    setState(() {
                      toggle = !toggle;
                    });
                  }
                ), 
              )
            ],
          )
        )
      )
    );
  }
}

示例效果:

使用手势识别器后一定要调用dispose()来释放资源,因为手势识别器内部使用了计时器,不释放的话会造成大量的资源消耗。

7.2.5 手势竞争

对于需要处理多个手势识别的场景,Flutter引入了手势竞技场的概念,用来识别究竟哪个手势最终响应用户事件。手势竞技场通过综合对比用户触摸屏幕的时长、位移以及拖拽方向来确定最终手势。

示例代码:

代码语言:javascript复制
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

void main() => runApp(GestureCompetePage());

class GestureCompetePage extends StatefulWidget {
  State<StatefulWidget> createState() {
    return GestureCompeteState();
  }
}

class GestureCompeteState extends State<GestureCompetePage> {
  double _top = 0.0;
  double _left = 0.0;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '事件处理--手势竞争',
      home: Scaffold(
        appBar: AppBar(title: Text('事件处理--手势竞争')),
        body: Stack(
          children: <Widget>[
            Positioned(
              top: _top,
              left: _left,
              child: GestureDetector(
                child: CircleAvatar(
                  minRadius: 40,
                  child: Text(
                    '手势竞争',
                    style: TextStyle(fontSize: 16)
                  ),
                ),
                onVerticalDragUpdate: (DragUpdateDetails details) {
                  setState(() {
                    _top  = details.delta.dy;
                  });
                },
                onHorizontalDragUpdate: (DragUpdateDetails details) {
                  setState(() {
                    _left  = details.delta.dx;
                  });
                },
              )
            )
          ],
        )
      )
    );
  }
}

示例效果:

每次拖动小球时,小球只会沿着一个方向移动。

0 人点赞