Dragging a rotated container in flutter - flutter

Normally, to drag a container or an image around the screen I would do this:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
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;
#override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned(
top: _y,
left: _x,
child: Transform.rotate(
angle: math.pi / 180 * 0,
child: Container(
height: 200,
width: 300,
color: Colors.black,
child: Stack(
children: <Widget>[
Positioned(
top: 0,
left: 0,
child: GestureDetector(
onPanUpdate: (details) {
var dx = details.delta.dx;
var dy = details.delta.dy;
setState(() {
_y += dy;
_x += dx;
});
},
child:
Image.network("https://via.placeholder.com/300x200"),
),
),
],
),
),
),
),
],
);
}
}
This works perfectly fine. However, when I rotate this container to say -136 degrees, it doesn't move correctly.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
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;
#override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned(
top: _y,
left: _x,
child: Transform.rotate(
angle: math.pi / 180 * -136,
child: Container(
height: 200,
width: 300,
color: Colors.black,
child: Stack(
children: <Widget>[
Positioned(
top: 0,
left: 0,
child: GestureDetector(
onPanUpdate: (details) {
var dx = details.delta.dx;
var dy = details.delta.dy;
setState(() {
_y += dy;
_x += dx;
});
},
child:
Image.network("https://via.placeholder.com/300x200"),
),
),
],
),
),
),
),
],
);
}
}
My requirement here is to have Transform.rotate on Stack>Positioned. The inner stack can have many more elements so I want the GestureDetector to be only inside image because the coordinates should only be updated when the image is moved. Please help me with this.

there was problem in GestureDetector it was getting x and y value rotated -136 degree
just place gesture detector above Transform Widget Than it will take correct x and
y values
your error is solved
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;
#override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned(
top: _y,
left: _x,
child: GestureDetector(
onPanUpdate: (details) {
var dx = details.delta.dx;
var dy = details.delta.dy;
setState(() {
_y += dy;
_x += dx;
});
},
child: Transform.rotate(
angle: math.pi / 180 * -136,
child: Container(
height: 200,
width: 300,
color: Colors.black,
child: Stack(
children: <Widget>[
Positioned(
top: 0,
left: 0,
child: Image.network("https://via.placeholder.com/300x200"),
),
],
),
),
),
),
),
],
);
}
}

Related

How to keep clipped area constant while resizing a container dynamically in Flutter?

