Open aloisdeniel opened 4 years ago
+1 on this, might be very useful :)
I am searching since a week for a solution to set an initial matrix.
Thump up to get this issue done.
Has anyone a quick and dirty solution for this?
I added a initMatrix method for this. I created a new class called: MyMatrixGestureDetector
import 'dart:math';
import 'package:flutter/widgets.dart'; import 'package:vector_math/vector_math_64.dart';
typedef MyMatrixGestureDetectorCallback = void Function( Matrix4 matrix, Matrix4 translationDeltaMatrix, Matrix4 scaleDeltaMatrix, Matrix4 rotationDeltaMatrix);
/// [MatrixGestureDetector] detects translation, scale and rotation gestures /// and combines them into [Matrix4] object that can be used by [Transform] widget /// or by low level [CustomPainter] code. You can customize types of reported /// gestures by passing [shouldTranslate], [shouldScale] and [shouldRotate] /// parameters. /// class MyMatrixGestureDetector extends StatefulWidget { /// [Matrix4] change notification callback /// final MyMatrixGestureDetectorCallback onMatrixUpdate;
/// The [child] contained by this detector. /// /// {@macro flutter.widgets.child} /// final Widget child;
/// Whether to detect translation gestures during the event processing. /// /// Defaults to true. /// final bool shouldTranslate;
final Matrix4 initMatrix;
/// Whether to detect scale gestures during the event processing. /// /// Defaults to true. /// final bool shouldScale;
/// Whether to detect rotation gestures during the event processing. /// /// Defaults to true. /// final bool shouldRotate;
/// Whether [ClipRect] widget should clip [child] widget. /// /// Defaults to true. /// final bool clipChild;
/// When set, it will be used for computing a "fixed" focal point /// aligned relative to the size of this widget. final Alignment focalPointAlignment;
const MyMatrixGestureDetector({ Key key, @required this.onMatrixUpdate, @required this.child, @required this.initMatrix, this.shouldTranslate = true, this.shouldScale = true, this.shouldRotate = true, this.clipChild = true, this.focalPointAlignment, }) : assert(onMatrixUpdate != null), assert(child != null), super(key: key);
@override _MyMatrixGestureDetectorState createState() => _MyMatrixGestureDetectorState();
/// /// Compose the matrix from translation, scale and rotation matrices - you can /// pass a null to skip any matrix from composition. /// /// If [matrix] is not null the result of the composing will be concatenated /// to that [matrix], otherwise the identity matrix will be used. /// static Matrix4 compose(Matrix4 matrix, Matrix4 translationMatrix, Matrix4 scaleMatrix, Matrix4 rotationMatrix) { if (matrix == null) matrix = Matrix4.identity(); if (translationMatrix != null) matrix = translationMatrix matrix; if (scaleMatrix != null) matrix = scaleMatrix matrix; if (rotationMatrix != null) matrix = rotationMatrix * matrix; return matrix; }
/// /// Decomposes [matrix] into [MatrixDecomposedValues.translation], /// [MatrixDecomposedValues.scale] and [MatrixDecomposedValues.rotation] components. /// static MyMatrixDecomposedValues decomposeToValues(Matrix4 matrix) { var array = matrix.applyToVector3Array([0, 0, 0, 1, 0, 0]); Offset translation = Offset(array[0], array[1]); Offset delta = Offset(array[3] - array[0], array[4] - array[1]); double scale = delta.distance; double rotation = delta.direction; return MyMatrixDecomposedValues(translation, scale, rotation); } }
class _MyMatrixGestureDetectorState extends State
@override Widget build(BuildContext context) { Widget child = widget.clipChild ? ClipRect(child: widget.child) : widget.child; return GestureDetector( onScaleStart: onScaleStart, onScaleUpdate: onScaleUpdate, child: child, ); }
@override void initState() { super.initState(); matrix = widget.initMatrix; }
_ValueUpdater
void onScaleStart(ScaleStartDetails details) { translationUpdater.value = details.focalPoint; rotationUpdater.value = double.nan; scaleUpdater.value = 1.0; }
void onScaleUpdate(ScaleUpdateDetails details) { //print(details); translationDeltaMatrix = Matrix4.identity(); scaleDeltaMatrix = Matrix4.identity(); rotationDeltaMatrix = Matrix4.identity();
// handle matrix translating
if (widget.shouldTranslate) {
Offset translationDelta = translationUpdater.update(details.focalPoint);
translationDeltaMatrix = _translate(translationDelta);
print(matrix);
matrix = translationDeltaMatrix * matrix;
}
Offset focalPoint;
if (widget.focalPointAlignment != null) {
focalPoint = widget.focalPointAlignment.alongSize(context.size);
} else {
RenderBox renderBox = context.findRenderObject();
focalPoint = renderBox.globalToLocal(details.focalPoint);
}
// handle matrix scaling
if (widget.shouldScale && details.scale != 1.0) {
double scaleDelta = scaleUpdater.update(details.scale);
scaleDeltaMatrix = _scale(scaleDelta, focalPoint);
matrix = scaleDeltaMatrix * matrix;
}
// handle matrix rotating
if (widget.shouldRotate && details.rotation != 0.0) {
if (rotationUpdater.value.isNaN) {
rotationUpdater.value = details.rotation;
} else {
double rotationDelta = rotationUpdater.update(details.rotation);
rotationDeltaMatrix = _rotate(rotationDelta, focalPoint);
matrix = rotationDeltaMatrix * matrix;
}
}
widget.onMatrixUpdate(
matrix, translationDeltaMatrix, scaleDeltaMatrix, rotationDeltaMatrix);
}
Matrix4 _translate(Offset translation) { var dx = translation.dx; var dy = translation.dy;
// ..[0] = 1 # x scale
// ..[5] = 1 # y scale
// ..[10] = 1 # diagonal "one"
// ..[12] = dx # x translation
// ..[13] = dy # y translation
// ..[15] = 1 # diagonal "one"
return Matrix4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, dx, dy, 0, 1);
}
Matrix4 _scale(double scale, Offset focalPoint) { var dx = (1 - scale) focalPoint.dx; var dy = (1 - scale) focalPoint.dy;
// ..[0] = scale # x scale
// ..[5] = scale # y scale
// ..[10] = 1 # diagonal "one"
// ..[12] = dx # x translation
// ..[13] = dy # y translation
// ..[15] = 1 # diagonal "one"
return Matrix4(scale, 0, 0, 0, 0, scale, 0, 0, 0, 0, 1, 0, dx, dy, 0, 1);
}
Matrix4 _rotate(double angle, Offset focalPoint) { var c = cos(angle); var s = sin(angle); 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);
} }
typedef _OnUpdate
class _ValueUpdater
_ValueUpdater({this.onUpdate});
T update(T newValue) { T updated = onUpdate(value, newValue); value = newValue; return updated; } }
class MyMatrixDecomposedValues { /// Translation, in most cases useful only for matrices that are nothing but /// a translation (no scale and no rotation). final Offset translation;
/// Scaling factor. final double scale;
/// Rotation in radians, (-pi..pi) range. final double rotation;
MyMatrixDecomposedValues(this.translation, this.scale, this.rotation);
@override String toString() { return 'MatrixDecomposedValues(translation: $translation, scale: ${scale.toStringAsFixed(3)}, rotation: ${rotation.toStringAsFixed(3)})'; } }
In your build method:
return MyMatrixGestureDetector( initMatrix: widget.appearience, onMatrixUpdate: (m, tm, sm, rm) { notifier.value = m; print(m); }, child: AnimatedBuilder( animation: notifier, builder: (ctx, child) { return Transform( transform: notifier.value, child: widget.child, ); }, ), ); }
It is pretty common to be able to edit an already transformed image.
For this use case, it would be needed to initialize the widget with an initial transform.