ok, so after a long time of trying, i found the solution...
PinchZooming
class PinchZooming extends StatefulWidget {
final Widget child;
final double maxScale, minScale;
final Duration resetDuration;
final bool zoomEnabled;
final Function? onZoomStart, onZoomEnd;
const PinchZooming(
{Key? key,
required this.child,
this.resetDuration = const Duration(milliseconds: 100),
this.maxScale = 4.0,
this.minScale = 1.0,
this.zoomEnabled = true,
this.onZoomStart,
this.onZoomEnd})
: assert(maxScale != 0 && minScale != 0 && maxScale > minScale,
'Either min or max scale value equal zero or max scale is less
than min scale'),
super(key: key);
@override
_PinchZoomingState createState() => _PinchZoomingState();
}
class _PinchZoomingState extends State<PinchZooming>
with SingleTickerProviderStateMixin {
final TransformationController _transformationController =
TransformationController();
late Animation<Matrix4> _animation;
late AnimationController _controller;
OverlayEntry? _entry;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.resetDuration,
vsync: this,
);
_animation = Matrix4Tween().animate(_controller);
_controller
.addListener(() => _transformationController.value =
_animation.value);
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
removeOverlay();
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void showOverlay(BuildContext context) {
final RenderBox _renderBox = context.findRenderObject()! as RenderBox;
final Offset _offset = _renderBox.localToGlobal(Offset.zero);
removeOverlay();
_entry = OverlayEntry(
builder: (c) => Stack(
children: [
Positioned.fill(
child:
Opacity(opacity: 0.5, child: Container(color:
Colors.black))),
Positioned(
left: _offset.dx,
top: _offset.dy,
child: InteractiveViewer(
minScale: widget.minScale,
clipBehavior: Clip.none,
scaleEnabled: widget.zoomEnabled,
maxScale: widget.maxScale,
panEnabled: false,
onInteractionStart: (ScaleStartDetails details) {
if (details.pointerCount < 2) return;
if (_entry == null) {
showOverlay(context);
}
},
onInteractionEnd: (_) => restAnimation(),
transformationController: _transformationController,
child: widget.child,
),
),
],
),
);
final OverlayState? _overlay = Overlay.of(context);
_overlay!.insert(_entry!);
}
void removeOverlay() {
_entry?.remove();
_entry = null;
}
void restAnimation() {
_animation = Matrix4Tween(
begin: _transformationController.value, end: Matrix4.identity())
.animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInBack));
_controller.forward(from: 0);
}
@override
Widget build(BuildContext context) {
return InteractiveViewer(
child: widget.child,
clipBehavior: Clip.none,
minScale: widget.minScale,
scaleEnabled: widget.zoomEnabled,
maxScale: widget.maxScale,
panEnabled: false,
onInteractionStart: (ScaleStartDetails details) {
if (details.pointerCount < 2) return;
if (_entry == null) {
showOverlay(context);
}
if (widget.onZoomStart != null) {
widget.onZoomStart!();
}
},
onInteractionUpdate: (details) {
if (_entry == null) return;
_entry!.markNeedsBuild();
},
onInteractionEnd: (details) {
if (details.pointerCount != 1) return;
restAnimation();
if (widget.onZoomEnd != null) {
widget.onZoomEnd!();
}
},
transformationController: _transformationController,
);
}
}
TouchCountRecognizer
class TouchCountRecognizer extends OneSequenceGestureRecognizer {
TouchCountRecognizer(this.onMultiTouchUpdated);
Function(bool) onMultiTouchUpdated;
int touchcount = 0;
@override
void addPointer(PointerDownEvent event) {
startTrackingPointer(event.pointer);
if (touchcount < 1) {
//resolve(GestureDisposition.rejected);
//_p = event.pointer;
onMultiTouchUpdated(false);
} else {
onMultiTouchUpdated(true);
//resolve(GestureDisposition.accepted);
}
touchcount++;
}
@override
String get debugDescription => 'touch count recognizer';
@override
void didStopTrackingLastPointer(int pointer) {}
@override
void handleEvent(PointerEvent event) {
if (!event.down) {
touchcount--;
if (touchcount < 1) {
onMultiTouchUpdated(false);
}
}
}
}
then i companied these two classes together like this...
return SizedBox(
height: 350.h,
child: Consumer<ProductViewModel>(
builder: (_, state, child) => RawGestureDetector(
gestures: <Type, GestureRecognizerFactory>{
TouchCountRecognizer:
GestureRecognizerFactoryWithHandlers<
TouchCountRecognizer>(
() => TouchCountRecognizer(
state.onMultiTouchUpdated),
(TouchCountRecognizer instance) {},
),
},
child: NotificationListener(
onNotification: (notification) {
if (notification is ScrollNotification) {
WidgetsBinding.instance
.addPostFrameCallback((timeStamp) {
state.scrolling = true;
if (state.scrolling == true &&
state.multiTouch == false) {
state.stopZoom();
} else if (state.scrolling == false &&
state.multiTouch == true) {
state.stopZoom();
} else if (state.scrolling == true &&
state.multiTouch == true) {
state.startZoom();
}
});
}
if (notification is ScrollUpdateNotification) {}
if (notification is ScrollEndNotification) {
WidgetsBinding.instance
.addPostFrameCallback((timeStamp) {
state.scrolling = false;
if (state.scrolling == true &&
state.multiTouch == false) {
state.stopZoom();
} else if (state.scrolling == false &&
state.multiTouch == true) {
state.stopZoom();
} else if (state.scrolling == true &&
state.multiTouch == true) {
state.startZoom();
}
});
}
return state.scrolling;
},
child: ListView.builder(
shrinkWrap: false,
physics: state.imagePagerScrollPhysics,
itemCount: imagesPath.length,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return PinchZooming(
zoomEnabled: state.isZoomEnabled,
onZoomStart: () => state.startZoom(),
onZoomEnd: () => state.stopZoom(),
child: CachedNetworkImage(
alignment: Alignment.center,
width: ScreenUtil.defaultSize.width,
imageUrl: imagesPath[index],
progressIndicatorBuilder:
(context, url, downloadProgress) =>
const DaraghmehShimmer(),
errorWidget: (context, url, error) =>
const Icon(Icons.error),
),
);
},
),
),
),
),
);