Flutter Zoomable Widget

28,905

Solution 1

As of Flutter 1.20, InteractiveViewer widget supports pan and Zoom out of the box.
To make any widget zoomable you need to simply wrap the child with InteractiveViewer.

@override
Widget build(BuildContext context) {
  return Center(
    child: InteractiveViewer(
      panEnabled: false, // Set it to false to prevent panning. 
      boundaryMargin: EdgeInsets.all(80),
      minScale: 0.5,
      maxScale: 4, 
      child: FlutterLogo(size: 200),
    ),
  );
}

Solution 2

This is working perfectly now, thanks for the reference @pskink.

import 'package:flutter/material.dart';
import 'package:matrix_gesture_detector/matrix_gesture_detector.dart';

class ZoomableWidget extends StatefulWidget {
  final Widget child;

  const ZoomableWidget({Key key, this.child}) : super(key: key);
  @override
  _ZoomableWidgetState createState() => _ZoomableWidgetState();
}

class _ZoomableWidgetState extends State<ZoomableWidget> {
  Matrix4 matrix = Matrix4.identity();

  @override
  Widget build(BuildContext context) {
    return MatrixGestureDetector(
      onMatrixUpdate: (Matrix4 m, Matrix4 tm, Matrix4 sm, Matrix4 rm) {
        setState(() {
          matrix = m;
        });
      },
      child: Transform(
        transform: matrix,
        child: widget.child,
      ),
    );
  }
}

Solution 3

I loved de resolution, you should put that in a packged in pub, you can even put some custom options, in my code I put doubletap to reset the zoom and locked the rotation.

import 'package:flutter/material.dart';
import 'package:matrix_gesture_detector/matrix_gesture_detector.dart';

class ZoomableWidget extends StatefulWidget {
  final Widget child;

  const ZoomableWidget({Key key, this.child}) : super(key: key);
  @override
  _ZoomableWidgetState createState() => _ZoomableWidgetState();
}

class _ZoomableWidgetState extends State<ZoomableWidget> {
  Matrix4 matrix = Matrix4.identity();
  Matrix4 zerada =  Matrix4.identity();

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onDoubleTap: (){
        setState(() {
          matrix = zerada;
        });
      },
      child: MatrixGestureDetector(
        shouldRotate: false,
        onMatrixUpdate: (Matrix4 m, Matrix4 tm, Matrix4 sm, Matrix4 rm) {
          setState(() {
            matrix = m;
          });
        },
        child: Transform(
          transform: matrix,
          child: widget.child,
        ),
      ),
    );
  }
}

Solution 4

As an alternative to MatrixGestureDetector, you can use the photo_view package: https://pub.dev/packages/photo_view

It has good limiting of the screen constraints so you can't drag the child off-screen, a bounce effect when hitting min/max size, and many other options.

It can be used with a custom child like this:

PhotoView.customChild(
    child: <your widget>
)

Solution 5

You can use Zoom Widget Zoom Widget only need set a canvas size and child

    Zoom(
    width: 1800,
    height: 1800,
    child: Center(
        child: Text("Happy zoom!!"),
    )
);
Share:
28,905
Aawaz Gyawali
Author by

Aawaz Gyawali

Android developer with knowledge of MySQL and php.

Updated on December 30, 2021

