Flutter:如何使用 matrix_gesture_detector 设置 max/min 比例

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

我正在尝试创建一个专栏 scale/movable。但是如何设置最大缩放和最小缩放?这样你就不会放大到无穷大。

现在使用这个方法:

 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(
      ),
    ),

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.

我遇到了同样的问题,花了一段时间但找到了解决方案。

在 Flutter 的 Transform.scale source 中稍微挖掘了一下后,我们发现了这一行,它给了我们一个提示:

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

它使用的是您在 onMatrixUpdate 中收到的 Matrix4 的对角线值。因此,第一个 Vector4 需要 x,第二个需要 y,第三个需要 z。 (据我所知,第 4 个是固定的)。所以这些是您需要限制的值。在这个例子中,我做了一个小的 _minMax 方法,它在相关时将比例限制到相关的 min/max (它们可以被传递 null 以忽略限制的任一侧)。

我用这个来限制比例:

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;
    });
  },

只是为了补充@casraf 的回答。

Flutter(以及其他图形语言如OpenGL)使用的坐标系是齐次坐标。这只是存在于这些矩阵中用于透视计算的辅助维度的奇特名称,就像当一个东西靠近它时它会变大,如果它远离它会变小。

因此,在这些坐标中,与笛卡尔不同,我们需要一个额外的维度。如果我们试图从透视图中表示 space 的 3 个维度,我们需要一个 4 维矩阵。

出于好奇,等轴测游戏和类似 2d 的 3d 游戏实际上会混淆这个额外的 W 维度(矩阵的第 4 行),因为根本不应用透视。

也就是说,Flutter/openGL 计算屏幕中的最终顶点,将 1 行矩阵 (x, y, z, 1) 形式的原始坐标乘以变换矩阵 Matrix4。如果您进行数学计算,结果仍然是一个 1 行矩阵,但是 translated/rotated/scaled/skewed 从原始顶点到矩阵。

这意味着幕后有一个庞大的矩阵乘法,手动更改特定系数可能会导致意外行为。在@casraf 的情况下,它适用于不涉及平移或旋转,因此更改的系数等于比例系数。如果您应用任何其他转换,结果可能会有很大差异。

TL;DR

始终使用 Matrix4 的适当内置方法进行转换,除非您真的知道自己在做什么。

此外,请记住 转换顺序很重要。进行平移然后旋转不同于进行旋转 -> 平移。