Extended_image

  • What function does a Flutter have Image
  • A Flutter can zoom in and out of dragged images
  • The effect of Flutter on wechat image slides to exit the page
  • Flutter picture clipping rotation flip editor

I searched in Pub, but didn’t find an image with the same effect as wechat that supports zooming and dragging effect, so I lifted one by myself, which was written beforeWhat function does a Flutter have ImageSo I added this function on top of this.

Main functions:

  • Scaling of drag and drop
  • Zoom and drag inside PageView

Supports zooming and dragging

usage

1. Set the mode parameters of extended_image to ExtendedImageMode. Gesture

2. Set the GestureConfig

 ExtendedImage.network(
  imageTestUrl,
  fit: BoxFit.contain,
  //enableLoadState: false,
  mode: ExtendedImageMode.Gesture,
  initGestureConfigHandler: (state) {
    return GestureConfig(
        minScale: 0.9,
        animationMinScale: 0.7,
        maxScale: 3.0,
        animationMaxScale: 3.5,
        speed: 1.0,
        inertialSpeed: 100.0,
        initialScale: 1.0,
        inPageView: false); },)Copy the code

GestureConfig Parameter Description

parameter describe The default value
minScale Minimum scale 0.8
animationMinScale The minimum value of the zoom animation, and returns to the minScale value when the zoom is finished MinScale * 0.8
maxScale Maximum scale 5.0
animationMaxScale Zoom animation Max, returns to maxScale value when scaling is over MaxScale * 1.2
speed Zoom drag speed is proportional to user operation 1.0
inertialSpeed The inertial velocity of drag is proportional to the inertial velocity 100
cacheGesture Whether to cache gesture state can be used to reserve the state in Pageview and clear it using the clearGestureDetailsCache method false
inPageView Whether to use ExtendedImageGesturePageView display pictures false

The implementation process

This function is relatively simple. In reference to the official Gestures demo, the scaled Scale and Offset are converted to the final display area of the image. The specific code translates gestureDetails into the corresponding display area of the image when the image is finally drawn.

 bool gestureClip = false;
  if(gestureDetails ! =null) {
    destinationRect =
        gestureDetails.calculateFinalDestinationRect(rect, destinationRect);

    ///outside and need clip
    gestureClip = outRect(rect, destinationRect);

    if(gestureClip) { canvas.save(); canvas.clipRect(rect); }}Copy the code

The rect is the area of the image on the screen, destinationRect image display area (which is different according to different BoxFit), through gestureDetails calculateFinalDestinationRect way, calculate the final display area.

Make the scaling look smooth

1. Scale the zoom point as the center point according to the position of the zoom point relative to the image

2. If Scale is less than or equal to 1.0, Scale according to the center point of the image, while if Scale is greater than 1.0 and the image is already covered, Scale according to 1

3. If the image has a wide difference in length and width, when zooming, first zoom along the center point of the long side until the image is fully covered, then follow 1

4. When scaling, do not move

1,2,3 correspond to the code

Offset _getCenter(Rect destinationRect) {
    if(! userOffset && _center ! =null) {
      return _center;
    }

    if (totalScale > 1.0) {
      if (_computeHorizontalBoundary && _computeVerticalBoundary) {
        return destinationRect.center * totalScale + offset;
      } else if (_computeHorizontalBoundary) {
        //only scale Horizontal
        return Offset(destinationRect.center.dx * totalScale,
                destinationRect.center.dy) +
            Offset(offset.dx, 0.0);
      } else if (_computeVerticalBoundary) {
        //only scale Vertical
        return Offset(destinationRect.center.dx,
                destinationRect.center.dy * totalScale) +
            Offset(0.0, offset.dy);
      } else {
        returndestinationRect.center; }}else {
      returndestinationRect.center; }}Copy the code

4 corresponding code, when details.scale==1.0, indicates a move operation, otherwise for a scale operation

void _handleScaleUpdate(ScaleUpdateDetails details) {
    ...
    var offset =
        ((details.scale == 1.0? details.focalPoint : _startingOffset) - _normalizedOffset * scale); . }Copy the code

