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.
Related
I'm facing a problem implementing drag and drop in my project. I want dynamically add and delete draggable elements. The problem is that I can't understand how to get the reference to the widget I'm currently moving so that I can understand which coordinates in the list I have to change.
Here is an example of the code where I use the static number of draggable widgets. They are assigned with coordinates from the list. But what if I have to dynamically add and delete those draggable widgets how can I understand which coordinates to change?
So, I have an array of draggble elements and array of coordinates. Widget with index 0 refers to coordinates with index 0. How can I understand which widget I'm currently moving so that I can get the index of it in array of widgets and then change proper coordinates in array of coordinates.
class DragAndDrop extends StatefulWidget {
const DragAndDrop({
Key? key,
this.width,
this.height,
}) : super(key: key);
final double? width;
final double? height;
#override
_DragAndDropState createState() => _DragAndDropState();
}
class _DragAndDropState extends State<DragAndDrop> {
List<double?> _x = [0.0, 20.0];
List<double?> _y = [0.0, 20.0];
int k = -1;
List<Widget> pel = [];
final GlobalKey stackKey = GlobalKey();
#override
Widget build(BuildContext context) {
pel.add(Container(color: Colors.blue));
k++;
Widget drag = Draggable<int>(
data: k,
child: Icon(
Icons.keyboard_arrow_down,
color: Color(0x95000000),
size: 40,
),
feedback: Icon(
Icons.keyboard_arrow_down,
color: Color.fromRGBO(212, 14, 14, 0.584),
size: 40,
),
childWhenDragging: Container(),
onDragStarted: () {},
onDragEnd: (dragDetails) {
setState(() {
final parentPos = stackKey.globalPaintBounds;
if (parentPos == null) return;
if (dragDetails.offset.dx - parentPos.left < 0)
_x[0] = 0;
else if (dragDetails.offset.dx - parentPos.left >
double.parse(widget.width.toString()) - 40)
_x[0] = double.parse(widget.width.toString()) - 40;
else
_x[0] = dragDetails.offset.dx - parentPos.left;
if (dragDetails.offset.dy - parentPos.top < 0)
_y[0] = 0;
else if (dragDetails.offset.dy - parentPos.top >
double.parse(widget.height.toString()) - 40)
_y[0] = double.parse(widget.height.toString()) - 40;
else
_y[0] = dragDetails.offset.dy - parentPos.top;
});
},
);
pel.add(Positioned(
left: _x[0],
top: _y[0],
child: drag,
));
pel.add(Positioned(
left: _x[1],
top: _y[1],
child: Draggable<int>(
data: k,
child: Icon(
Icons.keyboard_arrow_down,
color: Color(0x95000000),
size: 40,
),
feedback: Icon(
Icons.keyboard_arrow_down,
color: Color.fromRGBO(212, 14, 14, 0.584),
size: 40,
),
childWhenDragging: Container(),
onDragStarted: () {},
onDragEnd: (dragDetails) {
setState(() {
final parentPos = stackKey.globalPaintBounds;
if (parentPos == null) return;
_x[1] = dragDetails.offset.dx - parentPos.left; // 11.
_y[1] = dragDetails.offset.dy - parentPos.top;
});
},
),
));
return Stack(
key: stackKey,
fit: StackFit.expand,
children: pel,
);
}
}
extension GlobalKeyExtension on GlobalKey {
Rect? get globalPaintBounds {
final renderObject = currentContext?.findRenderObject();
var translation = renderObject?.getTransformTo(null).getTranslation();
if (translation != null && renderObject?.paintBounds != null) {
return renderObject!.paintBounds
.shift(Offset(translation.x, translation.y));
} else {
return null;
}
}
}
I tried to use a variable that I can assign to Dragble.data field but I'm not able to get it inside the widget.
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,
),
),
),
)
I am using Transform.translate to animate a Container as much as my finger move around on screen and I am using Listener widget to increase my offset. but I noticed that if I keep move my container outside the screen it also will keep go through outside the screen.
I also wrapped it into safe area but it also keeps going outside the border.
How can I prevent this behavior?
import 'package:flutter/material.dart';
class Test extends StatefulWidget {
const Test({Key? key}) : super(key: key);
#override
State<Test> createState() => _TestState();
}
class _TestState extends State<Test> {
late Offset offsetLocal= const Offset(0,0);
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Listener(
onPointerMove:(t){
offsetLocal+=t.delta;
setState(() {});
},
child: Center(
child: Transform.translate(
offset: offsetLocal,
child: Container(
color: Colors.red,
width: 200,
height: 200,
)
),
),
),
),
);
}
}
The update position using Transform.translate will be based on user tap location.
///Tap position needed to be centered
updatePositionOnTransform(BoxConstraints constraints, PointerMoveEvent t) {
debugPrint(t.position.toString());
//* using single condition force to one dimension
// if (t.position.dx < constraints.maxWidth - boxSize.width / 2 &&
// t.position.dx > boxSize.width / 2 &&
// t.localPosition.dy > boxSize.height / 2 &&
// t.localPosition.dy < constraints.maxHeight - boxSize.height / 2) {
// offsetLocal += t.delta;
// setState(() {});
// }
double dx = 0;
double dy = 0;
if (t.position.dx < constraints.maxWidth - boxSize.width / 2 &&
t.position.dx > boxSize.width / 2) {
dx = t.delta.dx;
}
if (t.localPosition.dy > boxSize.height / 2 &&
t.localPosition.dy < constraints.maxHeight - boxSize.height / 2) {
dy = t.delta.dy;
}
offsetLocal += Offset(dx, dy);
setState(() {});
}
using Stack user can tap anywhere
updatePosition(BoxConstraints constraints, offset) {
double dx = offset.dx;
double dy = offset.dy;
//* -100 is coming from half container size
if (dx < constraints.maxWidth - boxSize.width / 2 &&
dx > boxSize.width / 2) {
offsetLocal = Offset(dx - (boxSize.width / 2), offsetLocal.dy);
}
if (dy > boxSize.height / 2 &&
dy < constraints.maxHeight - boxSize.height / 2) {
offsetLocal = Offset(offsetLocal.dx, dy - (boxSize.height / 2));
}
setState(() {});
}
You can follow the example and select the one you prefer.
class _TestState extends State<Test> {
late Offset offsetLocal = const Offset(0, 0);
updatePosition(BoxConstraints constraints, offset) {
double dx = offset.dx;
double dy = offset.dy;
//* -100 is coming from half container size
if (dx < constraints.maxWidth - boxSize.width / 2 &&
dx > boxSize.width / 2) {
offsetLocal = Offset(dx - (boxSize.width / 2), offsetLocal.dy);
}
if (dy > boxSize.height / 2 &&
dy < constraints.maxHeight - boxSize.height / 2) {
offsetLocal = Offset(offsetLocal.dx, dy - (boxSize.height / 2));
}
setState(() {});
}
///Tap position needed to be centered
updatePositionOnTransform(BoxConstraints constraints, PointerMoveEvent t) {
debugPrint(t.position.toString());
//* using single condition force to one dimension
// if (t.position.dx < constraints.maxWidth - boxSize.width / 2 &&
// t.position.dx > boxSize.width / 2 &&
// t.localPosition.dy > boxSize.height / 2 &&
// t.localPosition.dy < constraints.maxHeight - boxSize.height / 2) {
// offsetLocal += t.delta;
// setState(() {});
// }
double dx = 0;
double dy = 0;
if (t.position.dx < constraints.maxWidth - boxSize.width / 2 &&
t.position.dx > boxSize.width / 2) {
dx = t.delta.dx;
}
if (t.localPosition.dy > boxSize.height / 2 &&
t.localPosition.dy < constraints.maxHeight - boxSize.height / 2) {
dy = t.delta.dy;
}
offsetLocal += Offset(dx, dy);
setState(() {});
}
final Size boxSize = const Size(200, 200);
#override
Widget build(BuildContext context) {
return Scaffold(
body: LayoutBuilder(
builder: (context, constraints) => SafeArea(
// child: usingTransform(constraints),
child: usingStack(constraints),
),
),
);
}
Listener usingTransform(constraints) {
return Listener(
onPointerMove: (t) {
updatePositionOnTransform(constraints, t);
},
child: Center(
child: Transform.translate(
offset: offsetLocal,
child: Container(
color: Colors.red,
width: 200,
height: 200,
)),
),
);
}
GestureDetector usingStack(BoxConstraints constraints) {
return GestureDetector(
onPanUpdate: (details) {
updatePosition(constraints, details.localPosition);
},
child: Stack(
children: [
Positioned(
// duration: const Duration(milliseconds: 100), //you can use `AnimatedPositioned`
left: offsetLocal.dx,
top: offsetLocal.dy,
child: Container(
color: Colors.red,
width: boxSize.width,
height: boxSize.height,
),
),
],
),
);
}
}
I have a word search app in which I use Rect to paint over a grid.
I have a gesture detector that allows me to identify which grid fields have been selected, through the global key and using onPanStart, onPanUpdate and onPanEnd I can see all the words that have been marked.
Everything works fine though, I don't know how to add a diagonal marker.
I currently get this result:
I would like to implement this diagonal painting
Demonstration of how the marking is currently, ps: The Gesture Detector still needs to be improved for painting, diagonally
I tried to add the RotationTransition, but the behavior didn't look good, below.
RotationTransition(
alignment: Alignment.centerLeft,
turns: new AlwaysStoppedAnimation(45 / 360),
child: Container(
padding: const EdgeInsets.all(2.0),
child: DecoratedBox(
decoration: BoxDecoration(
color: colorSelection!.withOpacity(0.4),
border: Border.all(
color: colorSelection!.withOpacity(0.1),
//width: width,
),
borderRadius: BorderRadius.circular(radius),
),
),
),
)
An overview of the important parts
Widget build(BuildContext context) {
return Stack(
children: [
GridView.count(
physics: NeverScrollableScrollPhysics(),
childAspectRatio: letterWidth / letterHeight,
crossAxisCount: nCols,
children: puzzle.asMap().map(
(index, letter) => MapEntry(
index,
BoardLetter(
letter,
isSelected: selection.contains(index),
isHit: hitIndexes.contains(index),
key: uniqueLetters[index]['key'],
),
),
)
.values
.toList(),
),
...markers,
Positioned(
width: widget.width,
height: widget.height,
child: RotationTransition(
turns: new AlwaysStoppedAnimation(360 / 360),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onPanStart: onPanStart,
onPanEnd: onPanEnd,
onPanUpdate: onPanUpdate,
),
),
),
],
);
Selection checks words and markers.last paints areas.
void onPanUpdate(DragUpdateDetails details) {
final currentIndex = computeLetterIndex(details.localPosition);
if(currentIndex >= 0 && currentIndex <= 99){
final key = uniqueLetters[currentIndex]['key'];
final RenderBox renderBox = key.currentContext.findRenderObject();
print("render box size ${renderBox.localToGlobal(Offset.zero, ancestor: context.findRenderObject())}");
markerRect = renderBox.localToGlobal(Offset.zero, ancestor: context.findRenderObject()) & renderBox.size;
List<int> _selection = [];
if (checkSameRow(startIndex, currentIndex)) {
if(direction == "horizontal"){
markers.last = adjustedMarker(markers.last, markerRect!);
_selection = genSelection(startIndex, currentIndex, 1);
}
} else if (checkSameCol(startIndex, currentIndex)) {
//print("direction $direction");
if(direction == "vertical"){
markers.last = adjustedMarker(markers.last, markerRect!);
_selection = genSelection(startIndex, currentIndex, nCols);
}
} else if (checkSameMainDiagonal(startIndex, currentIndex)) {
markers.last = adjustedMarker(markers.last, markerRect!);
_selection = genSelection(startIndex, currentIndex, nCols + 1);
} else if (checkSameCounterDiagonal(startIndex, currentIndex)) {
markers.last = adjustedMarker(markers.last, markerRect!);
_selection = genSelection(startIndex, currentIndex, nCols - 1);
}
setState(() {
selection = _selection;
});
}
}
It is checked if a word was found, otherwise it removes the paint(markers.last)
void onPanEnd(DragEndDetails details) {
final word = selection
.map((index) => puzzle[index])
.fold("", (dynamic value, letter) => value + letter);
// Check if this is a valid word
var reversedWord = word.split('').reversed.join();
var wordIndex = widget.words
.indexWhere((gameWord) => gameWord == word || gameWord == reversedWord);
if (wordIndex != -1) {
print("word $word/$reversedWord was hit");
widget.onHitWord(word, wordIndex);
this.setState(() {
direction = "";
colorNumber = colorNumber + 1 ;
hitIndexes = List.from(hitIndexes)..addAll(selection);
});
}else{
setState(() {
direction = "";
markers.removeLast();
selection = [];
});
}
}
The initial position is captured and marked.
void onPanStart(DragStartDetails details) {
startIndex = computeLetterIndex(details.localPosition);
final currentIndex = computeLetterIndex(details.localPosition);
final key = uniqueLetters[currentIndex]['key'];
final renderBox = key.currentContext.findRenderObject();
print(uniqueLetters[currentIndex]['letter']);
setState(() {
markerRect = renderBox.localToGlobal(Offset.zero, ancestor: context.findRenderObject()) & renderBox.size;
addMarker(markerRect, currentIndex);
});
}
The bookmark is added
void addMarker(Rect rect, int startIndex) {
markers.add(
WordMarker(
rect: rect,
startIndex: startIndex,
colorSelection: getColor(context),
color: getColor(context),
));
}
Markers are adjusted with rect
WordMarker adjustedMarker(WordMarker originalMarker, Rect endRect) {
originalMarker.colorSelection = getColor(context);
originalMarker.copyWith(rect: originalMarker.rect!.inflate(20.0));
return originalMarker.copyWith(rect: originalMarker.rect!.expandToInclude(endRect));
}
I am trying to draw over image on custompainter. I am using the example on flutter custompainter video and here is what I have so far. I can draw in the image but I cannot scale image. How do I scale image on gesture and draw in image? I would prefer not to use any package.
Container(
height: double.infinity,
width: double.infinity,
color: Colors.black87,
child: FittedBox(
child: GestureDetector(
onScaleStart: _scaleStartGesture,
onScaleUpdate: _scaleUpdateGesture,
onScaleEnd: (_) => _scaleEndGesture(),
child: SizedBox(
height: _image.height.toDouble(),
width: _image.width.toDouble(),
child: CustomPaint(
willChange: true,
painter: ImagePainter(
image: _image,
points: points
),
),
),
),
),
),
Merge LongPressDraggable or Draggable and GestureDetector's onScaleUpdate;
onScaleUpdate: (s) {
if (!(s.scale == 1 && s.rotation == 0)) {
controller
..setImageRotate(s.rotation)
..setImageScale(s.scale)
..setImageOffset(s.focalPoint);
setState(() {
message = controller.selectedController.toString();
});
}
},
Controller Class ;
final StreamController<ImageController> _controllerStreamController =
StreamController<ImageController>.broadcast();
Stream<ImageController> get controllerTypeStream =>
_controllerStreamController.stream;
double rotateSync;
void setImageRotate(double rotate) {
if (selectedController == null) {
rotateSync = rotate;
_controllerStreamController.sink.add(this);
}
}
Offset offset;
void setImageOffset(Offset rotate) {
if (selectedController == null) {
offset = rotate;
_controllerStreamController.sink.add(this);
}
}
double scaleSync;
void setImageScale(double scale) {
if (selectedController == null) {
scaleSync = scale;
_controllerStreamController.sink.add(this);
}
}
And than set image widget in 'Stack' widget ;
Stack -> GestureDetector -> Draggable -> Transform.scale -> Transform.translate -> Tranform.rotate -> SizedBox(ImageWidget)