I have a following code:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:vector_math/vector_math.dart' as vector;
import 'dart:math' as math;
void main() {
debugPaintSizeEnabled = true;
return runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.grey,
body: SafeArea(
child: TestComp(),
),
),
);
}
}
class TestComp extends StatefulWidget {
@override
_TestCompState createState() => _TestCompState();
}
class _TestCompState extends State<TestComp> {
double _y = 0;
double _x = 0;
double _height = 200;
double _width = 300;
int angle = -136;
vector.Vector4 testRotation(var dx, var dy) {
vector.Matrix4 matrix = vector.Matrix4.identity();
matrix..rotateZ(math.pi / 180 * angle);
return matrix * vector.Vector4(dx, dy, 0, 0);
}
double get _aspectRatio {
return _height / _width;
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned(
top: _y,
left: _x,
child: Transform.rotate(
angle: math.pi / 180 * angle, //-136,
child: Container(
height: _height,
width: _width,
color: Colors.black,
child: Stack(
overflow: Overflow.visible,
children: <Widget>[
Positioned(
top: 0,
left: 0,
child: GestureDetector(
onPanUpdate: (details) {
var newdx =
testRotation(details.delta.dx, details.delta.dy)[0];
var newdy =
testRotation(details.delta.dx, details.delta.dy)[1];
setState(() {
_y += newdy;
_x += newdx;
});
},
child: Image.network(
"https://via.placeholder.com/300x200",
height: _height,
width: _width,
fit: BoxFit.fill,
),
),
),
Positioned(
top: 0,
left: 0,
child: GestureDetector(
onPanUpdate: (DragUpdateDetails details) {
var position = testRotation(
details.delta.dx,
details.delta.dy,
);
// print(position);
var dx = position[0];
var dy = position[1];
var newWidth = _width - dx;
var newHeight = newWidth * _aspectRatio;
setState(() {
_y = _y + (_height - newHeight);
_x = _x + dx;
_width = newWidth;
_height = newHeight;
});
},
child: Container(
width: 10,
height: 10,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(10),
),
),
),
),
],
),
),
),
),
],
);
}
}
There's a handle in top left of image which will be used to resize. I expect the rotation to be like:
But it doesn't work that way. The resize is messed up. Please help me in this.
try this FooResizer
widget - you could add 4 additional corner sizers in the same way:
final SIZE = 64.0;
final sizerSize = Size.square(SIZE);
final sizers = [
Sizer(Alignment.centerLeft, EdgeInsets.only(left: 1), Offset(1, 0)),
Sizer(Alignment.topCenter, EdgeInsets.only(top: 1), Offset(0, 1)),
Sizer(Alignment.centerRight, EdgeInsets.only(right: 1), Offset(-1, 0)),
Sizer(Alignment.bottomCenter, EdgeInsets.only(bottom: 1), Offset(0, -1)),
];
class Sizer {
final Alignment alignment;
final EdgeInsets insets;
final Offset mask;
Sizer(this.alignment, this.insets, this.mask);
}
class FooResizer extends StatefulWidget {
@override
_FooResizerState createState() => _FooResizerState();
}
class _FooResizerState extends State<FooResizer> with TickerProviderStateMixin {
ValueNotifier<Rect> rect;
ValueNotifier<int> serial = ValueNotifier<int>(0);
Sizer currentSizer;
Rect savedRect;
Offset savedOffset;
AnimationController controller;
GlobalKey stackKey = GlobalKey();
@override
Widget build(BuildContext context) {
controller ??= AnimationController(vsync: this, duration: Duration(milliseconds: 300));
return LayoutBuilder(
builder: (context, constraints) {
_init(constraints.biggest);
return AnimatedBuilder(
animation: Listenable.merge([rect, serial, controller]),
builder: (context, child) {
return Transform.rotate(
angle: math.pi / 8,
child: Stack(
key: stackKey,
overflow: Overflow.visible,
children: [
Positioned.fromRect(
rect: rect.value,
child: Material(
borderRadius: BorderRadius.circular(12),
color: Colors.grey[300],
elevation: 4,
child: FlutterLogo(),
),
),
...sizers.map(_sizerBuilder),
],
),
);
},
);
},
);
}
Widget _sizerBuilder(Sizer sizer) {
var isCurrent = currentSizer == sizer;
var finalRect = sizer.alignment.inscribe(sizerSize, rect.value).shift(sizer.mask * SIZE * -0.33);
var centerRect = Alignment.center.inscribe(sizerSize, rect.value);
var interpolated = Rect.lerp(finalRect, centerRect, isCurrent? 0 : controller.value);
return Positioned.fromRect(
rect: interpolated,
child: GestureDetector(
onPanStart: (details) => _panStart(sizer, details),
onPanUpdate: _panUpdate,
onPanEnd: _panEnd,
child: Opacity(
opacity: isCurrent? 1 : 1 - controller.value,
child: AnimatedContainer(
duration: isCurrent? Duration(milliseconds: 500) : Duration.zero,
decoration: ShapeDecoration(
shape: isCurrent? RoundedRectangleBorder() : CircleBorder(),
color: isCurrent? Colors.orange[800] : Colors.green,
),
),
),
),
);
}
_panStart(Sizer sizer, DragStartDetails details) {
// timeDilation = 10;
currentSizer = sizer;
savedRect = rect.value;
savedOffset = _invertedOffset(details.globalPosition);
serial.value++;
controller.forward();
}
_panUpdate(DragUpdateDetails details) {
assert(currentSizer != null);
var delta = savedOffset - _invertedOffset(details.globalPosition);
var insets = EdgeInsets.fromLTRB(
currentSizer.insets.left * delta.dx * currentSizer.mask.dx,
currentSizer.insets.top * delta.dy * currentSizer.mask.dy,
currentSizer.insets.right * delta.dx * currentSizer.mask.dx,
currentSizer.insets.bottom * delta.dy * currentSizer.mask.dy,
);
rect.value = insets.inflateRect(savedRect);
}
_panEnd(DragEndDetails details) {
assert(currentSizer != null);
currentSizer = null;
serial.value++;
controller.reverse();
}
Offset _invertedOffset(Offset offset) {
RenderBox rb = stackKey.currentContext.findRenderObject();
return rb.globalToLocal(offset);
}
void _init(Size size) {
var r = Offset.zero & size;
rect = ValueNotifier(r.deflate(100));
}
}
and the result would be something like this: