flutter 手势识别GestureDetector

GestureDetector是一个用于手势识别的功能性Widget,我们通过它可以来识别各种手势,它是指针事件的语义化封装

1.示例

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

class MyGesture extends StatefulWidget {
  @override
  State createState() {
    // TODO: implement createState
    return _MyGesture();
  }
}

class _MyGesture extends State {
  String _eTxt = "No Gesture";
  double _left = 20.0;
  double _top = 20.0;

  bool _toogle = false; //变色开关

  TapGestureRecognizer _tp = new TapGestureRecognizer();  //初始化tab的GestureRecognizer

  void updateText(str) {
    setState(() {
      _eTxt = str;
    });
  }

  @override
  void dispose() {
     //用到GestureRecognizer的话一定要调用其dispose方法释放资源
    _tp.dispose();
    super.dispose();
  }
  
  // 点击、双击、长按
  Widget _getGes() {
    return GestureDetector(
      child: Container(
        width: 150.0,
        height: 150.0,
        color: Colors.black12,
        alignment: Alignment.center,
        child: Text(
          _eTxt,
          style: TextStyle(color: Colors.white),
        ),
      ),
      onTap: () => updateText("tab"), //单击
      onDoubleTap: () => updateText("double tab"), //双击
      onLongPress: () => updateText("long press"), //长按
    );
  }

  //拖动、滑动
  Widget _drag() {
    return ConstrainedBox(
      constraints: BoxConstraints.tightFor(width: 200.0, height: 200.0),
      child: Stack(
        children: [
          Container(
            width: 200.0,
            height: 200.0,
            color: Colors.black12,
          ),
          Positioned(
            left: _left,
            top: _top,
            child: GestureDetector(
              child: CircleAvatar(
                radius: 20.0,
                child: Text("拖动"),
              ),
              onPanDown: (e) => print("按下的位置${e.globalPosition}"), //手指按下时会触发此回调
              onPanUpdate: (e) { //手指滑动时会触发此回调
                //用户手指滑动时,更新偏移,重新构建
                setState(() {
                  _left += e.delta.dx;
                  _top += e.delta.dy;
                });
              },
              //手指滑动结束
              onPanEnd: (e) {
                print(e.velocity);
              },
            ),
          ),
        ],
      ),
    );
  }

  // GestureRecognizer
  Widget _getRichText() {
    return Text.rich(TextSpan(children: [
      TextSpan(text: "这是Txt1"),
      TextSpan(
          text: "手势操作",
          style: TextStyle(
              fontSize: 30.0, color: _toogle ? Colors.red : Colors.tealAccent),
          // tab事件    
          recognizer: _tp
            ..onTap = () {
              setState(() {
                _toogle = !_toogle;
              });
            })
    ]));
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text("gesture"),
      ),
      body: Column(
        children: [_drag(),_getRichText()],
      ),
    );
  }
}

flutter 手势识别GestureDetector_第1张图片  flutter 手势识别GestureDetector_第2张图片  flutter 手势识别GestureDetector_第3张图片


     


  • DragDownDetails.globalPosition:当用户按下时,此属性为用户按下的位置相对于屏幕(而非父widget)原点(左上角)的偏移。
  • DragUpdateDetails.delta:当用户在屏幕上滑动时,会触发多次Update事件,delta指一次Update事件的滑动的偏移量。
  • DragEndDetails.velocity:该属性代表用户抬起手指时的滑动速度(包含x、y两个轴的),示例中并没有处理手指抬起时的速度,常见的效果是根据用户抬起手指时的速度做一个减速动画。

2.单一方向拖动

在很多场景,我们只需要沿一个方向来拖动,如一个垂直方向的列表,GestureDetector可以只识别特定方向的手势事件,类似下面的事件

onVerticalDragUpdate

onHorizontalDragUpdate

 3.手势竞争和冲突

