GestureDetector tab does not work after rotation in Flutter - flutter

There is an icon under the image widget and I can rotate the image using GestureDetector and Transform.rotate.
At first, it works well to rotate the image by dragging the icon.
However, tapping the icon again after rotating does not work.
Can you tell me the cause and solution of this?
Transform.rotate(
angle: imageModel.radians,
child: Stack(
// image widget
// TODO image rotate icon
// left bottom
Positioned(
left: -5,
bottom: -5,
child: GestureDetector(
onPanStart: (details) {
logger.d("rotate click");
_imageStartPoint = details.globalPosition;
},
onPanUpdate: (details) {
double initX = _imageStartPoint.dx;
double initY = _imageStartPoint.dy;
// moving distance?
var dragX = details.globalPosition.dx - initX;
var dragY = details.globalPosition.dy - initY;
// The direction of the drag and the direction of rotation are opposite
double degrees = -dragX;
double radiams = 0;
// rignt drag
if (dragX < 0) {
radiams = degrees * math.pi / 180;
} else {
radiams = degrees * math.pi / 180;
}
imageModel.radians = radiams;
_imageStreamController.add(imageModel);
},
child: const Icon(
Icons.rotate_right,
size: 30,
color: Colors.red,
),
),
),
)

Related

Flutter positioned widget smooth slide

As the above picture shows i have 4 seprate areas with 4 seperate motion defined.
For example: I want to slide the positioned widget in top left to bottom right diagonal if user begins sliding at the red box. I am able to move and slide the widget the side that i want to slide but the animation of sliding is not smooth.I think that issue happens because of the wrong X and Y values to change position. So here is the code.
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/container.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'dart:math' as math;
class HomePage extends StatefulWidget {
HomePage({super.key});
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
double? dragStartY;
double? dragStartX;
bool dragging = false;
double? top;
double? bottom;
double? left;
double? right;
int? lastAction;
double angle = 0;
void actionDecider(double currentX, double currentY, double changeY,
double changeX, int triggerType) {
//0 DISMISS
//1 LIKE
//2 IMAGE -> 21 FORWARD - 22 BACK
//3 SUPER LIKE
//4 DIRECT MESSAGE
//TRIGGER TYPE -> 0 VERTICAL - 1 HORIZONTAL
if ((dragStartX! >= 0 && dragStartX! <= 150) &&
(dragStartY! >= 0 && dragStartY! <= 75)) {
if ((triggerType == 0) && (currentY > dragStartY! || changeY == 0)) {
print(right);
setState(() {
lastAction = 0;
right = right! - changeY;
angle = angle + math.pi / 600;
bottom = bottom! - changeY;
left = null;
});
} else if ((triggerType == 1) &&
(currentX > dragStartX! || changeX == 0)) {
print(right);
setState(() {
lastAction = 0;
right = right! - changeX;
angle = angle + math.pi / 600;
bottom = bottom! - changeX;
left = null;
});
}
} else if ((dragStartX! > 150 && dragStartX! <= 300) &&
(dragStartY! >= 0 && dragStartY! <= 75)) {
if ((triggerType == 0) && (currentY > dragStartY! || changeY == 0)) {
print("left");
print(left);
print("cx");
print(changeX);
setState(() {
lastAction = 1;
angle = angle - math.pi / 600;
right = null;
left = -changeX;
});
} else if ((triggerType == 1) &&
(currentX < dragStartX! || changeX == 0)) {
print("left");
print(left);
print("cx");
print(changeX);
setState(() {
lastAction = 1;
angle = angle - math.pi / 600;
right = null;
left = -changeX;
});
}
} else if ((dragStartX! >= 0 && dragStartX! <= 300) &&
(dragStartY! > 75 && dragStartY! < 225)) {
if (currentX > dragStartX! || changeX == 0) {
setState(() {
lastAction = 21;
});
} else if (currentX < dragStartX! || changeX == 0) {
setState(() {
lastAction = 22;
});
}
} else if ((dragStartX! >= 0 && dragStartX! <= 300) &&
(dragStartY! > 225 && dragStartY! <= 300)) {
if (dragStartY! > currentY || changeY == 0) {
setState(() {
lastAction = 3;
});
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: double.maxFinite,
height: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 300,
height: 300,
child: Stack(
clipBehavior: Clip.none,
children: [
Positioned(
child: Container(
width: 300,
height: 300,
color: Colors.blue,
)),
Positioned(
right: dragging ? right : 0,
bottom: dragging ? bottom : 0,
child: Transform.rotate(
angle: angle,
child: GestureDetector(
onVerticalDragStart: (details) {
setState(() {
dragging = true;
dragStartX = details.localPosition.dx;
dragStartY = details.localPosition.dy;
});
},
onHorizontalDragStart: (details) {
setState(() {
dragging = true;
dragStartX = details.localPosition.dx;
dragStartY = details.localPosition.dy;
});
},
onVerticalDragUpdate: (details) {
actionDecider(
details.localPosition.dx,
details.localPosition.dy,
details.delta.dy,
details.delta.dx,
0);
},
onHorizontalDragUpdate: (details) {
actionDecider(
details.localPosition.dx,
details.localPosition.dy,
details.delta.dy,
details.delta.dx,
1);
},
onHorizontalDragEnd: (details) {
setState(() {
dragging = false;
dragStartX = null;
dragStartY = null;
angle = 0.0;
right = 0;
bottom = 0;
});
},
onVerticalDragEnd: (details) {
setState(() {
dragging = false;
dragStartX = null;
dragStartY = null;
angle = 0.0;
right = 0;
bottom = 0;
});
},
child: Container(
width: 300,
height: 300,
child: Stack(
children: [
Positioned(
left: 0,
top: 0,
child: Container(
width: 150,
height: 75,
color: Colors.red,
)),
Positioned(
right: 0,
top: 0,
child: Container(
width: 150,
height: 75,
color: Colors.black,
)),
Positioned(
right: 0,
left: 0,
top: 75,
child: Container(
width: 300,
height: 150,
color: Colors.purple,
)),
Positioned(
left: 0,
bottom: 0,
child: Container(
width: 300,
height: 75,
color: Colors.green,
)),
],
),
)),
)),
],
),
)
],
),
),
);
}
}
What should i do for slide these positioned widgets smoothly on dragUpdate events?
The problem is that you don't use any animation in your code. You just change states.
Try to wrap your Stack in AnimatedBuilder or to use AnimatedPositioned instead of Positioned.
Also you can use InteractiveViewer (wrap your Stack in it), which lets you to interact with its child by dragging.
The parent widget in this flutter app is Padding which is employing its only property padding to print an empty space of 300 px on the top of its child widget which is Stack. The alignment is set to center in the Stack widget. Stack, as it does, is taking a list of widgets as children, here it’s taking in two Positioned widgets. The first one is containing a green-colored material design message icon, which is 128 px long in width and height.

