Files
pilipala/lib/plugin/pl_gallery/interactive_viewer_boundary.dart
2024-12-08 23:50:36 +08:00

118 lines
3.2 KiB
Dart

import 'package:flutter/material.dart';
/// A callback for the [InteractiveViewerBoundary] that is called when the scale
/// changed.
typedef ScaleChanged = void Function(double scale);
/// Builds an [InteractiveViewer] and provides callbacks that are called when a
/// horizontal boundary has been hit.
///
/// The callbacks are called when an interaction ends by listening to the
/// [InteractiveViewer.onInteractionEnd] callback.
class InteractiveViewerBoundary extends StatefulWidget {
const InteractiveViewerBoundary({
required this.child,
required this.boundaryWidth,
this.controller,
this.onScaleChanged,
this.onLeftBoundaryHit,
this.onRightBoundaryHit,
this.onNoBoundaryHit,
this.maxScale,
this.minScale,
Key? key,
}) : super(key: key);
final Widget child;
/// The max width this widget can have.
///
/// If the [InteractiveViewer] can take up the entire screen width, this
/// should be set to `MediaQuery.of(context).size.width`.
final double boundaryWidth;
/// The [TransformationController] for the [InteractiveViewer].
final TransformationController? controller;
/// Called when the scale changed after an interaction ended.
final ScaleChanged? onScaleChanged;
/// Called when the left boundary has been hit after an interaction ended.
final VoidCallback? onLeftBoundaryHit;
/// Called when the right boundary has been hit after an interaction ended.
final VoidCallback? onRightBoundaryHit;
/// Called when no boundary has been hit after an interaction ended.
final VoidCallback? onNoBoundaryHit;
final double? maxScale;
final double? minScale;
@override
InteractiveViewerBoundaryState createState() =>
InteractiveViewerBoundaryState();
}
class InteractiveViewerBoundaryState extends State<InteractiveViewerBoundary> {
TransformationController? _controller;
double? _scale;
@override
void initState() {
super.initState();
_controller = widget.controller ?? TransformationController();
}
@override
void dispose() {
_controller!.dispose();
super.dispose();
}
void _updateBoundaryDetection() {
final double scale = _controller!.value.row0[0];
if (_scale != scale) {
// the scale changed
_scale = scale;
widget.onScaleChanged?.call(scale);
}
if (scale <= 1.01) {
// cant hit any boundaries when the child is not scaled
return;
}
final double xOffset = _controller!.value.row0[3];
final double boundaryWidth = widget.boundaryWidth;
final double boundaryEnd = boundaryWidth * scale;
final double xPos = boundaryEnd + xOffset;
if (boundaryEnd.round() == xPos.round()) {
// left boundary hit
widget.onLeftBoundaryHit?.call();
} else if (boundaryWidth.round() == xPos.round()) {
// right boundary hit
widget.onRightBoundaryHit?.call();
} else {
widget.onNoBoundaryHit?.call();
}
}
@override
Widget build(BuildContext context) {
return InteractiveViewer(
maxScale: widget.maxScale!,
minScale: widget.minScale!,
transformationController: _controller,
onInteractionEnd: (_) => _updateBoundaryDetection(),
child: widget.child,
);
}
}