Flutter image zoomed but how to change zoom position - flutter

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,

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,
),
),
),
)

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

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 - How to scale and draw over image in custompainter?

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)

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