Flutter image zoomed but how to change zoom position

Hello i have implemented double tap to zoom on flutter app but when the image is zoomed i can't navigate to other parts of image when it's in zoomed position.
Pinch to zoom working fine,
Double tap to zoom and reset to normal also working fine.
Only issue is not be able to move in image when its in zoomed state.
please help me for that here is my code :
GestureDetector(
onDoubleTapDown: (details) {
tapDownDetails = details;
},
onDoubleTap: () {
final position = tapDownDetails?.localPosition;
final double scale = 4;
final x = -position!.dx * (scale - 1);
final y = -position.dy * (scale - 1);
final zoomed = Matrix4.identity()
..translate(x, y)
..scale(scale);
final end = _controller!.value.isIdentity()
? zoomed
: Matrix4.identity();
_animation = Matrix4Tween(
begin: _controller?.value,
end: end,
).animate(
CurveTween(curve: Curves.easeInOut)
.animate(_animationController!),
// CurvedAnimation(parent: _animationController!, curve: Curves.easeInOut),
);
_animationController?.forward(from: 0);
},
child: InteractiveViewer(
transformationController: _controller,
clipBehavior: Clip.none,
panEnabled: false,
minScale: minScale,
maxScale: maxScale,
onInteractionStart: (details) {
if (details.pointerCount < 2) return;
if (entry == null) {
showOverlay(context);
}
},
onInteractionUpdate: (details) {
if (entry == null) return;
this.scale = details.scale;
entry!.markNeedsBuild();
},
onInteractionEnd: (details) {
if (details.pointerCount != 1) return;
resetAnimation();
},
child: Image(
image: imageProvider,
),
),
),
Remove this line or set to true, which is the default. This will enable panning.
panEnabled: true,

How to get InteractiveViewer image pixel coordinate in flutter?