I have a following code which has an image that is cropped to 150px by default. What I want is to make this cropped area remain constant while resizing the container using the resize handle.
I want the resize happen like so: (See how the cropped area is constant i.e. it doesn't move. It's as if I'm resizing an image of cropped width & height)
But in my code, it works like this:
See how the cropped area also gets cropped when resizing. I want to keep the cropped area constant(not move) while resizing. Any help would be greatly appreciated.
Here's my code:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
debugPaintSizeEnabled = true;
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Test(),
);
}
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
double _cropWidth = 150;
double _width = 300;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Positioned(
top: 0,
left: 0,
child: ClipRect(
clipper: Cropper(
height: 200,
width: _cropWidth,
top: 0,
left: 0,
),
child: Image.network(
"https://picresize.com/images/t1rsz_pexels-photo-1108099.jpg",
height: 200,
width: _width,
fit: BoxFit.fill,
),
),
),
Positioned(
top: 0,
left: 0,
child: Container(
height: 200,
width: _cropWidth,
child: Stack(
children: <Widget>[
Positioned(
top: 80,
left: _cropWidth - 20,
child: GestureDetector(
onPanUpdate: (d) {
_cropWidth = _cropWidth + d.delta.dx;
_width = _width + d.delta.dx;
setState(() {});
},
child:
Container(height: 20, width: 20, color: Colors.blue),
),
)
],
),
),
),
],
),
);
}
}
class Cropper extends CustomClipper<Rect> {
final double left;
final double top;
final double width;
final double height;
const Cropper({
this.left = 0.0,
this.top = 0.0,
this.width = 0.0,
this.height = 0.0,
});
#override
Rect getClip(Size a) {
return Rect.fromLTWH(left, top, width, height);
}
#override
bool shouldReclip(CustomClipper<Rect> old) => true;
}
There is no problem in cropWidth. The problem is in the original image width.
You may notice the area cropWidth and width should be related in some ratio. You should calculate crop width from width (or reverse).
In your problem, the ratio seems to be a fixed number:
double _ratio = 0.5;
double _cropWidth = 150;
double _width = 300;
...
onPanUpdate: (d) {
_cropWidth = _cropWidth + d.delta.dx;
_width = _cropWidth / _ratio;
setState(() {});
}
...
If you want, you can change the crop ratio by modifying it
Positioned(
top: 120,
left: _cropWidth - 20,
child: GestureDetector(
onPanUpdate: (d) {
_cropWidth = _cropWidth + d.delta.dx;
_ratio = _cropWidth / _width;
setState(() {});
},
child:
Container(height: 20, width: 20, color: Colors.green),
),
),
Is this what you want?
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
debugPaintSizeEnabled = true;
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Test(),
);
}
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
double _cropWidth = 300;
double _width = 300;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Positioned(
top: 0,
left: 0,
child: ClipRect(
clipper: Cropper(
height: 200,
width: _cropWidth,
top: 0,
left: 0,
),
child: Image.network(
"https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/dog-puppy-on-garden-royalty-free-image-1586966191.jpg",
height: 200,
width: _width,
fit: BoxFit.fill,
),
),
),
Positioned(
top: 0,
left: 0,
child: Container(
height: 200,
width: _cropWidth,
child: Stack(
children: <Widget>[
Positioned(
top: 80,
left: _cropWidth - 20,
child: GestureDetector(
onPanUpdate: (d) {
_cropWidth = _cropWidth + d.delta.dx;
_width = _width + d.delta.dx;
setState(() {});
},
child:
Container(height: 20, width: 20, color: Colors.blue),
),
)
],
),
),
),
],
),
);
}
}
class Cropper extends CustomClipper<Rect> {
final double left;
final double top;
final double width;
final double height;
const Cropper({
this.left = 0.0,
this.top = 0.0,
this.width = 0.0,
this.height = 0.0,
});
#override
Rect getClip(Size a) {
return Rect.fromLTWH(left, top, width, height);
}
#override
bool shouldReclip(CustomClipper<Rect> old) => true;
}
i am also try this to do thing, may be its helpful for you
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
debugPaintSizeEnabled = true;
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Test(),
);
}
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
double _cropWidth = 350;
double _width = 350;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
ClipRect(
clipper: Cropper(
height: 200,
width: _cropWidth,
),
child: Image.network(
"https://images.pexels.com/photos/1108099/pexels-photo-1108099.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
height: 200,
width: _width,
fit: BoxFit.fill,
),
),
Container(
height: 200,
width: _cropWidth,
child: Stack(
children: <Widget>[
Positioned(
top: 80,
left: _cropWidth - 20,
child: GestureDetector(
onPanUpdate: (d) {
_cropWidth = _cropWidth + d.delta.dx;
_width = _width + d.delta.dx;
setState(() {});
},
child:
Container(height: 20, width: 20, color: Colors.blue),
),
)
],
),
),
],
),
);
}
}
class Cropper extends CustomClipper<Rect> {
final double left;
final double top;
final double width;
final double height;
const Cropper({
this.left = 0.0,
this.top = 0.0,
this.width = 0.0,
this.height = 0.0,
});
#override
Rect getClip(Size a) {
return Rect.fromLTWH(left, top, width, height);
}
#override
bool shouldReclip(CustomClipper<Rect> old) => true;
}

Flutter onPanStart invokes late when the widget is wrapped inside InteractiveViewer

