How to dynamically draw rectangles on an image in Flutter? - 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,
)
]),
);
}

Related

GestureDetector tab does not work after rotation in 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,
),
),
),
)

Flutter: How to rotate diagonally a rect within a plane to paint

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));
}

Flutter: Zoom and pan a widget

I'm fighting with zoom/pan gestures.
I've found a partially suitable example in this book:
https://books.google.bg/books?id=ex-tDwAAQBAJ&pg=PA284&lpg=PA284&dq=flutter+_startLastOffset&source=bl&ots=YUQna09jIf&sig=ACfU3U0QrHwl2RdrVUv5EtpHaHFKx_cXhA&hl=en&sa=X&ved=2ahUKEwid9uPJ8abnAhVnlosKHTKQBn4Q6AEwAHoECAMQAQ#v=onepage&q=flutter%20_startLastOffset&f=false
which, I guess was based on this:
https://chromium.googlesource.com/external/github.com/flutter/flutter/+/refs/heads/dev/examples/layers/widgets/gestures.dart
And my code based on it:
Offset _startLastOffset = Offset.zero;
Offset _lastOffset = Offset.zero;
Offset _currentOffset = Offset.zero;
double _lastScale = 1.0;
double _currentScale = 1.0;
void _onScaleStart(ScaleStartDetails details) {
_startLastOffset = details.focalPoint;
_lastOffset = _currentOffset;
_lastScale = _currentScale;
}
void _onScaleUpdate(ScaleUpdateDetails details) {
if (details.scale != 1.0) {
// Scaling
double currentScale = _lastScale * details.scale;
if (currentScale < 0.5) {
currentScale = 0.5;
}
_currentScale = currentScale;
_bloc.setScale(_currentScale);
} else if (details.scale == 1.0) {
// We are not scaling but dragging around screen
// Calculate offset depending on current Image scaling.
Offset offsetAdjustedForScale = (_startLastOffset - _lastOffset) / _lastScale;
Offset currentOffset = details.focalPoint - (offsetAdjustedForScale * _currentScale);
_currentOffset = currentOffset;
_bloc.setOffset(_currentOffset);
}
}
---------------
child: StreamBuilder<Object>(
stream: _bloc.scale,
builder: (context, snapshot1) {
double _cscale = snapshot1.hasData? snapshot1.data : _currentScale;
return StreamBuilder<Object>(
stream: _bloc.offset,
builder: (context, snapshot2) {
Offset _coffset = snapshot2.hasData? snapshot2.data : _currentOffset;
return Transform(
transform: Matrix4.identity()
..scale(_cscale, _cscale)
..translate(_coffset.dx, _coffset.dy),
alignment: FractionalOffset.center,
child: Stack(
key: _imgStack,
fit: StackFit.expand,
children: <Widget>[
Image(image: AssetImage('assets/images/image.png')),
SvgPicture.asset(svgFile)
],
),
);
}
);
}
),
The Pan of which, however, behaves unnatural when the widget is zoomed - the widget moves much further than the finger, in the moving direction.
I managed to fix this by changing:
_currentOffset = currentOffset;
_bloc.setOffset(_currentOffset);
to:
_bloc.setOffset(currentOffset/_currentScale);
_currentOffset = currentOffset;
Which works exactly as expected, but just until the Second zoom.
After the second time I zoom, the widget get shifted on first touch.
On zoom-in it shifts to the right, on zoom-out it shifts to the left.
Any ideas?
Use InteractiveViewer widget. Here is an example.
#override
Widget build(BuildContext context) {
return Center(
child: InteractiveViewer(
boundaryMargin: EdgeInsets.all(100),
minScale: 0.1,
maxScale: 1.5,
child: Container(width: 200, height: 200, color: Colors.blue),
),
);
}

Flutter : Change size of stack when children overflow