i want to get pixel coordinate when i tapped the screen, i use interactiveView and GestureDetect in Fultter, i am so confused about the matrix transform , i am new to App develop, please give some advice if you could, very appraciate, Below is my code, which now i can zoom the image, but i can't calculate the coorect pixel coordiante when i click the screen. and since i have no idea how to calculate the ratio between pixel distance<->screen distance, i was stucked there. please help me.
What i am doing is i need pick a pixel position from the image, so i need zoom image first to get precise position,that's why i need ineractiveViewer . and at the same time i need record the gesture behavior, to monitor the behavior, then i wrapper InteractiveView to GestureDetect.
it look like this for now:
enter image description here
Widget mapView() {
double offsetX, offsetY;
return Stack(
children: <Widget>[
Positioned.fill(
child:
GestureDetector(
onTapUp: (TapUpDetails details) {
offsetX = details.localPosition.dx;
offsetY = details.localPosition.dy;
// print(
//"tap local pos, X: ${details.localPosition.dx}, Y: ${details.localPosition.dy}");
// print(
// "tap global pos, X: ${details.globalPosition.dx}, Y: ${details.globalPosition.dy}");
_childWasTappedAt = _transformationController!.toScene(details.localPosition);
// print(
// "child pos to scene , X: ${_childWasTappedAt!.dx}, Y: ${_childWasTappedAt!.dy}");
//double origin_scree_pixel_radio = 17;
MediaQueryData queryData;
queryData = MediaQuery.of(context);
double pixel_ratio = queryData.devicePixelRatio;
double scree_pixel_radio = (1.0 / _cur_scale_value!)*pixel_ratio;
double trans_x = -1.0 * _transformationController!.value.getTranslation().x;
double local_offset_x = offsetX;
double pixel_x = trans_x + local_offset_x * scree_pixel_radio;
print("scale: ${_cur_scale_value}");
print("radio: ${pixel_ratio}");
print("view tran x: ${trans_x}");
print("offset x: ${local_offset_x}");
//print("image_Info: ${_image_info.toString()}");
print("Pixel X: ${pixel_x}");
},
child:
InteractiveViewer(
transformationController: _transformationController,
minScale: 0.001,
maxScale: 200.0,
constrained: false,
child: Image.asset(
imagePath,
filterQuality:FilterQuality.high,
),
onInteractionEnd: (ScaleEndDetails details) {
_cur_scale_value = _transformationController!.value.getMaxScaleOnAxis();
//print("current scale: ${_cur_scale_value}");
},
onInteractionUpdate: (ScaleUpdateDetails details){
//print('onInteractionUpdate----' + details.toString());
},
),
),
),
Positioned(
top: 0.0,//_vehicle_y,
left: 0.0,//_vehicle_x,
child: Icon(Icons.favorite, color: Colors.red,),
),
],
);
}
Use this onInteractionUpdate method to get Coordinates. use also use different methods.
Vist This site for more info:-
https://api.flutter.dev/flutter/widgets/InteractiveViewer/onInteractionUpdate.html
https://api.flutter.dev/flutter/gestures/ScaleStartDetails/localFocalPoint.html
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: InteractiveViewer(
onInteractionUpdate: (v) {
print(v.localFocalPoint.dx);
print(v.localFocalPoint.dy);
},
child: Image.network(
"https://images.unsplash.com/photo-1643832678771-fdd9ed7638ae?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHx0b3BpYy1mZWVkfDd8Ym84alFLVGFFMFl8fGVufDB8fHx8&auto=format&fit=crop&w=2400&q=60",
fit: BoxFit.fitHeight,
),
),
),
);
}

How to dynamically draw rectangles on an image in Flutter?

I want to allow the user draw rectangles on the image he receives in a certain way and get the coordinates of the drawn rectangle. One of the ways I was thinking about is allowing him to tap on the image four times to draw a rectangle from these four coordinates. Currently I'm unable to get the exact local tap position.. it's not always exact.
Any recommendations for my requirements?
This is my current code:
double posx = 100.0;
double posy = 100.0;
void onTapDown(BuildContext context, TapDownDetails details) {
print('${details.globalPosition}');
final RenderBox box = context.findRenderObject();
final Offset localOffset = box.globalToLocal(details.globalPosition);
setState(() {
posx = localOffset.dx;
posy = localOffset.dy;
});
}
Offset _tapPosition;
void _handleTapDown(TapDownDetails details) {
final RenderBox referenceBox = context.findRenderObject();
setState(() {
_tapPosition = referenceBox.globalToLocal(details.globalPosition);
posx = _tapPosition.dx;
posy = _tapPosition.dy;
});
}
#override
Widget build(BuildContext context) {
double height = MediaQuery.of(context).size.height;
double width = MediaQuery.of(context).size.width;
return
GestureDetector(
onTapDown: _handleTapDown,
onTap: (){
},
child:new Stack(fit: StackFit.expand, children: <Widget>[
// Hack to expand stack to fill all the space. There must be a better
// way to do it.
new Container(
color:Colors.white,
child: Image.asset("lib/assets/rectangles.png"),
),
// new Container(height:200,width: 100,color: Colors.white),
new Positioned(
child: new Text('.'),
left: posx,
top: posy,
)
]),
);
}

Stack Alignment for an indeterminate number of children

