美文网首页
flutter中photo_vew嵌套GestureDetect

flutter中photo_vew嵌套GestureDetect

作者: QiShare | 来源:发表于2022-01-04 19:09 被阅读0次

    前言

    业务场景为,在摄像机的播放画面上,按下手指左右上下滑动,摄像机跟随滑动。

    由于播放页面使用了photo_view提供了画面图片的放大缩小和拖拽功能,导致和嵌套的要实现监听滑动的GestureDetector存在冲突的问题。

    最终实现的解决方案为,在photo_vew默认状态下,让GestureDetector接收手指滑动事件,摄像机跟随旋转;在photo_vew放大状态下,GestureDetector不接收事件,让photo_vew处理放大图片的拖拽。比较好的解决了用户的使用场景问题

    问题描述

    onPanDown实现

    首先,正常思路肯定是直接在Photoview的外部包裹GestureDetector,然后重写onPanDown,onPanUpdate方法,如下

    body: GestureDetector(
            onPanDown: (e) {
              print('fuxiao:按下 $e');
            },
            onPanStart: (e) {
              print('fuxiao:开始 $e');
            },
            onPanCancel: () {
              print('fuxiao:取消');
            },
            onPanEnd: (e) {
              print('fuxiao:结束');
            },
            onPanUpdate: (e) {
              print('fuxiao:更新$e');
            },
            child: Container(
              constraints: BoxConstraints.expand(
                height: MediaQuery.of(context).size.height,
              ),
              // child: Container(
              //   color: Colors.blue,
              //   width: 400,
              //   height: 400,
              // )
              child: PhotoView(
                imageProvider: imageProvider,
                loadingBuilder: loadingBuilder,
                backgroundDecoration: backgroundDecoration,
                minScale: minScale,
                maxScale: maxScale,
                initialScale: initialScale,
                basePosition: basePosition,
                filterQuality: filterQuality,
                disableGestures: disableGestures,
                errorBuilder: errorBuilder,
              ),
            ),
          ),
    

    结果存在两个问题

    1、onPanUpdate第一次滑动不触发,而触发onPanCancel方法,猜想是PhotoView处理事件导致的,上层树的事件取消,通过将上面注释代码打开验证,确实是PhotoView导致的问题

    2、由于重写onPanDown方法,导致PhotoView,双指缩放画面功能失效

    两个功能都不能正常工作,尝试解决这个问题,在网上浏览资料发现,GestureDetector并没有提供,双指的按下检测方法。

    所以希望通过双指按下PhotoView处理事件,单指按下外层GestureDetector处理事件的方法行不通。

    onHorizontalDragUpdate 实现

    通过查看GestureDetector构造方法发现,提供了水平和竖直方向的检测方法

    水平拖拽

    • onHorizontalDragStart 水平移动开始
    • onHorizontalDragUpdate 水平方向移动
    • onHorizontalDragEnd 水平移动结束

    垂直拖拽

    • onVerticalDragStart 垂直移动开始
    • onVerticalDragUpdate 垂直移动
    • onVerticalDragEnd 垂直移动结束

    通过重写上述方法,实际打印log发现,onHorizontalDragUpdate和onVerticalDragUpdate在单手移动屏幕时总会优先调用,代码如下

    body: GestureDetector(
            behavior: HitTestBehavior.opaque,
            onHorizontalDragUpdate: (e) {
              print('fuxiao:水平$e');
            },
            onVerticalDragUpdate: (e) {
              print('fuxiao:垂直$e');
            },
            child: Container(
              constraints: BoxConstraints.expand(
                height: MediaQuery.of(context).size.height,
              ),
              child: PhotoView(
                imageProvider: imageProvider,
                loadingBuilder: loadingBuilder,
                backgroundDecoration: backgroundDecoration,
                minScale: minScale,
                maxScale: maxScale,
                initialScale: initialScale,
                basePosition: basePosition,
                filterQuality: filterQuality,
                disableGestures: disableGestures,
                errorBuilder: errorBuilder,
              ),
            ),
          ),
    

    手指在屏幕上滑动,打印log

    I/flutter ( 5191): fuxiao:水平DragUpdateDetails(Offset(1.1, 0.0))
    I/flutter ( 5191): fuxiao:水平DragUpdateDetails(Offset(1.1, 0.0))
    I/flutter ( 5191): fuxiao:水平DragUpdateDetails(Offset(1.8, 0.0))
    I/flutter ( 5191): fuxiao:垂直DragUpdateDetails(Offset(0.0, -2.5))
    I/flutter ( 5191): fuxiao:垂直DragUpdateDetails(Offset(0.0, -2.9))
    I/flutter ( 5191): fuxiao:垂直DragUpdateDetails(Offset(0.0, -2.5))
    

    并且,删除了onPanDown的重写方法,PhotoView双指放大缩小的功能也正常了,这样我们的需求基本上已经可以实现了,只不过还有一点小小的优化:PhotoView在画面放大状态下,左右滑动是移动画面,而不是回调onHorizontalDragUpdate方法

    image

    跟相册中预览图片的效果是相似的。

    而如果重写onHorizontalDragUpdate,会导致画面移动失效

    image

    可以看到,放大以后,按下水平手指,水平滑动画面没有跟随移动,事件被上层消费了

    解决的思路就是,当PhotoView放大或缩小状态时,禁止onHorizontalDragUpdate的调用

    冲突解决

    可以猜想,PhotoView应该提供了缩放状态的监听,查看PhotoView的构造方法

    PhotoView({
        Key? key,
        required this.imageProvider,
        this.loadingBuilder,
        this.backgroundDecoration,
        this.gaplessPlayback = false,
        this.heroAttributes,
        /// 缩放状态监听
        this.scaleStateChangedCallback,
        this.enableRotation = false,
        this.controller,
        this.scaleStateController,
        this.maxScale,
        this.minScale,
        this.initialScale,
        this.basePosition,
        this.scaleStateCycle,
        this.onTapUp,
        this.onTapDown,
        this.onScaleEnd,
        this.customSize,
        this.gestureDetectorBehavior,
        this.tightMode,
        this.filterQuality,
        this.disableGestures,
        this.errorBuilder,
        this.enablePanAlways,
      })  : child = null,
            childSize = null,
            super(key: key);
    
    /// A [Function] to be called whenever the scaleState changes, this happens when the user double taps the content ou start to pinch-in.
      final ValueChanged<PhotoViewScaleState>? scaleStateChangedCallback;
    
    /// A way to represent the step of the "doubletap gesture cycle" in which PhotoView is.
    enum PhotoViewScaleState {
      initial,
      covering,
      originalSize,
      zoomedIn,
      zoomedOut,
    }
    

    这样就拿到了,PhotoView画面状态变化回调,只需要做一个处理,当state为initial时,才允许GestureDetector监听滑动事件,其他情况,走PhotoView的拖拽

    结论

    最终代码如下

    这里直接贴上photo_view中example里的common_example_wrapper.dart代码

    import 'package:flutter/material.dart';
    import 'package:photo_view/photo_view.dart';
    
    class CommonExampleRouteWrapper extends StatefulWidget {
      const CommonExampleRouteWrapper({
        this.imageProvider,
        this.loadingBuilder,
        this.backgroundDecoration,
        this.minScale,
        this.maxScale,
        this.initialScale,
        this.basePosition = Alignment.center,
        this.filterQuality = FilterQuality.none,
        this.disableGestures,
        this.errorBuilder,
        this.scaleChangedListener
      });
    
      final ImageProvider? imageProvider;
      final LoadingBuilder? loadingBuilder;
      final BoxDecoration? backgroundDecoration;
      final dynamic minScale;
      final dynamic maxScale;
      final dynamic initialScale;
      final Alignment basePosition;
      final FilterQuality filterQuality;
      final bool? disableGestures;
      final ImageErrorWidgetBuilder? errorBuilder;
    
      final ValueChanged<PhotoViewScaleState>? scaleChangedListener;
    
      @override
      _CommonExampleRouteWrapperState createState() => _CommonExampleRouteWrapperState();
    }
    
    class _CommonExampleRouteWrapperState extends State<CommonExampleRouteWrapper> {
      ValueChanged<PhotoViewScaleState>? _scaleChangedListener;
      bool canZoomControl = true;
      GestureDragUpdateCallback? updateCallback;
      @override
      void initState() {
        updateCallback = (e) {
          print('fuxiao: 滑动:$e');
        };
        if(widget.scaleChangedListener == null) {
          _scaleChangedListener = (PhotoViewScaleState statue) {
            print('fuxiao: 状态:$statue');
            switch(statue) {
              case PhotoViewScaleState.initial:
                canZoomControl = true;
                break;
              default:
                canZoomControl = false;
                break;
            }
            setState(() {
            });
          };
        } else {
          _scaleChangedListener = widget.scaleChangedListener;
        }
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: GestureDetector(
    
            onHorizontalDragUpdate: canZoomControl ? updateCallback : null,
            onVerticalDragUpdate: canZoomControl ? updateCallback : null,
            child: Container(
              constraints: BoxConstraints.expand(
                height: MediaQuery.of(context).size.height,
              ),
              child: PhotoView(
                imageProvider: widget.imageProvider,
                loadingBuilder: widget.loadingBuilder,
                backgroundDecoration: widget.backgroundDecoration,
                scaleStateChangedCallback: _scaleChangedListener,
                minScale: widget.minScale,
                maxScale: widget.maxScale,
                initialScale: widget.initialScale,
                basePosition: widget.basePosition,
                filterQuality: widget.filterQuality,
                disableGestures: widget.disableGestures,
                errorBuilder: widget.errorBuilder,
              ),
            ),
          ),
        );
      }
    }
    
    

    最终效果如下

    最终方案

    总结

    本文主要分析解决了,photo_view嵌套GestureDetector滑动监听的冲突问题,暂时先记录问题的解决,原理有时间再写一篇分析。

    大家如果有遇到相同情况,并且有更好的解决方案,欢迎在评论区交流分享,感谢~

    相关文章

      网友评论

          本文标题:flutter中photo_vew嵌套GestureDetect

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