flutter中photo_vew嵌套GestureDetector屏幕滑动冲突解决

前言

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

由于播放页面使用了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方法

图片拖拽

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

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

水平移动

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

解决的思路就是,当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滑动监听的冲突问题,暂时先记录问题的解决,原理有时间再写一篇分析。

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

Supongo que te gusta

Origin juejin.im/post/7049306163149537294
Recomendado
Clasificación