Once we get the center of the image, we wait for the entire region of the image based on Scale

 Rect _getDestinationRect(Rect destinationRect, Offset center) {
    final double width = destinationRect.width * totalScale;
    final double height = destinationRect.height * totalScale;

    return Rect.fromLTWH(
        center.dx - width / 2.0, center.dy - height / 2.0, width, height);
  }
Copy the code

Calculation of drag boundary

1. Calculate whether it is necessary to calculate the limit boundary. 2

    if (_computeHorizontalBoundary) {
      //move right
      if (result.left >= layoutRect.left) {
        result = Rect.fromLTWH(0.0, result.top, result.width, result.height);
        _boundary.left = true;
      }

      ///move left
      if (result.right <= layoutRect.right) {
        result = Rect.fromLTWH(layoutRect.right - result.width, result.top,
            result.width, result.height);
        _boundary.right = true; }}if (_computeVerticalBoundary) {
      //move down
      if (result.bottom <= layoutRect.bottom) {
        result = Rect.fromLTWH(result.left, layoutRect.bottom - result.height,
            result.width, result.height);
        _boundary.bottom = true;
      }

      //move up
      if (result.top >= layoutRect.top) {
        result = Rect.fromLTWH(
            result.left, layoutRect.top, result.width, result.height);
        _boundary.top = true;
      }
    }

    _computeHorizontalBoundary =
        result.left <= layoutRect.left && result.right >= layoutRect.right;

    _computeVerticalBoundary =
        result.top <= layoutRect.top && result.bottom >= layoutRect.bottom;
Copy the code

Zoom rebound effect and drag inertia effect

void _handleScaleEnd(ScaleEndDetails details) {
    //animate back to maxScale if gesture exceeded the maxScale specified
    if (_gestureDetails.totalScale > _gestureConfig.maxScale) {
      final double velocity =
          (_gestureDetails.totalScale - _gestureConfig.maxScale) /
              _gestureConfig.maxScale;

      _gestureAnimation.animationScale(
          _gestureDetails.totalScale, _gestureConfig.maxScale, velocity);
      return;
    }

    //animate back to minScale if gesture fell smaller than the minScale specified
    if (_gestureDetails.totalScale < _gestureConfig.minScale) {
      final double velocity =
          (_gestureConfig.minScale - _gestureDetails.totalScale) /
              _gestureConfig.minScale;

      _gestureAnimation.animationScale(
          _gestureDetails.totalScale, _gestureConfig.minScale, velocity);
      return;
    }

    if (_gestureDetails.gestureState == GestureState.pan) {
      // get magnitude from gesture velocity
      final double magnitude = details.velocity.pixelsPerSecond.distance;

      // do a significant magnitude
      if (magnitude >= minMagnitude) {
        finalOffset direction = details.velocity.pixelsPerSecond / magnitude * _gestureConfig.inertialSpeed; _gestureAnimation.animationOffset( _gestureDetails.offset, _gestureDetails.offset + direction); }}}Copy the code

