preface

In a service scenario, press your finger and slide the camera up and down on the playing screen.

Because the playing page uses photo_view to provide the functions of zooming in and out and dragging pictures, it conflicts with the nested GestureDetector to monitor sliding.

The final solution is to make the GestureDetector receive the finger sliding event under the default state of photo_VEW, and the camera follows the rotation; In the photo_VEW zoom state, the GestureDetector does not receive events and lets Photo_VEW handle drag-and-drop of the zoom image. It better solves the problem of user’s usage scenario

Problem description

OnPanDown implementation

First of all, the normal idea is to wrap the GestureDetector directly outside the Photoview, and then rewrite the onPanDown and onPanUpdate methods as follows

body: GestureDetector(
        onPanDown: (e) {
          print('fuxiao: press$e');
        },
        onPanStart: (e) {
          print('fuxiao: start$e');
        },
        onPanCancel: () {
          print('fuxiao: cancel');
        },
        onPanEnd: (e) {
          print('end of fuxiao:');
        },
        onPanUpdate: (e) {
          print('fuxiao: update$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,
          ),
        ),
      ),
Copy the code

There are two problems with the result

1. OnPanUpdate does not trigger when it slides for the first time, but the onPanCancel method is triggered. It is assumed that the event is caused by PhotoView processing, and the event of the upper tree is cancelled

2. Due to the rewriting of onPanDown method, the functions of PhotoView and double finger zooming in and out of the picture failed

Neither of the two functions can work properly. Trying to solve this problem, I found that the GestureDetector does not provide a two-finger press detection method after browsing online.

So while you want to process the event by pressing the PhotoView with two fingers, you can’t process the event by pressing the outer GestureDetector with one finger.

OnHorizontalDragUpdate implementation

The GestureDetector constructor provides both horizontal and vertical detection methods

Horizontal dragging

  • OnHorizontalDragStart Horizontal movement begins
  • OnHorizontalDragUpdate moves horizontally
  • OnHorizontalDragEnd moves horizontally to end

Vertical drag

  • OnVerticalDragStart vertical movement starts
  • OnVerticalDragUpdate moves vertically
  • OnVerticalDragEnd moves vertically to end

By overriding the above method and actually printing the log, onHorizontalDragUpdate and onVerticalDragUpdate are always called first when moving the screen with one hand, as shown below

body: GestureDetector(
        behavior: HitTestBehavior.opaque,
        onHorizontalDragUpdate: (e) {
          print('fuxiao: level$e');
        },
        onVerticalDragUpdate: (e) {
          print('fuxiao: vertical$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,
          ),
        ),
      ),
Copy the code

Swipe your finger across the screen to print a log

I/flutter (5191): fuXIAO: level DragUpdateDetails(Offset(1.1, 0.0)) Fuxiao: horizontal DragUpdateDetails(Offset(1.1, 0.0)) I/ FLUTTER (5191): Fuxiao: horizontal DragUpdateDetails(Offset(1.8, 0.0)) I/ FLUTTER (5191): Fuxiao: Vertical DragUpdateDetails(Offset(0.0, -2.5)) I/ FLUTTER (5191): Fuxiao: Vertical DragUpdateDetails(Offset(0.0, -2.9)) I/ FLUTTER (5191): fuxiao: vertical DragUpdateDetails(Offset(0.0, -2.5))Copy the code

In addition, the onPanDown override method was removed, and the PhotoView double finger zoom in and out function was also normal, so we can basically achieve our requirements, but there is a little optimization: When PhotoView is zoomed in, swiping left and right moves the image instead of calling the onHorizontalDragUpdate method

The effect is similar to that of a preview picture in an album.

Overwriting onHorizontalDragUpdate will invalidate screen movement

As you can see, after zooming in, press the horizontal finger, the horizontal slide screen does not follow the movement, and the event is consumed by the upper layer

The idea is to disable onHorizontalDragUpdate when PhotoView zooms in or out of state

Conflict resolution

As you can guess, PhotoView should provide a listener for the zoom state to see the constructor of the PhotoView

PhotoView({
    Key? key,
    required this.imageProvider,
    this.loadingBuilder,
    this.backgroundDecoration,
    this.gaplessPlayback = false.this.heroAttributes,
  	/// Zoom state monitor
    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);
Copy the code
/// 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;
Copy the code
/// A way to represent the step of the "doubletap gesture cycle" in which PhotoView is.
enum PhotoViewScaleState {
  initial,
  covering,
  originalSize,
  zoomedIn,
  zoomedOut,
}
Copy the code

When state is initial, the GestureDetector is allowed to listen for the slide event. Otherwise, the PhotoView drags

conclusion

The final code is as follows

The common_example_wrapper.dart code from example in photo_view is directly pasted here

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: slide:$e');
    };
    if(widget.scaleChangedListener == null) {
      _scaleChangedListener = (PhotoViewScaleState statue) {
        print('fuxiao: status:$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, ), ), ), ); }}Copy the code

The end result is as follows

conclusion

This paper mainly analyzes and solves the conflict problem of sliding listening of photo_view nested GestureDetector. For the time being, the solution of the problem will be recorded first, and another analysis of the principle will be written later.

If you have encountered the same situation, and have a better solution, welcome to share in the comments section, thanks ~