Files
imajviewer/lib/widgets/image_canvas.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),
),
],
),
);
},
),
),
),
);
}
}