I just have a simple container which has a resize handle on center right which means the container can be resized using that handle. It is wrapped inside InteractiveViewer widget.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
debugPaintSizeEnabled = true;
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
double _width = 300;
double _height = 200;
#override
Widget build(BuildContext context) {
final m = MediaQuery.of(context);
return Scaffold(
body: SafeArea(
child: Container(
height: m.size.height,
width: m.size.width,
child: InteractiveViewer(
maxScale: 30,
child: Stack(
children: <Widget>[
Container(height: _height, width: _width, color: Colors.red),
Positioned(
top: _height / 2 - 10,
left: _width - 20,
child: GestureDetector(
onPanStart: (details) {
print("PAN STARTED");
},
onPanUpdate: (details) {
final delta = details.delta;
setState(() {
_width = _width + delta.dx;
});
},
child: Container(height: 20, width: 20, color: Colors.blue),
),
)
],
),
),
),
),
);
}
}
The problem is onPanStart invokes a lot late after the initial drag. Any help would be greatly appreciated.
Note: This problem cannot be replicated in DartPad. In DartPad it works fine. The problem can only be seen in simulators/phones.
I found a fix for this issue. Just use Listener instead of GestureDetector (it has substitutes for the most parameters: onPanStart -> onPointerDown, onPanUpdate -> onPointerMove). With Listener, there will be no delay.
Minimum code example:
import 'package:flutter/material.dart';
class IssueExamplePage extends StatefulWidget {
#override
_IssueExamplePageState createState() => _IssueExamplePageState();
}
class _IssueExamplePageState extends State<IssueExamplePage> {
bool drawingBlocked = false;
List<Offset> points;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
child: InteractiveViewer(
child: AbsorbPointer(
absorbing: drawingBlocked,
child: Listener(
onPointerMove: (details) {
RenderBox renderBox = context.findRenderObject();
Offset cursorLocation = renderBox.globalToLocal(details.localPosition);
setState(() {
points = List.of(points)..add(cursorLocation);
});
},
child: CustomPaint(
painter: MyPainter(points),
size: Size.infinite
),
),
),
),
),
),
floatingActionButton: Column(
children: [
FloatingActionButton(
child: Icon(Icons.clear),
onPressed: () {
setState(() {
points = [];
});
}
),
FloatingActionButton(
child: Icon(drawingBlocked? CupertinoIcons.hand_raised_fill : CupertinoIcons.hand_raised),
onPressed: () {
drawingBlocked = !drawingBlocked;
}
),
],
),
);
}
}
class MyPainter extends CustomPainter {
MyPainter(this.points);
List<Offset> points;
Paint paintBrush = Paint()
..color = Colors.blue
..strokeWidth = 5
..strokeJoin = StrokeJoin.round;
#override
void paint(Canvas canvas, Size size) {
for (int i = 0; i < points.length - 1; i++) {
canvas.drawLine(points[i], points[i + 1], paintBrush);
}
}
#override
bool shouldRepaint(MyPainter oldDelegate) {
return points != oldDelegate.points;
}
}

Moving a rotated image using GestureDetector in flutter

I have a following code:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:math' as math;
void main() {
debugPaintSizeEnabled = true;
return runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
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;
#override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned(
top: _y,
left: _x,
child: Transform.rotate(
angle: math.pi / 180 * 90,
child: Container(
height: 200,
width: 300,
color: Colors.black,
child: Stack(
children: <Widget>[
Positioned(
top: 0,
left: 0,
child: GestureDetector(
onPanUpdate: (details) {
var dx = details.delta.dx;
var dy = details.delta.dy;
setState(() {
_y += dy;
_x += dx;
});
},
child: Image.network("https://via.placeholder.com/300x200"),
),
),
],
),
),
),
),
],
);
}
}
Basically, the functionality is make the image draggable. But when I rotate it and try to move the image, the coordinates get messed up. It doesn't move in right direction. It works perfectly when the angle is 0. Any help would be appreciated.
Flutter coordinate structure works slightly differently. I could not find a clear url for this, but in your case the values change as follows. You can try the code sample. To change the angle, you can assign another angle in the "map" into "selectedAngle".
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:math' as math;
void main() {
debugPaintSizeEnabled = true;
return runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
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;
Map<int, double> map = {
0: 0.0, // Same 360 angle
90: math.pi / 180 * 90,
180: math.pi / 180 * 180,
270: math.pi / 180 * 270,
};
int selectedAngle = 270;
#override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned(
top: _y,
left: _x,
child: Transform.rotate(
angle: map[selectedAngle],
child: Container(
height: 200,
width: 300,
color: Colors.black,
child: Stack(
children: <Widget>[
Positioned(
top: 0,
left: 0,
child: GestureDetector(
onPanUpdate: (details) {
var dx = details.delta.dx;
var dy = details.delta.dy;
var result = calculate(dx, dy);
setState(() {
_y += result['y'];
_x += result['x'];
});
},
child: Image.network("https://via.placeholder.com/300x200"),
),
),
],
),
),
),
),
],
);
}
Map<String, double> calculate(double x, double y) {
switch(selectedAngle) {
case(0):
return {'x': x, 'y': y};
break;
case(90):
return {'x': -y, 'y': x};
break;
case(180):
return {'x': -x, 'y': -y};
break;
case(270):
return {'x': y, 'y': -x};
break;
default:
return {'x': x, 'y': y};
break;
}
}
}
You can copy paste run full code below
You can move down Transform.rotate to wrap Image.network
see working demo below
code snippet
Transform.rotate(
angle: math.pi / 180 * 90,
child: Image.network(
"https://via.placeholder.com/300x200")),
),
working demo
full code
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:math' as math;
void main() {
debugPaintSizeEnabled = true;
return runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
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;
#override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned(
top: _y,
left: _x,
child: Container(
height: 200,
width: 300,
color: Colors.black,
child: Stack(
children: <Widget>[
Positioned(
top: 0,
left: 0,
child: GestureDetector(
onPanUpdate: (details) {
var dx = details.delta.dx;
var dy = details.delta.dy;
setState(() {
_y += dy;
_x += dx;
});
},
child: Transform.rotate(
angle: math.pi / 180 * 90,
child: Image.network(
"https://via.placeholder.com/300x200")),
),
),
],
),
),
),
],
);
}
}

