import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/gestures.dart'; class ImageCanvas extends StatefulWidget { final String filePath; const ImageCanvas({super.key, required this.filePath}); @override State createState() => _ImageCanvasState(); } class _ImageCanvasState extends State { late TransformationController _controller; double _currentScale = 1.0; @override void initState() { super.initState(); _controller = TransformationController(); _controller.addListener(_onTransformChanged); } @override void didUpdateWidget(ImageCanvas oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.filePath != widget.filePath) { _resetView(); } } void _resetView() { _controller.value = Matrix4.identity(); _currentScale = 1.0; } void _onTransformChanged() { _currentScale = _controller.value.getMaxScaleOnAxis(); } void _handleScroll(PointerScrollEvent event) { final renderBox = context.findRenderObject() as RenderBox?; if (renderBox == null) return; final localPosition = renderBox.globalToLocal(event.position); final delta = event.scrollDelta.dy; final scaleFactor = delta < 0 ? 1.1 : 1 / 1.1; final newScale = (_currentScale * scaleFactor).clamp(0.1, 10.0); if (newScale <= 0.1 || newScale >= 10.0) return; final actualScale = newScale / _currentScale; // Decompose current matrix: widget_point = scale * child_point + translation // So: child_point = (widget_point - translation) / scale final matrix = _controller.value; final tx = matrix.getTranslation().x; final ty = matrix.getTranslation().y; final childX = (localPosition.dx - tx) / _currentScale; final childY = (localPosition.dy - ty) / _currentScale; // Build new matrix: zoom around child_point, then re-apply final newMatrix = Matrix4.identity() // ignore: deprecated_member_use ..translate(childX, childY) // ignore: deprecated_member_use ..scale(actualScale) // ignore: deprecated_member_use ..translate(-childX, -childY) ..multiply(_controller.value); _controller.value = newMatrix; } @override void dispose() { _controller.removeListener(_onTransformChanged); _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Listener( onPointerSignal: (event) { if (event is PointerScrollEvent) { _handleScroll(event); } }, child: InteractiveViewer( transformationController: _controller, boundaryMargin: const EdgeInsets.all(double.infinity), minScale: 0.1, maxScale: 10.0, panEnabled: true, scaleEnabled: false, child: Center( child: Image.file( File(widget.filePath), fit: BoxFit.contain, filterQuality: FilterQuality.high, errorBuilder: (context, error, stackTrace) { return const Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.broken_image, size: 48, color: Colors.white38), SizedBox(height: 8), Text( 'Resim yüklenemedi', style: TextStyle(color: Colors.white38), ), ], ), ); }, ), ), ), ); } }