Created
August 13, 2022 19:27
-
-
Save wrbl606/bc14993b89331788667eebc0e311702c to your computer and use it in GitHub Desktop.
Flutter InteractiveViewer with tap-only gesture
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
const imageUrl = 'https://images.unsplash.com/photo-1519501025264-65ba15a82390?w=1500&q=80'; | |
void main() => runApp(MyApp()); | |
class MyApp extends StatefulWidget { | |
@override | |
State<MyApp> createState() => _MyAppState(); | |
} | |
class _MyAppState extends State<MyApp> { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
home: DoubleTappableInteractiveViewer( | |
scaleDuration: const Duration(milliseconds: 600), | |
child: Image.network(imageUrl), | |
), | |
); | |
} | |
} | |
class DoubleTappableInteractiveViewer extends StatefulWidget { | |
final double scale; | |
final Duration scaleDuration; | |
final Curve curve; | |
final Widget child; | |
const DoubleTappableInteractiveViewer({ | |
super.key, | |
this.scale = 2, | |
this.curve = Curves.fastLinearToSlowEaseIn, | |
required this.scaleDuration, | |
required this.child, | |
}); | |
@override | |
State<DoubleTappableInteractiveViewer> createState() => _DoubleTappableInteractiveViewerState(); | |
} | |
class _DoubleTappableInteractiveViewerState extends State<DoubleTappableInteractiveViewer> | |
with SingleTickerProviderStateMixin { | |
late AnimationController _animationController; | |
Animation<Matrix4>? _zoomAnimation; | |
late TransformationController _transformationController; | |
TapDownDetails? _doubleTapDetails; | |
@override | |
void initState() { | |
super.initState(); | |
_transformationController = TransformationController(); | |
_animationController = AnimationController( | |
vsync: this, | |
duration: widget.scaleDuration, | |
)..addListener(() { | |
_transformationController.value = _zoomAnimation!.value; | |
}); | |
} | |
@override | |
void dispose() { | |
_transformationController.dispose(); | |
_animationController.dispose(); | |
super.dispose(); | |
} | |
void _handleDoubleTapDown(TapDownDetails details) { | |
_doubleTapDetails = details; | |
} | |
void _handleDoubleTap() { | |
final newValue = | |
_transformationController.value.isIdentity() ? | |
_applyZoom() : _revertZoom(); | |
_zoomAnimation = Matrix4Tween( | |
begin: _transformationController.value, | |
end: newValue, | |
).animate( | |
CurveTween(curve: widget.curve) | |
.animate(_animationController) | |
); | |
_animationController.forward(from: 0); | |
} | |
Matrix4 _applyZoom() { | |
final tapPosition = _doubleTapDetails!.localPosition; | |
final translationCorrection = widget.scale - 1; | |
final zoomed = Matrix4.identity() | |
..translate( | |
-tapPosition.dx * translationCorrection, | |
-tapPosition.dy * translationCorrection, | |
) | |
..scale(widget.scale); | |
return zoomed; | |
} | |
Matrix4 _revertZoom() => Matrix4.identity(); | |
@override | |
Widget build(BuildContext context) { | |
return GestureDetector( | |
onDoubleTapDown: _handleDoubleTapDown, | |
onDoubleTap: _handleDoubleTap, | |
child: InteractiveViewer( | |
transformationController: _transformationController, | |
child: widget.child, | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment