美文网首页记录自学flutter点点滴滴
Flutter 学习之旅(三十二) 触摸事件学习(二)

Flutter 学习之旅(三十二) 触摸事件学习(二)

作者: Tsm_2020 | 来源:发表于2020-09-04 14:49 被阅读0次

    GestureDetector

    GestureDetector是一个用于手势识别的功能性组件,我们通过它可以来识别各种手势,其实在android中也有一个 GestureDetector,
    而且功能上也差不多,封装了各种手势,比如单机 双击 移动
    先来看一下构造方法

      GestureDetector({
        Key key,
        this.child,
        this.onTapDown,
        this.onTapUp,
        this.onTap,
        this.onTapCancel,
        this.onSecondaryTap,
        this.onSecondaryTapDown,
        this.onSecondaryTapUp,
        this.onSecondaryTapCancel,
        this.onDoubleTap,
        this.onLongPress,
        this.onLongPressStart,
        this.onLongPressMoveUpdate,
        this.onLongPressUp,
        this.onLongPressEnd,
        this.onSecondaryLongPress,
        this.onSecondaryLongPressStart,
        this.onSecondaryLongPressMoveUpdate,
        this.onSecondaryLongPressUp,
        this.onSecondaryLongPressEnd,
        this.onVerticalDragDown,
        this.onVerticalDragStart,
        this.onVerticalDragUpdate,
        this.onVerticalDragEnd,
        this.onVerticalDragCancel,
        this.onHorizontalDragDown,
        this.onHorizontalDragStart,
        this.onHorizontalDragUpdate,
        this.onHorizontalDragEnd,
        this.onHorizontalDragCancel,
        this.onForcePressStart,
        this.onForcePressPeak,
        this.onForcePressUpdate,
        this.onForcePressEnd,
        this.onPanDown,
        this.onPanStart,
        this.onPanUpdate,
        this.onPanEnd,
        this.onPanCancel,
        this.onScaleStart,
        this.onScaleUpdate,
        this.onScaleEnd,
        this.behavior,
        this.excludeFromSemantics = false,
        this.dragStartBehavior = DragStartBehavior.start,
      })
    

    各种各样的手势接口,Pan 触摸事件 scale 缩放事件 onTap 单机 onDoubleTap双击 onLongPress长按等等...
    例子

          GestureDetector(
                child: Container(
                  height: 100,
                  width: double.infinity,
                  color: Colors.greenAccent,
                  child: Center(
                    child: Text(_tag ??= '',style: TextStyle(color: Colors.black87),),
                  ),
                ),
                onTap: () {
                  setState(() {
                    _tag = "单击";
                  });
                },
                onDoubleTap: () {
                  setState(() {
                    _tag = "双击";
                  });
                },
                onLongPress: () {
                  setState(() {
                    _tag = "长按";
                  });
                },
              )
    

    下面再来个拖动的例子

    Container(
                height: 200,
                color: Colors.pink,
                child: Stack(
                  children: [
                    Positioned(
                      left: _left,
                      top: _top,
                      child: GestureDetector(
                        child: CircleAvatar(
                          key: key,
                          child: Text('A'),
                        ),
                        onPanUpdate: (event) {
                          setState(() {
                            _left += event.delta.dx;
                            if(_left<0){
                              _left=0;
                            }
                            var width=getAppWidth(context);
                            var wiget_width=getWidgetWidthAndHeight(key)[0];
                            if(_left>width-wiget_width){
                              _left=width-wiget_width;
                            }
                            _top += event.delta.dy;
                            if(_top<0){
                              _top=0;
                            }
                            if(_top>200-wiget_width){
                              _top=200-wiget_width;
                            }
                          });
                        },
                      ),
                    )
                  ],
                ),
              )
    

    这里我添加了获取屏幕高度和根据GlobalKey获取控件宽高的,这样做一个限制,可以防止控件滑动出父widget之外,


    GIF 2020-9-4 14-55-02.gif

    先来一下GestureDetector的build 一部分源码,

      Widget build(BuildContext context) {
        final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
    
        if (
          onTapDown != null ||
          onTapUp != null ||
          onTap != null ||
          onTapCancel != null ||
          onSecondaryTap != null ||
          onSecondaryTapDown != null ||
          onSecondaryTapUp != null ||
          onSecondaryTapCancel != null
        ) {
          gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
            () => TapGestureRecognizer(debugOwner: this),
            (TapGestureRecognizer instance) {
              instance
                ..onTapDown = onTapDown
                ..onTapUp = onTapUp
                ..onTap = onTap
                ..onTapCancel = onTapCancel
                ..onSecondaryTap = onSecondaryTap
                ..onSecondaryTapDown = onSecondaryTapDown
                ..onSecondaryTapUp = onSecondaryTapUp
                ..onSecondaryTapCancel = onSecondaryTapCancel;
            },
          );
        }
    }
    

    下面分析一下 ,先创建了一个Map[type ,GestureRecognizerFactory] ,如果存在按下 抬起等事件中的一个时候,则会在上述map中创建一个TapGestureRecognizer 元素,可见TapGestureRecognizer内部应该是处理手势地方,

    查看了一下源码,TapGestureRecognizer继承了BaseTapGestureRecognizer 并且实现了各种事件的分发,
    先来查看一下按下的分发,

      @protected
      @override
      void handleTapDown({PointerDownEvent down}) {
        final TapDownDetails details = TapDownDetails(
          globalPosition: down.position,
          localPosition: down.localPosition,
          kind: getKindForPointer(down.pointer),
        );
        switch (down.buttons) {
    ///主键的按钮
          case kPrimaryButton:
            if (onTapDown != null)
              invokeCallback<void>('onTapDown', () => onTapDown(details));
            break;
    ///次要的按钮  猜测应该是双指使用
          case kSecondaryButton:
            if (onSecondaryTapDown != null)
              invokeCallback<void>('onSecondaryTapDown', () => onSecondaryTapDown(details));
            break;
          default:
        }
      }
    
    
    
      @protected
      T invokeCallback<T>(String name, RecognizerCallback<T> callback, { String debugReport() }) {
        assert(callback != null);
        T result;
        try {
          assert(() {
            if (debugPrintRecognizerCallbacksTrace) {
              final String report = debugReport != null ? debugReport() : null;
              // The 19 in the line below is the width of the prefix used by
              // _debugLogDiagnostic in arena.dart.
              final String prefix = debugPrintGestureArenaDiagnostics ? ' ' * 19 + '❙ ' : '';
              debugPrint('$prefix$this calling $name callback.${ report?.isNotEmpty == true ? " $report" : "" }');
            }
            return true;
          }());
          result = callback();
        } catch (exception, stack) {
          InformationCollector collector;
          assert(() {
            collector = () sync* {
              yield StringProperty('Handler', name);
              yield DiagnosticsProperty<GestureRecognizer>('Recognizer', this, style: DiagnosticsTreeStyle.errorProperty);
            };
            return true;
          }());
          FlutterError.reportError(FlutterErrorDetails(
            exception: exception,
            stack: stack,
            library: 'gesture',
            context: ErrorDescription('while handling a gesture'),
            informationCollector: collector
          ));
        }
        return result;
      }
    
    

    invokeCallback 除了检测事件,还有try catch 的方法,实际有用的就是一个 result = callback();
    这里主要是为了说明GestureDetector只是GestureRecognizer的封装,而实际分发事件的是GestureRecognizer,还记得在TextSpan
    例子

      TextSpan(
                      text: "文本点击",
                      style: TextStyle(
                          fontSize: 30.0,
                          color: _toggle ? Colors.blue : Colors.red
                      ),
                      recognizer: _tapGestureRecognizer
                        ..onTap = () {
                          printString("点击");
                        },
                    )
    
    

    这里使用的是AbsorbPointer,拦截他和他子widget 的触摸事件,所以只打印one ,如果换做IgnorePointer 则两个都不会打印

    我学习flutter的整个过程都记录在里面了
    https://www.jianshu.com/c/36554cb4c804

    最后附上demo 地址

    https://github.com/tsm19911014/tsm_flutter

    相关文章

      网友评论

        本文标题:Flutter 学习之旅(三十二) 触摸事件学习(二)

        本文链接:https://www.haomeiwen.com/subject/zkkzsktx.html