如果在上例中我们同时监听水平和垂直方向的拖动事件,那么我们斜着拖动时哪个方向会生效?实际上取决于第一次移动时两个轴上的位移分量,哪个轴的大,哪个轴在本次滑动事件竞争中就胜出。实际上Flutter中的手势识别引入了一个Arena的概念,Arena直译为“竞技场”的意思,每一个手势识别器(GestureRecognizer)都是一个“竞争者”(GestureArenaMember),当发生滑动事件时,他们都要在“竞技场”去竞争本次事件的处理权,而最终只有一个“竞争者”会胜出(win)。例如,假设有一个ListView,它的第一个子Widget也是ListView,如果现在滑动这个子ListView,父ListView会动吗?答案是否定的,这时只有子Widget会动,因为这时子Widget会胜出而获得滑动事件的处理权


竞争

我们以拖动手势为例,同时识别水平和垂直方向的拖动手势,当用户按下手指时就会触发竞争(水平方向和垂直方向),一旦某个方向“获胜”,则直到当次拖动手势结束都会沿着该方向移动

import 'package:flutter/material.dart';

class BothDirectionTestRoute extends StatefulWidget {
  @override
  BothDirectionTestRouteState createState() =>
      new BothDirectionTestRouteState();
}

class BothDirectionTestRouteState extends State {
  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("A")),
            //垂直方向拖动事件
            onVerticalDragUpdate: (DragUpdateDetails details) {
              setState(() {
                _top += details.delta.dy;
              });
            },
            onHorizontalDragUpdate: (DragUpdateDetails details) {
              setState(() {
                _left += details.delta.dx;
              });
            },
          ),
        )
      ],
    );
  }
}

手势冲突

由于手势竞争最终只有一个胜出者,所以,当有多个手势识别器时,可能会产生冲突。假设有一个widget,它可以左右拖动,现在我们也想检测在它上面手指按下和抬起的事件

class GestureConflictTestRouteState extends State {
  double _left = 0.0;
  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Positioned(
          left: _left,
          child: GestureDetector(
              child: CircleAvatar(child: Text("A")), //要拖动和点击的widget
              onHorizontalDragUpdate: (DragUpdateDetails details) {
                setState(() {
                  _left += details.delta.dx;
                });
              },
              onHorizontalDragEnd: (details){
                print("onHorizontalDragEnd");
              },
              onTapDown: (details){
                print("down");
              },
              onTapUp: (details){
                print("up");
              },
          ),
        )
      ],
    );
  }
}
I/flutter (17539): down
I/flutter (17539): onHorizontalDragEnd

我们发现没有打印"up",这是因为在拖动时,刚开始按下手指时在没有移动时,拖动手势还没有完整的语义,此时TapDown手势胜出(win),此时打印"down",而拖动时,拖动手势会胜出,当手指抬起时,onHorizontalDragEnd和 onTapUp发生了冲突,但是因为是在拖动的语义中,所以onHorizontalDragEnd胜出,所以就会打印 “onHorizontalDragEnd”。如果我们的代码逻辑中,对于手指按下和抬起是强依赖的,比如在一个轮播图组件中,我们希望手指按下时,暂停轮播,而抬起时恢复轮播,但是由于轮播图组件中本身可能已经处理了拖动手势(支持手动滑动切换),甚至可能也支持了缩放手势,这时我们如果在外部再用onTapDownonTapUp来监听的话是不行的。这时我们应该怎么做?其实很简单,通过Listener监听原始指针事件就行

Positioned(
  top:80.0,
  left: _leftB,
  child: Listener(
    onPointerDown: (details) {
      print("down");
    },
    onPointerUp: (details) {
      //会触发
      print("up");
    },
    child: GestureDetector(
      child: CircleAvatar(child: Text("B")),
      onHorizontalDragUpdate: (DragUpdateDetails details) {
        setState(() {
          _leftB += details.delta.dx;
        });
      },
      onHorizontalDragEnd: (details) {
        print("onHorizontalDragEnd");
      },
    ),
  ),
)

手势冲突只是手势级别的,而手势是对原始指针的语义化的识别,所以在遇到复杂的冲突场景时,都可以通过Listener直接识别原始指针事件来解决冲突

你可能感兴趣的:(flutter,flutter,gesture)