Reputation: 1
I'm currently using the matrix_gesture_detector package to scale, transform and rotate a Transform
widget.
Everything works fine but to improve UX I would like to snap the widget at 90, 180, 270 or 360 degrees once the user rotates the widget close enough to said angles.
Edit: To clarify I would like the user to be able to freely rotate the widget, but snap into the nearest 90 degree rotation within whichever quadrant it is, once it gets close enough. Hence, the solution should detect that "closeness" and then act accordingly. Please visit this link to see a GIF which shows the desired effect
How can I achieve this?
Below is the code snippet
Widget transformContainer() {
Matrix4 matrix;
GlobalKey matrixDetectorKey = GlobalKey();
return MatrixGestureDetector(
key: matrixDetectorKey,
onMatrixUpdate: (m, tm, sm, rm) {
setState(() {
matrix = MatrixGestureDetector.compose(matrix, tm, sm, rm);
});
},
child: Transform(
transform: matrix,
child: Container(
padding: EdgeInsets.all(24.0),
width: 100.0,
height: 200.0,
color: Colors.teal,
),
),
);}
Upvotes: 0
Views: 2330
Reputation: 450
For this to work, you have to check if the rotation of the matrix is closing in on a snapping point and rotate the Z-Axis to that point. I was able to write this code which works for this scenario. You may have to tune the threshold
to adjust the "snappiness" to your taste.
import vector_math and use the following code:
import 'package:vector_math/vector_math_64.dart' as vec;
onMatrixUpdate: (Matrix4 m, Matrix4 tm, Matrix4 sm, Matrix4 rm) {
Matrix4 ogRm = rm.clone();
double radian = MatrixGestureDetector.decomposeToValues(m).rotation;
double degrees = vec.degrees(radian);
double delta_0 = vec.absoluteError(degrees, 0);
double delta_90 = vec.absoluteError(degrees, 90);
double delta_180 = vec.absoluteError(degrees, 180);
double delta_270 = vec.absoluteError(degrees, -90);
double threshold = 4;
if (delta_0 <= threshold) {
rm.rotateZ(vec.radians(0) - radian);
} else if (delta_90 <= threshold) {
rm.rotateZ(vec.radians(90) - radian);
} else if (delta_180 <= threshold) {
rm.rotateZ(vec.radians(180) - radian);
} else if (delta_270 <= threshold) {
rm.rotateZ(vec.radians(270) - radian);
}
// update gesture matrix
if (ogRm != rm) m = m * rm;
setState(() {
//transform your widget using this matrix
matrix = m;
});
}
Note that 360 is the same as 0, so there's no need to check for it.
Upvotes: 1
Reputation: 31
You will have the rotate function inside the library. this code will work for snapping to 0 degrees. Basically what I am doing is if the difference in rotation and x-axis is less than 0.2 radians, I snap to 0 degrees while keeping the rotation in a separate variable. When the user moves beyond this, I add this value again and keep rotating normally.
Matrix4 _rotate(double angle, Offset focalPoint) {
double toBeRotated = 0;
var array = matrix.applyToVector3Array([0, 0, 0, 1, 0, 0]);
Offset delta = Offset(array[3] - array[0], array[4] - array[1]);
double rotation = delta.direction;
deltaAngle = deltaAngle + angle;
if ((rotation + deltaAngle).abs() > 0.2) {
toBeRotated = deltaAngle;
deltaAngle = 0;
} else if (rotation != 0 && (rotation + deltaAngle).abs() <= 0.2) {
toBeRotated = -rotation;
deltaAngle = deltaAngle + rotation;
} else {
toBeRotated = 0;
}
var c = cos(toBeRotated);
var s = sin(toBeRotated);
var dx = (1 - c) * focalPoint.dx + s * focalPoint.dy;
var dy = (1 - c) * focalPoint.dy - s * focalPoint.dx;
// ..[0] = c # x scale
// ..[1] = s # y skew
// ..[4] = -s # x skew
// ..[5] = c # y scale
// ..[10] = 1 # diagonal "one"
// ..[12] = dx # x translation
// ..[13] = dy # y translation
// ..[15] = 1 # diagonal "one"
return Matrix4(c, s, 0, 0, -s, c, 0, 0, 0, 0, 1, 0, dx, dy, 0, 1);
}
Upvotes: 0
Reputation: 12673
MatrixGestureDetector(
onMatrixUpdate: (m, tm, sm, rm) {
setState(() {
//270 is the angle
m.rotate(m.getTranslation(), 270);
matrix4 = m;
});
},
child: Transform(
transform: matrix4,
child: Container(
padding: EdgeInsets.all(24.0),
width: 100,
height: 200,
color: Colors.teal,
),
),
),
Upvotes: 0