The only thing to notice is that Scale’s bounce animation will be scaled around the last zoom center point so that the zoom animation looks comfortable

  //true: user zoom/pan
  //false: animation
  final bool userOffset;
  Offset _getCenter(Rect destinationRect) {
    if(! userOffset && _center ! =null) {
      return _center;
    }
Copy the code

Zoom and drag inside PageView

usage

1. Use ExtendedImageGesturePageView display pictures

2. Set the inPageView of GestureConfig to true

GestureConfig Parameter Description

parameter describe The default value
inPageView Whether to use ExtendedImageGesturePageView display pictures false

The implementation process

Gestures conflict

Need to focus on the scene is a gesture of conflict, inside the PageView is horizontal or vertical gestures, will follow onScaleStart/onScaleUpdate/onScaleEnd have conflict.

At the beginning, I thought that the gesture should bubble, could I not bubble up after LISTENING, so as to prevent the sliding behavior in PageView? Finally, the conclusion is that there is no way to prevent bubbling.

For gestures, you can read ramen Little Sister’s article on gestures, the magical arena concept.

Since I can’t stop the gesture from bubbling, I’m just going to stop you from scrolling, and I’m going to do all the gestures.

First, I looked at the source code of PageView about scrolling, pointing directly to the code in the final ScrollableState. In the setCanDrag method, I prepared the horizontal/vertical gesture according to whether it can Drag.

Taking out the code in ScrollableState for horizontal and vertical scrolling, I created a special extended_image_geSTUre_page_view that belongs to extended_image and has the same property as PageView, Just can’t set the physics because to NeverScrollableScrollPhysics mandatory Settings

    Widget result = PageView.custom(
      scrollDirection: widget.scrollDirection,
      reverse: widget.reverse,
      controller: widget.controller,
      childrenDelegate: widget.childrenDelegate,
      pageSnapping: widget.pageSnapping,
      physics: widget.physics,
      onPageChanged: widget.onPageChanged,
      key: widget.key,
    );

    result = RawGestureDetector(
      gestures: _gestureRecognizers,
      behavior: HitTestBehavior.opaque,
      child: result,
    );
Copy the code

Then we registered _gestureRecognizers via RawGestureDetector.

About _gestureRecognizers, I’ve been wondering how PageView can hold hands until I saw the source code. Source code is really a good thing.

 void _handleDragDown(DragDownDetails details) {
    //print(details);
    _gestureAnimation.stop();
    assert(_drag == null);
    assert(_hold == null);
    _hold = position.hold(_disposeHold);
  }
Copy the code

Scroll to the next image when you reach the boundary

Now that we have the basis for scaling and dragging, this part is a little bit easier. If you reach the boundary, you use the default code to manipulate PageView, otherwise you control the Image to drag and drop

void _handleDragUpdate(DragUpdateDetails details) {
    // _drag might be null if the drag activity ended and called _disposeDrag.
    assert(_hold == null || _drag == null);
    var delta = details.delta;

    if(extendedImageGestureState ! =null) {
      var gestureDetails = extendedImageGestureState.gestureDetails;
      if(gestureDetails ! =null) {
        if(gestureDetails.movePage(delta)) { _drag? .update(details); }else{ extendedImageGestureState.gestureDetails = GestureDetails( offset: gestureDetails.offset + delta * extendedImageGestureState.imageGestureConfig.speed, totalScale: gestureDetails.totalScale, gestureDetails: gestureDetails); }}else {
        _drag?.update(details);
      }
    } else {
      _drag?.update(details);
    }
  }
Copy the code

Drag inertia effect

In DragEnd, we need to pay attention to dealing with inertia. When the image is enlarged and can slide horizontally or vertically, we need the _drag to stop to prevent direct sliding to the previous or next image DragEndDetails(primaryVelocity: 0.0), and let the image continue to slide in the range according to its inertia.

void _handleDragEnd(DragEndDetails details) {
    // _drag might be null if the drag activity ended and called _disposeDrag.
    assert(_hold == null || _drag == null);

    var temp = details;

    if(extendedImageGestureState ! =null) {
      var gestureDetails = extendedImageGestureState.gestureDetails;

     if(gestureDetails ! =null &&
          gestureDetails.totalScale > 1.0 &&
          (gestureDetails.computeHorizontalBoundary ||
              gestureDetails.computeVerticalBoundary)) {
        //stop
        temp = DragEndDetails(primaryVelocity: 0.0);

        // get magnitude from gesture velocity
        final double magnitude = details.velocity.pixelsPerSecond.distance;

        // do a significant magnitude
        if (magnitude >= minMagnitude) {
          Offset direction = details.velocity.pixelsPerSecond /
              magnitude *
              (extendedImageGestureState.imageGestureConfig.inertialSpeed);

          if (widget.scrollDirection == Axis.horizontal) {
            direction = Offset(direction.dx, 0.0);
          } else {
            direction = Offset(0.0, direction.dy); } _gestureAnimation.animationOffset( gestureDetails.offset, gestureDetails.offset + direction); } } } _drag? .end(temp);assert(_drag == null);
  }
Copy the code

That concludes the entire Extended_image zooming and dragging feature. Just to make fun of this gesture, I hope the Flutter team has a better solution.

Finally put onextended_imagePlease let me know if there is anything you don’t understand or how to improve the program. Welcome to join usFlutter CandiesTogether to make cute little Flutter candiesQQ group: 181398081