I am building an app in flutter and part of what I am doing is fetching a list of actions from a server then building buttons for each action. I am trying to display the buttons in a circle and generate a list of raised button widgets. I am then trying to use a Stack to lay out this list in a circle, but cannot figure out how to use positioning to position objects in a circle when I do not know how many objects I will have (I know it will be in a range of about 3-15).
Here is the build method for my stack. Assume all methods and lists and variables are defined correctly and work. I am just concerned about the alignment
#override
Widget build(BuildContext context) {
List<Widget> actions = [];
for(var action in widget.actionsList){
actions.add(RaisedButton(
onPressed: () => _handleAction(action[0]),
shape: CircleBorder(),
child: Text(action[1] + ': ' + action[2]),
));
}
return Scaffold(
body: Center(
child: Stack(
children: actions,
alignment: //HELP,
),
),
);
}
If you have any ideas about how to do the alignment or another way to go about this to make a circle of buttons please let me know. I really want to make a circle, but am not wedded to it impossible (which I doubt) or super convoluted.
Thanks!
Here's how I would do it.
TL;DR;
final actionsList = [
"one",
"two",
"three",
"four",
"five",
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: LayoutBuilder(
builder: (context, constraints) => Stack(
children: layedOutButtons(
centerOfCircle:
Offset(constraints.maxWidth, constraints.maxHeight) / 2,
circleRadius: 100,
),
),
),
);
}
Offset getCoordinateFromAngle({double radius, double angle}) => Offset(
radius * sin(angle),
radius * cos(angle),
);
List<Widget> layedOutButtons({
Offset centerOfCircle,
double circleRadius,
}) {
var buttonRadius = 25.0;
var dispatchAngle = pi * 2 / actionsList.length;
List<Widget> widgets = [];
var i = 0;
for (var action in actionsList) {
var position = getCoordinateFromAngle(
radius: circleRadius,
angle: dispatchAngle * i++,
);
widgets.add(
Positioned(
top: centerOfCircle.dy - position.dy - buttonRadius,
left: centerOfCircle.dx + position.dx - buttonRadius,
width: buttonRadius * 2,
height: buttonRadius * 2,
child: FloatingActionButton(
child: Text(action),
onPressed: () {},
),
),
);
}
return widgets;
}
Explanation
If you want to dispatch a number of widgets on a circle, you have to:
define centerOfCircle, the position of the center of that circle. To do so, I use the LayoutBuilder widget to get the constraints of the layout and determine the center of the Stack => (width / 2, height / 2).
define circleRadius, the radius of that circle (in my example: 100)
Give those data to layedOutButtons that will dispatch widgets on the circle with the Positioned widget.
Widget build(BuildContext context) {
return Scaffold(
body: LayoutBuilder(
builder: (context, constraints) => Stack(
children: layedOutButtons(
centerOfCircle: Offset(constraints.maxWidth, constraints.maxHeight) / 2,
circleRadius: 100,
),
),
),
);
}
List<Widget> layedOutButtons({
Offset centerOfCircle,
double circleRadius,
}) {
List<Widget> widgets = [];
for (var action in actionsList) {
widgets.add(
Positioned(
child: FloatingActionButton(
child: Text(action),
onPressed: () {},
),
),
);
}
return widgets;
}
Now, you need to define how you will dispatch the widgets on the circle, based on their number. e.g, it there's 2 widgets, dispatch them at 180° from each other (meaning one on the top of the circle, one on the bottom. If there's 4, dispatch at 90° from each other, etc. Note that it is expressed in radians (not in degrees).
var dispatchAngle = pi * 2 / actionsList.length;
Then you have to define the coordinates (x, y) of a point on a circle based on an angle (look at this).
Offset getCoordinateFromAngle({double radius, double angle}) => Offset(
radius * sin(angle),
radius * cos(angle),
);
Use that to fill the top and left attributes of the Positioned widget.
List<Widget> layedOutButtons({
Offset centerOfCircle,
double circleRadius,
}) {
var dispatchAngle = pi * 2 / actionsList.length;
List<Widget> widgets = [];
var i = 0;
for (var action in actionsList) {
var position = getCoordinateFromAngle(
radius: circleRadius,
angle: dispatchAngle * i++, //increment angle for each widget
);
widgets.add(
Positioned(
top: centerOfCircle.dy - position.dy, //something's wrong here
left: centerOfCircle.dx + position.dx, //something's wrong here
child: FloatingActionButton(
child: Text(action),
onPressed: () {},
),
),
);
}
return widgets;
}
Now everything is almost ready except that there's a misalignement. It's due to the fact that the widgets are positioned based on their top left corners. We want to refine the positioning so it match the center of our widgets.
var buttonRadius = 25.0;
Positioned(
top: centerOfCircle.dy - position.dy - buttonRadius,
left: centerOfCircle.dx + position.dx - buttonRadius,
width: buttonRadius * 2,
height: buttonRadius * 2,
child: FloatingActionButton(
child: Text(action),
onPressed: () {},
),
),