After Rotate Widget, GestureDetector not working on Stack

The widget cannot be touched or changed the position of the widget on the purple mark when I change the rotation of the widget.
and will be touched successfully when the rotation returns to 0 degrees or before change rotation.
working fine when not rotating
this is my code
import 'package:flutter/material.dart';
import 'dart:math' as math;
class View_Test_Design extends StatefulWidget {
#override
_View_Test_DesignState createState() => _View_Test_DesignState();
}
class _View_Test_DesignState extends State<View_Test_Design> {
double x = 100;
double y = 100;
double w = 200;
double h = 50;
double r=0; // Not Working fine when change to another number
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Rotate|Pos|Drag"),
),
body: Stack(
children: <Widget>[
Positioned(
left: x,
top: y,
child: GestureDetector(
onPanUpdate: (details) {
setState(() {
x = x + details.delta.dx;
y = y + details.delta.dy;
});
},
child: Transform.rotate(
angle: math.pi * r / 180,
child:
Container(width: w, height: h, color: Colors.red))))
],
),
);
}
}
any solution why this is not working?
This is because the Transform widget translates a widget outside the bounds of a parent widget. So you need to make the parent widget bigger.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Rotate|Pos|Drag"),
),
body: Stack(
overflow: Overflow.visible,
children: <Widget>[
Positioned(
left: x,
top: y,
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: GestureDetector(
onPanUpdate: (details) {
setState(() {
x = x + details.delta.dx;
y = y + details.delta.dy;
});
},
child: Transform.rotate(
angle: math.pi * r / 180,
child:
Container(width: w, height: h, color: Colors.red))))
],
),
);
}
Answer is from here https://github.com/flutter/flutter/issues/27587
Had a similar problem, this is my solution.
https://stackoverflow.com/a/74165192/12098106
import 'package:flutter/material.dart';
// -------------------------------------------------------------------
// THE ITEM TO BE DRAWN
// -------------------------------------------------------------------
class DrawContainer {
Color color;
Offset offset;
double width;
double height;
double scale;
double angle;
late double _baseScaleFactor;
late double _baseAngleFactor;
DrawContainer(this.color, this.offset, this.width, this.height, this.scale,
this.angle) {
onScaleStart();
}
onScaleStart() {
_baseScaleFactor = scale;
_baseAngleFactor = angle;
}
onScaleUpdate(double scaleNew) =>
scale = (_baseScaleFactor * scaleNew).clamp(0.5, 5);
onRotateUpdate(double angleNew) => angle = _baseAngleFactor + angleNew;
}
// -------------------------------------------------------------------
// APP
// -------------------------------------------------------------------
void main() {
runApp(const MaterialApp(home: GestureTest()));
}
class GestureTest extends StatefulWidget {
const GestureTest({Key? key}) : super(key: key);
#override
// ignore: library_private_types_in_public_api
_GestureTestState createState() => _GestureTestState();
}
// -------------------------------------------------------------------
// APP STATE
// -------------------------------------------------------------------
class _GestureTestState extends State<GestureTest> {
final List<DrawContainer> containers = [
DrawContainer(Colors.red, const Offset(50, 50), 100, 100, 1.0, 0.0),
DrawContainer(Colors.yellow, const Offset(100, 100), 200, 100, 1.0, 0.0),
DrawContainer(Colors.green, const Offset(150, 150), 50, 100, 1.0, 0.0),
];
void onGestureStart(DrawContainer e) => e.onScaleStart();
onGestureUpdate(DrawContainer e, ScaleUpdateDetails d) {
e.offset = e.offset + d.focalPointDelta;
if (d.rotation != 0.0) e.onRotateUpdate(d.rotation);
if (d.scale != 1.0) e.onScaleUpdate(d.scale);
setState(() {}); // redraw
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: SizedBox(
height: double.infinity,
width: double.infinity,
child: Stack(
children: [
...containers.map((e) {
return GestureDetector(
onScaleStart: (details) {
// detect two fingers to reset internal factors
if (details.pointerCount == 2) {
onGestureStart(e);
}
},
onScaleUpdate: (details) => onGestureUpdate(e, details),
child: DrawWidget(e));
}).toList(),
],
),
),
));
}
}
// -------------------------------------------------------------------
// POSITION, ROTATE AND SCALE THE WIDGET
// -------------------------------------------------------------------
class DrawWidget extends StatelessWidget {
final DrawContainer e;
const DrawWidget(this.e, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned(
left: e.offset.dx,
top: e.offset.dy,
child: Transform.rotate(
angle: e.angle,
child: Transform.scale(
scale: e.scale,
child: Container(
height: e.width,
width: e.height,
color: e.color,
),
),
),
),
],
);
}
}
Use DeferredPointer widget to detect gestures outside the bounds of a parent.

