24

I'm trying to get some basic pan and zoom functionality for images. The stateless version can display images (rotated 180 degrees by Transform) and the Scale events show up in the logs, but that's it.

Is GestureDetector the correct widget for getting the pan/pinch/spread events? Should I be looking at Transform, Animation, or should I just be modifying the fields inside the Image widget?

Stateless version

// Wraps an Image widget to provide pan and zoom functionality.
class InteractiveImage extends StatelessWidget {
  InteractiveImage(this._image, {Key key}) : super(key: key);

  final Image _image;

  @override
  Widget build(BuildContext context) {
    return new Center(
      child: new GestureDetector(
        onScaleStart: (ScaleStartDetails details) => print(details),
        onScaleUpdate: (ScaleUpdateDetails details) => print(details),
        onScaleEnd: (ScaleEndDetails details) => print(details),
        child: new Transform(
          transform: new Matrix4.rotationZ(math.PI),
          alignment: FractionalOffset.center,
          child: _image,
        ),
      ),
    );
  }
}

Stateful version (doesn't work)

// Wraps an Image widget to provide pan and zoom functionality.
class InteractiveImage extends StatefulWidget {
  InteractiveImage(this._image, {Key key}) : super(key: key);

  final Image _image;

  @override
  _InteractiveImageState createState() => new _InteractiveImageState(_image);
}

class _InteractiveImageState extends State<InteractiveImage> {
  _InteractiveImageState(this._image);

  final Image _image;

  @override
  Widget build(BuildContext context) {
    setState(() => print("STATE SET\n"));
    return new GestureDetector(
      onScaleStart: (ScaleStartDetails details) => print(details),
      onScaleUpdate: (ScaleUpdateDetails details) => print(details),
      onScaleEnd: (ScaleEndDetails details) => print(details),
      child: new Transform(
        transform: new Matrix4.rotationZ(math.PI),
        alignment: FractionalOffset.center,
        child: _image,
      ),
    );
  }
}

Update (library solution)

Use https://pub.dartlang.org/packages/zoomable_image

(And maybe help me add physics and elastic edges?)

CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
perlatus
  • 531
  • 2
  • 6
  • 12

3 Answers3

80

You can use InteractiveViewer which comes out of the box with Flutter.

@override
Widget build(BuildContext context) {
  return Center(
    child: InteractiveViewer(
      panEnabled: false, // Set it to false
      boundaryMargin: EdgeInsets.all(100),
      minScale: 0.5,
      maxScale: 2,
      child: Image.asset(
        'your_image_asset',
        width: 200,
        height: 200,
        fit: BoxFit.cover,
      ),
    ),
  );
}
CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
8

I'm not sure what you mean by "doesn't work" but you're on the right track.

Check out https://github.com/flutter/flutter/blob/master/examples/layers/widgets/gestures.dart for a working example of a widget you can pan, pinch, and scale.

Collin Jackson
  • 110,240
  • 31
  • 221
  • 152
  • how does the gesture scale work? I didn't understand how to test it. I can tap, double tap and long press. I can drag the object. Is that scale? I think of scale as zoom in. – Golden Lion Apr 05 '22 at 14:51
4

Based on the code from the question and encouraged by Collin's answer, this is what I came up with:

import 'package:flutter/material.dart';
import 'package:vector_math/vector_math_64.dart';

class InteractiveImage extends StatefulWidget {
  InteractiveImage(this.image, {Key key}) : super(key: key);

  final Image image;

  @override
  _InteractiveImageState createState() => new _InteractiveImageState();
}

class _InteractiveImageState extends State<InteractiveImage> {
  _InteractiveImageState();

  double _scale = 1.0;
  double _previousScale = null;

  @override
  Widget build(BuildContext context) {
    setState(() => print("STATE SET\n"));
    return new GestureDetector(
      onScaleStart: (ScaleStartDetails details) {
        print(details);
        // Does this need to go into setState, too?
        // We are only saving the scale from before the zooming started
        // for later - this does not affect the rendering...
        _previousScale = _scale;
      },
      onScaleUpdate: (ScaleUpdateDetails details) {
        print(details);
        setState(() => _scale = _previousScale * details.scale);
      },
      onScaleEnd: (ScaleEndDetails details) {
        print(details);
        // See comment above
        _previousScale = null;
      },
      child: new Transform(
        transform: new Matrix4.diagonal3(new Vector3(_scale, _scale, _scale)),
        alignment: FractionalOffset.center,
        child: widget.image,
      ),
    );
  }
}

While it seems to work, it would be great to get confirmation or correction from someone inside the Flutter team.

(Also, this only allows for zooming. Panning is not implemented.)

EDIT: Edited to reflect Collin's comment and his his more detailed explanation. But note there is still an open question with regards to private setState() and private fields that don't immediately affect the rendering.

Community
  • 1
  • 1
david.mihola
  • 12,062
  • 8
  • 49
  • 73
  • 1
    Your State should not take any constructor arguments. Use widget.image instead. Make your State's mutable member fields private (starting with _) and make sure to always modify them in a setState() block. – Collin Jackson May 09 '17 at 16:11
  • I updated my question with a solution for panning + zooming, although it doesn't let you zoom in past 1:1 pixels so it only works for large images. Might be possible to combine our approaches. – perlatus May 10 '17 at 07:32
  • Combined yours with mine and factored it out into a library with an adjustable scale factor. See updated question. – perlatus May 11 '17 at 21:20
  • Have you added panning yet? – alex351 Apr 08 '19 at 13:15
  • 1
    @alex351: Sorry, no. But perlatus made his version into a library which seems to very usable and does have panning: https://pub.dartlang.org/packages/zoomable_image – david.mihola Apr 09 '19 at 17:12
  • This library no longer works with Flutter v1.7.8+hotfix.4, and the repository has been archived so no issues can be filed against it. I would recommend searching pub.dev/flutter for libraries that suit your needs. – Daniel Allen Sep 25 '19 at 19:15