I have the GestureDetector, and I detect only onHorizontalDrag if it started near the edges. How I can pass the gesture to InteractiveViewer if the gesture is not valid for me? if(isHorizontalDragActive == false)
return GestureDetector(
behavior: HitTestBehavior.deferToChild,
onHorizontalDragStart: (DragStartDetails details) {
// allows start only near L/R edges.
final double dX = details.localPosition.dx;
final double dW = constraints.maxWidth / 6;
if (dX < dW || dX > (constraints.maxWidth - dW)) {
isHorizontalDragActive = true;
} else {
isHorizontalDragActive = false;
}
},
onHorizontalDragEnd: (DragEndDetails details) {
if (isHorizontalDragActive == true) {
final double velocity = details.primaryVelocity ?? 0;
if (velocity < 0) {
manager.toForwardPage();
} else if (velocity > 0) {
manager.toBackwardPage();
}
}
isHorizontalDragActive = false;
},
child: InteractiveViewer
Now if I scale InteractiveViewer I can't start moving content inside InteractiveViewer in a horizontal direction.
A GestureDetector wrapping the InteractiveViewer will not respond to GestureDetector events as expected. Try listeners
Listener(
onPointerMove: (moveEvent){
if(moveEvent.delta.dx > 0) {
print("user swiped right");
}
}
child: InteractiveViewer()
.....
)
Related
My app has a draggable floating action button that could move everywhere on the screen and stay at that position when being dropped.
The code for the button is from a blogspot and not mine.
The problem is, since everything is set in portrait mode, and there is no detection when rotation occurs, the button could be unreachable when device rotates from portrait mode to landscape mode (if the button was previously on the bottom half of the screen in portrait mode).
Is there anyway to detect previous orientation in order to set the offset for the button again when it rotates?
Here is the code for the floating button class/widget
import 'package:flutter/material.dart';
class DraggableFloatingActionButton extends StatefulWidget {
final Widget child;
final double deviceWidth;
final double deviceHeight;
final Function onPressed;
final GlobalKey parentKey;
DraggableFloatingActionButton({
required this.child,
required this.deviceWidth,
required this.deviceHeight,
required this.onPressed,
required this.parentKey,
});
#override
State<StatefulWidget> createState() => _DraggableFloatingActionButtonState();
}
class _DraggableFloatingActionButtonState extends State<DraggableFloatingActionButton> {
final GlobalKey _key = GlobalKey();
bool _isDragging = false;
late Offset _offset;
late Offset _minOffset;
late Offset _maxOffset;
#override
void initState() {
_offset = Offset(widget.deviceWidth * 0.8, widget.deviceHeight * 0.75);
WidgetsBinding.instance.addPostFrameCallback(_setBoundary);
super.initState();
}
void _setBoundary(_) {
final RenderBox parentRenderBox =
widget.parentKey.currentContext?.findRenderObject() as RenderBox;
final RenderBox renderBox = _key.currentContext?.findRenderObject() as RenderBox;
try {
final Size parentSize = parentRenderBox.size;
final Size size = renderBox.size;
setState(() {
_minOffset = const Offset(0, 0);
_maxOffset = Offset(parentSize.width - size.width, parentSize.height - size.height);
});
} catch (e) {
print('catch: $e');
}
}
void _updatePosition(PointerMoveEvent pointerMoveEvent) {
double newOffsetX = _offset.dx + pointerMoveEvent.delta.dx;
double newOffsetY = _offset.dy + pointerMoveEvent.delta.dy;
if (newOffsetX < _minOffset.dx) {
newOffsetX = _minOffset.dx;
} else if (newOffsetX > _maxOffset.dx) {
newOffsetX = _maxOffset.dx;
}
if (newOffsetY < _minOffset.dy) {
newOffsetY = _minOffset.dy;
} else if (newOffsetY > _maxOffset.dy) {
newOffsetY = _maxOffset.dy;
}
setState(() {
_offset = Offset(newOffsetX, newOffsetY);
});
}
#override
Widget build(BuildContext context) {
return Positioned(
left: _offset.dx,
top: _offset.dy,
child: Listener(
onPointerMove: (PointerMoveEvent pointerMoveEvent) {
_updatePosition(pointerMoveEvent);
setState(() {
_isDragging = true;
});
},
onPointerUp: (PointerUpEvent pointerUpEvent) {
print('onPointerUp');
if (_isDragging) {
setState(() {
_isDragging = false;
});
} else {
widget.onPressed();
}
},
child: Container(
key: _key,
child: widget.child,
),
),
);
}
}
As you can see, it only sets the button's offset based on portrait mode once in initState function, and doesn't deal with rotation.
A walk around that I could think of at this moment is just having another floating button specifically set for landscape mode.
Thank you in advance for any answer.
Ok, so I still don't know how to detect rotation, but my walk around works.
All I do is create another class(widget) for a landscape floating button.
In main, check device orientation to use either the floating button portrait class or landscape class.
The only thing that I change in the landscape button class is
from
_offset = Offset(widget.deviceWidth * 0.8, widget.deviceHeight * 0.75);
to
_offset = Offset(widget.deviceHeight * 1.75, widget.deviceWidth * 0.3);
You shouldn't care about orientation. You should use something like LayoutBuilder to determine your space available, and make breakpoint decisions based on that. LayoutBuilder will update immediately if the device space changes, and you should react to that by doing another layout.
I'm making a whiteboard app and I'm going to implement the draw and zoom functions through the GestureDetector.
Each test went well, but if I use both together, only Zoom function is used in onScaleUpdate() and Draw function is not output.
So I'm going to implement it so that if I touch two fingers, I can only use the Zoom function, and if I touch one, I can only use the Draw function.
Can you tell the number of fingers touched using the Gesture Detector?
Or is there another good way?
The following is part of my code
GestureDetector(
onScaleStart: (details) {
Tool tool = context.read<DrawProvider>().tool;
double seletedPenWidth = context.read<DrawProvider>().seletedPenWidth;
Color seletedPenColor = context.read<DrawProvider>().seletedPenColor;
RenderBox box = context.findRenderObject() as RenderBox;
// zoom test
context.read<DrawProvider>().onScaleStart(details);
// Use Pen
if (tool == Tool.pen) {
Offset point = box.globalToLocal(details.focalPoint);
point = Offset(point.dx, point.dy);
currentLine = DrawingModel(
pointList: [point],
color: seletedPenColor,
width: seletedPenWidth,
);
} else {
// TODO Other Tool
}
},
onScaleUpdate: (details) {
Tool tool = context.read<DrawProvider>().tool;
double seletedPenWidth = context.read<DrawProvider>().seletedPenWidth;
Color seletedPenColor = context.read<DrawProvider>().seletedPenColor;
RenderBox box = context.findRenderObject() as RenderBox;
// zoom test
context.read<DrawProvider>().onScaleUpdate(details);
if (tool == Tool.pen) {
Offset point =
box.globalToLocal(details.focalPoint);
point = Offset(point.dx, point.dy);
List<Offset> path = List.from(currentLine!.pointList!)..add(point);
currentLine = DrawingModel(
pointList: path,
color: seletedPenColor,
width: seletedPenWidth,
);
currentLineStreamController.add(currentLine!);
}
},
onScaleEnd: (details) {
Tool tool = context.read<DrawProvider>().tool;
// zoom test
context.read<DrawProvider>().onScaleEnd(details);
if (tool == Tool.pen) {
allLines = List.from(allLines)..add(currentLine!);
linesStreamController.add(allLines);
}
}
provider.dart, zoom functions
Offset _offset = Offset.zero;
Offset _initialFocalPoint = Offset.zero;
Offset _sessionOffset = Offset.zero;
double _scale = 1.0;
double _initialScale = 1.0;
void onScaleStart(ScaleStartDetails details) {
// TODO if use move tool
// _initialFocalPoint = details.focalPoint;
_initialScale = _scale;
notifyListeners();
}
void onScaleUpdate(ScaleUpdateDetails details) {
// TODO if use move tool
// _sessionOffset = details.focalPoint - _initialFocalPoint;
_scale = _initialScale * details.scale;
notifyListeners();
}
void onScaleEnd(ScaleEndDetails details) {
// TODO if use move tool
// _offset += _sessionOffset;
_sessionOffset = Offset.zero;
notifyListeners();
}
whiteboard screen widget
Transform.translate(
offset: _offset + _sessionOffset,
child: Transform.scale(
scale: _scale,
child: buildAllPaths(allLines: allLines), // drawing screen
),
),
Use details.pointerCount from onScaleUpdate and onScaleStart in GestureDetector.
I am just new to Flutter and mobile dev overall, so I've tried to create some Line Chart using fl_chart and there is a problem, which I don't understand why it doesn't work.
So, here is a simple widget, which has a chart and I want to add some interactions to it like a horizontal scroll (it works), reset the state with a double-tap (works as well), and the ability to zoom in to the chart (doesn't work).
Gesture Detector has onScaleUpdate, which I thought it's what I need, but when I try to pinch-zoom in or out, the callback is not being called and I can't change the chart. A lot of things were tried, in order to make it work, but it was unsuccessful.
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
// ...
class FlChartState extends State<FlChart> {
List<LineChartBarData> mainChartData = [];
double minX = 0;
double maxX = 0;
double resultsLength = 60;
#override
void initState() {
_mainBarDataCalculation(); // Method that prepares the data for the chart
super.initState();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onDoubleTap: () {
setState(() {
minX = 0;
maxX = resultsLength;
});
},
onScaleUpdate: (scaleUpd) {
// doesn't trigger
},
onHorizontalDragUpdate: (dragUpdDet) {
setState(() {
double primDelta = dragUpdDet.primaryDelta ?? 0.0;
if (primDelta != 0) {
if (primDelta.isNegative) {
if (maxX < resultsLength) {
minX += maxX * 0.005;
maxX += maxX * 0.005;
}
} else {
if (minX > 0) {
minX -= maxX * 0.005;
maxX -= maxX * 0.005;
}
}
}
});
},
child: LineChart(_createLineChartDataWidget()),
);
}
// ...
}
Is there something wrong?
I'm making a kind-of painting app that has you paint over a background image. There's a couple of tools like pen and then there's eraser. Ideally, we would just paint over the background color, but it's an image. We had the idea of removing the points that were under our finger as we panned across the screen but the offsets and the local position didn't match (they were like 40 (units?) off).
onPanUpdate: (DragUpdateDetails details) {
//Adds the point you are tapping on to the '_points' list as Offset(x,y). Later we draw those points.
if (tool == 'pen') {
RenderBox object = context.findRenderObject();
Offset _localPosition =
object.globalToLocal(details.globalPosition);
_points = List.from(_points)..add(_localPosition);
} else if (tool == 'eraser') {
RenderBox object = context.findRenderObject();
Offset _localPosition =
object.globalToLocal(details.globalPosition);
_points = List.from(_points)
..removeWhere(
(offset) {
if (offset == null) return false;
if (offset.dx.round() == _localPosition.dx.round() &&
offset.dy.round() == _localPosition.dy.round()) {
return true;
}
return false;
},
);
//This part is not working.
}
setState(() {});
},
If there's another method that I could use, that would be great but I can't see one as of now.
I am using GestureDetector and didn't find any onXYZ event which tells you the direction of drag.
Did you try onPanUpdate(details) method? Here is how you can do it.
GestureDetector(
onPanUpdate: (details) {
if (details.delta.dx > 0)
print("Dragging in +X direction");
else
print("Dragging in -X direction");
if (details.delta.dy > 0)
print("Dragging in +Y direction");
else
print("Dragging in -Y direction");
},
child: Container(
color: Colors.blue,
width: double.infinity,
height: double.infinity,
),
)
Note: This callback causes a crash if onHorizontalDragUpdate() or onVerticalDragUpdate() is also provided as mentioned by anmol.majhail.
For those, who have "Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan." error:
Implementing both drag and zoom-in with [onScaleUpdate] in [GestureDetector] widget:
onScaleUpdate: (scaleInfo) {
//for drag, use [scaleInfo.focalPointDelta.dx] and [scaleInfo.focalPointDelta.dy]
_controller.translate(scaleInfo.focalPointDelta.dx, scaleInfo.focalPointDelta.dy);
//for zooming use [scaleInfo.scale] and don't react when zoom is exactly '1' [scaleInfo.scale != 1]
if (scaleInfo.scale > 0.5 && scaleInfo.scale < 2 && scaleInfo.scale != 1) {
setState(() {
currentSize = Size(widget.size.width * scaleInfo.scale, widget.size.height * scaleInfo.scale);
});
}
},