I have a draggable widget (from https://medium.com/flutter-community/create-a-draggable-widget-in-flutter-50b61f12635d ) with a stack in a container (red color) consists of moveable children. Here is the widget tree:
I wanted to add a Gesture Transformations as FormBuilder ( https://github.com/flutter/flutter/blob/master/examples/flutter_gallery/lib/demo/transformations/transformations_demo.dart ) to transform the matrix, as you can see in the GIF, mainly zoom in/out and transform x/y.
class _HomeViewState extends State<HomeView> {
final _stackKey;
_HomeViewState(this._stackKey);
List<Widget> movableItems = [];
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text('SynApp'),
actions: <Widget>[
IconButton(
onPressed: () {
x = 200.0;
y = 200.0;
setState(() {
movableItems.add(
MoveableStackItem(_stackKey),
);
});
},
icon: Icon(Icons.add),
),
],
),
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
// Draw the scene as big as is available, but allow the user to
// translate beyond that to a visibleSize that's a bit bigger.
final Size size = Size(constraints.maxWidth, constraints.maxHeight);
final Size visibleSize = Size(size.width * 3, size.height * 2);
return GestureTransformable(
reset: _reset,
onResetEnd: () {
setState(() {
_reset = false;
});
},
child: new Container(
color: Colors.red,
child: Stack(
key: _stackKey,
overflow: Overflow.visible,
fit: StackFit.expand,
children: movableItems),
),
boundaryRect: Rect.fromLTWH(
-visibleSize.width / 2,
-visibleSize.height / 2,
visibleSize.width,
visibleSize.height,
),
initialTranslation: Offset(size.width, size.height),
size: size,
);
}),
);
}
}
The problem is:
a) the size of the stack is equal to the initial screen.
b)when I move items out of the screen, gestureDetection stops, the items are no longer moveable.
What I want:
I want to dynamically resize the size of the stack (the red box) depending on where I move the items.
I was able to find the position and size of the stack widget.
Size stackSize;
Offset stackPosition;
_MoveableStackItemState(this._stackKey);
getSizeAndPosition() {
RenderStack _stackStack = _stackKey.currentContext.findRenderObject();
stackSize = _stackStack.size;
stackPosition = _stackStack.localToGlobal(Offset.zero);
print('stackSize $stackSize');
print('stackPosition $stackPosition');
}
But I'm starting to get lost in advanced UI object orientated stateful widget manipulation.
You can wrap the red stack with an AnimatedContainer.
LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
// Draw the scene as big as is available, but allow the user to
// translate beyond that to a visibleSize that's a bit bigger.
final Size size = Size(constraints.maxWidth, constraints.maxHeight);
final Size visibleSize = Size(size.width * 3, size.height * 2);
return GestureTransformable(
reset: _reset,
onResetEnd: () {
setState(() {
_reset = false;
});
},
child: new AnimatedContainer(
color: Colors.red,
duration: Duration(milliseconds: 200),
width: _stackWidth,
height: _stackHeight,
child: Stack(
key: _stackKey,
overflow: Overflow.visible,
fit: StackFit.expand,
children: movableItems),
),
boundaryRect: Rect.fromLTWH(
-visibleSize.width / 2,
-visibleSize.height / 2,
visibleSize.width,
visibleSize.height,
),
initialTranslation: Offset(size.width, size.height),
size: size,
);
}),
Try to listen the following event of the GestureTransformable
onPanUpdate: (DragUpdateDetails details){
var deltaX = details.delta.dx;
var deltaY = details.delta.dy;
}
DragUpdateDetails object let you to know the delta
the amount the pointer has moved in the coordinate space of the event
receiver since the previous update
on the x and y axis.
Inside the "onPanUpdate" you can update the width and the height of the animated container related to the delta of the gesture.
setState((){
_stackHeight = /* ... */
_stackWidth = /* ... */
});

Scrollable CustomMultiChildLayout

I can't get CustomMultiChildLayout to be scrollable.
For now my CustomMultiChildLayout just laying out children in a column. In a future I'll make it more complex, but now I want to make it scrollable. I tried putting it inside ListView, but got RenderIndexedSemantics object was given an infinite size during layout..
I tried putting it inside SingleChildScrollView with IntrinsicHeight but in this case it can't be scrolled and it's height is the same as viewport.
Here is some examples that is not working properly:
return SingleChildScrollView(
child: IntrinsicHeight(
child: CustomMultiChildLayout(
delegate: ViewLayoutBuilder(itemCount: cards.length),
children: _buildChildren(context),
),
),
);
and
return ListView(
children: <Widget>[
CustomMultiChildLayout(
delegate: ViewLayoutBuilder(itemCount: cards.length),
children: _buildChildren(context),
)
],
)
Here is my ViewLayoutBuilder delegate:
class ViewLayoutBuilder extends MultiChildLayoutDelegate {
final int itemCount;
ViewLayoutBuilder({#required this.itemCount});
#override
void performLayout(Size size) {
Offset position = Offset(0,0);
Size firstSize = layoutChild('card_0', BoxConstraints.tightFor(width: size.width));
positionChild('card_0', position);
position = position.translate(0, firstSize.height);
for (int i = 1; i < itemCount; i++) {
final String cardId = 'card_$i';
if (hasChild(cardId)) {
Size newSize = layoutChild('$cardId', BoxConstraints.tightFor(width: size.width));
positionChild('$cardId', position);
position = position.translate(0, newSize.height);
}
}
}
#override
bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) => false;
}
The main error in all my tries is that my CustomMultiChildLayout given an infinity height. In other cases it is cropped to a viewport height and can't be scrolled.