164 lines
5.0 KiB
Dart
164 lines
5.0 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;
|
||
bool _isFilled = true; // varsayılan: ekranı doldur
|
||
|
||
@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;
|
||
_isFilled = true;
|
||
}
|
||
|
||
void _onTransformChanged() {
|
||
_currentScale = _controller.value.getMaxScaleOnAxis();
|
||
}
|
||
|
||
void _toggleFillFit() {
|
||
setState(() {
|
||
_isFilled = !_isFilled;
|
||
_controller.value = Matrix4.identity();
|
||
_currentScale = 1.0;
|
||
});
|
||
}
|
||
|
||
void _handleDoubleTap() {
|
||
_toggleFillFit();
|
||
}
|
||
|
||
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;
|
||
|
||
// Fare altındaki child-space noktayı hesapla
|
||
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;
|
||
|
||
// controller * zoomAroundPoint sırasıyla uygula
|
||
final newMatrix = Matrix4.copy(_controller.value)
|
||
// ignore: deprecated_member_use
|
||
..translate(childX, childY)
|
||
// ignore: deprecated_member_use
|
||
..scale(actualScale)
|
||
// ignore: deprecated_member_use
|
||
..translate(-childX, -childY);
|
||
|
||
_controller.value = newMatrix;
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_controller.removeListener(_onTransformChanged);
|
||
_controller.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return LayoutBuilder(
|
||
builder: (context, constraints) {
|
||
return Listener(
|
||
onPointerSignal: (event) {
|
||
if (event is PointerScrollEvent) {
|
||
_handleScroll(event);
|
||
}
|
||
},
|
||
child: GestureDetector(
|
||
onDoubleTap: _handleDoubleTap,
|
||
child: InteractiveViewer(
|
||
transformationController: _controller,
|
||
boundaryMargin: const EdgeInsets.all(double.infinity),
|
||
minScale: 0.1,
|
||
maxScale: 10.0,
|
||
panEnabled: true,
|
||
scaleEnabled: false,
|
||
child: _isFilled
|
||
? SizedBox.expand(
|
||
child: Image.file(
|
||
File(widget.filePath),
|
||
fit: BoxFit.cover,
|
||
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)),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
),
|
||
)
|
||
: 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)),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}
|
||
}
|