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 的适当内置方法进行转换,除非您真的知道自己在做什么。
此外,请记住 转换顺序很重要。进行平移然后旋转不同于进行旋转 -> 平移。
我正在尝试创建一个专栏 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 的适当内置方法进行转换,除非您真的知道自己在做什么。
此外,请记住 转换顺序很重要。进行平移然后旋转不同于进行旋转 -> 平移。