Comments

  • Aawaz Gyawali
    Aawaz Gyawali over 2 years

    What I want to build is a widget that can make its child widget zoomable similar to the zoomable behavior.

    Gestures I want to cover are

    1. Pinch To Zoom
    2. Double Tap to Zoom
    3. Tap to get the local Position of the widget

    Here is my widget plan:

    ZoomableWidget(
       child: // My custom Widget which should be zoomable.
    )
    

    Here is my current progress:

    import 'package:flutter/material.dart';
    import 'package:flutter/widgets.dart';
    import 'package:vector_math/vector_math_64.dart';
    
    class ZoomableWidget extends StatefulWidget {
      final Widget child;
    
      const ZoomableWidget({Key key, this.child}) : super(key: key);
      @override
      _ZoomableWidgetState createState() => _ZoomableWidgetState();
    }
    
    class _ZoomableWidgetState extends State<ZoomableWidget> {
      double _scale = 1.0;
      double _previousScale;
      @override
      Widget build(BuildContext context) {
        return ClipRect(
          child: GestureDetector(
            onScaleStart: (ScaleStartDetails details) {
              _previousScale = _scale;
            },
            onScaleUpdate: (ScaleUpdateDetails details) {
              setState(() {
                _scale = _previousScale * details.scale;
              });
            },
            onScaleEnd: (ScaleEndDetails details) {
              _previousScale = null;
            },
            child: Transform(
              transform: Matrix4.diagonal3(Vector3(_scale.clamp(1.0, 5.0),
                  _scale.clamp(1.0, 5.0), _scale.clamp(1.0, 5.0))),
              alignment: FractionalOffset.center,
              child: widget.child,
            ),
          ),
        );
      }
    }
    

    The problem I have faced is, I cannot change the center of the pinch thus the image only zooms at (0,0) even after I zoom in the corner. Also, I cannot access horizontal drag and vertical drag to scroll the widget.

    Thanks in advance.

  • autlunatic
    autlunatic over 4 years
    I like this solution, the one thing i don't like is, that when you zoom and doubletap and zoom again. the zooming seems to start randomly anywhere. When you use a Key, wich is newly generated on doubleTap, it works better for me. Please let me know, if there is a better solution.
  • Evripides Kyriacou
    Evripides Kyriacou almost 4 years
    Why is so hard to zoom I have a list with images that displaying using carouse, I used the above and is very hard to zoom, when I am trying to zoom the app is trying to scroll down , if I placed my fingers for a few seconds then is working fine
  • Jose Georges
    Jose Georges almost 4 years
    can you explain your solution? @autlunatic, I'm not sure which widget should get the key
  • autlunatic
    autlunatic almost 4 years
    i cannot remember exactly. Maybe i mixed something up. i have a version of this where doubletap reverts to "default Zoom". There was a problem that zooming after Doubletap startet at a "wrong" point because it had saved the Matrix or something else. To Solve this i generated a new gestureKey for the Gesturedetector on doubletap, wich worked fine for me.
  • kakyo
    kakyo over 3 years
    Doesn't seem to be able to scale down html page inside a WebView, only up-scaling works.
  • Mrak Vladar
    Mrak Vladar over 3 years
    How can you make it to restore to default after the interaction is over. Take Interacting with an Image for example.
  • Till Friebe
    Till Friebe over 3 years
    @MrakVladar You can restore to the default by assigning a TransformationController to the InteractiveViewer and set the value of the controller to Matrix4.identity().
  • Noor
    Noor about 3 years
    How to make it work smoothly with a listview inside? Sometimes have to press it really hard to zoom in/out.
  • Alpit Panchal
    Alpit Panchal about 3 years
    it's helpful full but I used InteractiveViewer with GestureDetector by changing the position of the multiple widgets that time it's not working perfectly because of InteractiveViewer only works according to the child widget size. So I can't give inside the Stack widget every child full size. what we do for this issue. Please help and Thanks
  • litt
    litt over 2 years
    Hey @autlunatic, I'm facing the same issue. Can you give a code snippet on how you generated the key for the GestureDetector to solve this issue? It'd be great if you could do this. Thanks in advance.
  • autlunatic
    autlunatic over 2 years
    @RCN look at Jilly Tabogas next answer. I added a key which i created new on doubletap. GestureDetector( key: _gestureKey, onDoubleTap: () { setState(() { _gestureKey = GlobalKey(); matrix = zerada; }); }, child: MatrixGestureDetector( shouldRotate: false, onMatrixUpdate: (m, tm, sm, rm) { setState(() { matrix = m; }); }, child: Transform( transform: matrix, child: widget.child, ), ), ); }
  • Oliver Dixon
    Oliver Dixon about 2 years
    Doesn't work on MacOS, just errors with 'package:flutter/src/rendering/mouse_tracker.dart': Failed assertion: line 201 pos 12: '!_debugDuringDeviceUpdate': is not true.