Search code examples
flutterdartzoomingscalegesture

Flutter: How to set a max/min scale using matrix_gesture_detector


Im trying to make a Column scale/movable. But how can I set a max zoom and a min zoom? So that you don´t zoom to infinity.

Using this method right now:

 Matrix4 matrix = Matrix4.identity();

     MatrixGestureDetector(
      shouldRotate: false
      onMatrixUpdate: (Matrix4 m, Matrix4 tm, Matrix4 sm, Matrix4 rm) {
        setState(() {
          matrix = m;
        });
      },
      child: Transform(
        transform: matrix,
        child: Column(
      ),
    ),

Solution

  • Update: As @rdnobrega kindly explained, this is not an ideal solution by any means. It will only work when the only transformed Matrix is scale, and will break with other transformations. I am no math expert or Matrix4 expert, so the solution below is at your own risk.

    I had the same problem and took a while but came across the solution.

    After digging around just a bit in Flutter's Transform.scale source reveals this line which gives us a hint:

    transform = Matrix4.diagonal3Values(scale, scale, 1.0)
    

    It's using the diagonal values from the Matrix4 you receive in onMatrixUpdate. So it takes x from the 1st Vector4, y from the 2nd, and z from the 3rd. (the 4th is fixed from what I can tell). So those are the values you need to limit. In this example I've made a small _minMax method which bounds the scale to the relevant min/max when relevant (they can be passed null to ignore either side of the limit).

    I used this to limit the scale:

    typedef MathF<T extends num> = T Function(T, T);
    typedef VFn = Vector4 Function(double x, double y, double z, double w);
    
    double _minMax(num _min, num _max, num actual) {
      if (_min == null && _max == null) {
        return actual.toDouble();
      }
    
      if (_min == null) {
        return min(_max.toDouble(), actual.toDouble());
      }
    
      if (_max == null) {
        return max(_min.toDouble(), actual.toDouble());
      }
    
      return min(_max.toDouble(), max(_min.toDouble(), actual.toDouble()));
    }
    
    // ... ... ...
    
    onMatrixUpdate: (Matrix4 m, Matrix4 tm, Matrix4 sm, Matrix4 rm) {
        var finalM = Matrix4.copy(m);
        Map<int, VFn> colmap = {
          0: (x, y, z, w) {
            x = _minMax(widget.minScale, widget.maxScale, x);
            return Vector4(x, y, z, w);
          },
          1: (x, y, z, w) {
            y = _minMax(widget.minScale, widget.maxScale, y);
            return Vector4(x, y, z, w);
          },
          2: (x, y, z, w) {
            z = _minMax(widget.minScale, widget.maxScale, z);
            return Vector4(x, y, z, w);
          },
        };
        for (var col in colmap.keys) {
          var oldCol = m.getColumn(col);
          var colD = colmap[col];
          if (colD != null) {
            finalM.setColumn(col, colD(oldCol.x, oldCol.y, oldCol.z, oldCol.w));
          }
        }
        setState(() {
          matrix = finalM;
        });
      },