126 lines
3.5 KiB
Dart
126 lines
3.5 KiB
Dart
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<ImageCanvas> createState() => _ImageCanvasState();
|
|
}
|
|
|
|
class _ImageCanvasState extends State<ImageCanvas> {
|
|
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),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|