Flutter: How to move, rotate and zoom the container?

I want to make a container which can be dragged around, zoom and rotate. I am able to achieve a zoom. Below is my code:
//variable declaration
double _scale = 1.0;
double _previousScale;
var yOffset = 400.0;
var xOffset = 50.0;
var rotation = 0.0;
var lastRotation = 0.0;
//build method
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Center(
child: GestureDetector(
onScaleStart: (scaleDetails) {
_previousScale = _scale;
print(' scaleStarts = ${scaleDetails.focalPoint}');
},
onScaleUpdate: (scaleUpdates){
//ScaleUpdateDetails
rotation += lastRotation - scaleUpdates.rotation;
lastRotation = scaleUpdates.rotation;
print("lastRotation = $lastRotation");
print(' scaleUpdates = ${scaleUpdates.scale} rotation = ${scaleUpdates.rotation}');
setState(() => _scale = _previousScale * scaleUpdates.scale);
},
onScaleEnd: (scaleEndDetails) {
_previousScale = null;
print(' scaleEnds = ${scaleEndDetails.velocity}');
},
child:
Transform(
transform: Matrix4.diagonal3( Vector3(_scale, _scale, _scale))..rotateZ(rotation * math.pi/180.0),
alignment: FractionalOffset.center,
child: Container(
height: 200.0,
width: 200.0,
color: Colors.red,
),
)
,
),
),
),
);
}
Currently, there is no rotation and I can't move the container around.
use Matrix Gesture Detector package1 package, here you have the basic sample:
MatrixGestureDetector(
onMatrixUpdate: (m, tm, sm, rm) {
setState(() {
matrix = n;
});
},
child: Transform(
transform: matrix,
child: ....
),
),
for more sample code refer to example folder that contains 6 demos
You can use RotatedBox widget (for rotation) along with InteractiveViewer widget(for zoom in and zoom out).
panEnabled property in InteractiveViewer widget used for moving the container
Scaffold(
backgroundColor: Colors.black,
body: Center(
child: RotatedBox(
quarterTurns: 1,
child: InteractiveViewer(
boundaryMargin: EdgeInsets.zero,
minScale: 1,
maxScale: 4,
child: Container(
height: 200,
width: 200,
color: Colors.blue,
),
),
),
),
),
You can also use photo_viewer instead of matrix_gesture_detector. More info in this answer: https://stackoverflow.com/a/62426232/2942294
i had same issue. the problem is solved by Matrix gesture detetctor package. but i have solved this by another case:- the whole code is here.
// #dart=2.9
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class ContainerList {
double height;
double width;
double scale;
double rotation;
double xPosition;
double yPosition;
ContainerList({
this.height,
this.rotation,
this.scale,
this.width,
this.xPosition,
this.yPosition,
});
}
class HomePage extends StatefulWidget {
const HomePage({Key key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<ContainerList> list = [];
Offset _initPos;
Offset _currentPos = Offset(0, 0);
double _currentScale;
double _currentRotation;
Size screen;
#override
void initState() {
screen = Size(400, 500);
list.add(ContainerList(
height: 200.0,
width: 200.0,
rotation: 0.0,
scale: 1.0,
xPosition: 0.1,
yPosition: 0.1,
));
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
height: 500.0,
color: Colors.blue.withOpacity(0.8),
width: double.infinity,
child: Stack(
children: list.map((value) {
return GestureDetector(
onScaleStart: (details) {
if (value == null) return;
_initPos = details.focalPoint;
_currentPos = Offset(value.xPosition, value.yPosition);
_currentScale = value.scale;
_currentRotation = value.rotation;
},
onScaleUpdate: (details) {
if (value == null) return;
final delta = details.focalPoint - _initPos;
final left = (delta.dx / screen.width) + _currentPos.dx;
final top = (delta.dy / screen.height) + _currentPos.dy;
setState(() {
value.xPosition = Offset(left, top).dx;
value.yPosition = Offset(left, top).dy;
value.rotation = details.rotation + _currentRotation;
value.scale = details.scale * _currentScale;
});
},
child: Stack(
children: [
Positioned(
left: value.xPosition * screen.width,
top: value.yPosition * screen.height,
child: Transform.scale(
scale: value.scale,
child: Transform.rotate(
angle: value.rotation,
child: Container(
height: value.height,
width: value.width,
child: FittedBox(
fit: BoxFit.fill,
child: Listener(
onPointerDown: (details) {
// if (_inAction) return;
// _inAction = true;
// _activeItem = val;
_initPos = details.position;
_currentPos =
Offset(value.xPosition, value.yPosition);
_currentScale = value.scale;
_currentRotation = value.rotation;
},
onPointerUp: (details) {
// _inAction = false;
},
child: Container(
height: value.height,
width: value.width,
color: Colors.red,
),
// child: Image.network(value.name),
),
),
),
),
),
)
],
),
);
}).toList(),
),
),
);
}
}
so now i can khow the rotation angle value and scale value also. in matrix gesture detector package you can not khow about this values.
Had the same problem, here is my solution:
https://stackoverflow.com/a/74165192/12098106
import 'package:flutter/material.dart';
// -------------------------------------------------------------------
// THE ITEM TO BE DRAWN
// -------------------------------------------------------------------
class DrawContainer {
Color color;
Offset offset;
double width;
double height;
double scale;
double angle;
late double _baseScaleFactor;
late double _baseAngleFactor;
DrawContainer(this.color, this.offset, this.width, this.height, this.scale,
this.angle) {
onScaleStart();
}
onScaleStart() {
_baseScaleFactor = scale;
_baseAngleFactor = angle;
}
onScaleUpdate(double scaleNew) =>
scale = (_baseScaleFactor * scaleNew).clamp(0.5, 5);
onRotateUpdate(double angleNew) => angle = _baseAngleFactor + angleNew;
}
// -------------------------------------------------------------------
// APP
// -------------------------------------------------------------------
void main() {
runApp(const MaterialApp(home: GestureTest()));
}
class GestureTest extends StatefulWidget {
const GestureTest({Key? key}) : super(key: key);
#override
// ignore: library_private_types_in_public_api
_GestureTestState createState() => _GestureTestState();
}
// -------------------------------------------------------------------
// APP STATE
// -------------------------------------------------------------------
class _GestureTestState extends State<GestureTest> {
final List<DrawContainer> containers = [
DrawContainer(Colors.red, const Offset(50, 50), 100, 100, 1.0, 0.0),
DrawContainer(Colors.yellow, const Offset(100, 100), 200, 100, 1.0, 0.0),
DrawContainer(Colors.green, const Offset(150, 150), 50, 100, 1.0, 0.0),
];
void onGestureStart(DrawContainer e) => e.onScaleStart();
onGestureUpdate(DrawContainer e, ScaleUpdateDetails d) {
e.offset = e.offset + d.focalPointDelta;
if (d.rotation != 0.0) e.onRotateUpdate(d.rotation);
if (d.scale != 1.0) e.onScaleUpdate(d.scale);
setState(() {}); // redraw
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: SizedBox(
height: double.infinity,
width: double.infinity,
child: Stack(
children: [
...containers.map((e) {
return GestureDetector(
onScaleStart: (details) {
// detect two fingers to reset internal factors
if (details.pointerCount == 2) {
onGestureStart(e);
}
},
onScaleUpdate: (details) => onGestureUpdate(e, details),
child: DrawWidget(e));
}).toList(),
],
),
),
));
}
}
// -------------------------------------------------------------------
// POSITION, ROTATE AND SCALE THE WIDGET
// -------------------------------------------------------------------
class DrawWidget extends StatelessWidget {
final DrawContainer e;
const DrawWidget(this.e, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned(
left: e.offset.dx,
top: e.offset.dy,
child: Transform.rotate(
angle: e.angle,
child: Transform.scale(
scale: e.scale,
child: Container(
height: e.width,
width: e.height,
color: e.color,
),
),
),
),
],
);
}
}