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

157 lines
3.9 KiB
Dart

import 'package:flutter/material.dart';
/// A widget used to dismiss its [child].
///
/// Similar to [Dismissible] with some adjustments.
class CustomDismissible extends StatefulWidget {
const CustomDismissible({
required this.child,
this.onDismissed,
this.dismissThreshold = 0.2,
this.enabled = true,
Key? key,
}) : super(key: key);
final Widget child;
final double dismissThreshold;
final VoidCallback? onDismissed;
final bool enabled;
@override
State<CustomDismissible> createState() => _CustomDismissibleState();
}
class _CustomDismissibleState extends State<CustomDismissible>
with SingleTickerProviderStateMixin {
late AnimationController _animateController;
late Animation<Offset> _moveAnimation;
late Animation<double> _scaleAnimation;
late Animation<Decoration> _opacityAnimation;
double _dragExtent = 0;
bool _dragUnderway = false;
bool get _isActive => _dragUnderway || _animateController.isAnimating;
@override
void initState() {
super.initState();
_animateController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_updateMoveAnimation();
}
@override
void dispose() {
_animateController.dispose();
super.dispose();
}
void _updateMoveAnimation() {
final double end = _dragExtent.sign;
_moveAnimation = _animateController.drive(
Tween<Offset>(
begin: Offset.zero,
end: Offset(0, end),
),
);
_scaleAnimation = _animateController.drive(Tween<double>(
begin: 1,
end: 0.5,
));
_opacityAnimation = DecorationTween(
begin: const BoxDecoration(color: Color(0xFF000000)),
end: const BoxDecoration(color: Color(0x00000000)),
).animate(_animateController);
}
void _handleDragStart(DragStartDetails details) {
_dragUnderway = true;
if (_animateController.isAnimating) {
_dragExtent =
_animateController.value * context.size!.height * _dragExtent.sign;
_animateController.stop();
} else {
_dragExtent = 0.0;
_animateController.value = 0.0;
}
setState(_updateMoveAnimation);
}
void _handleDragUpdate(DragUpdateDetails details) {
if (!_isActive || _animateController.isAnimating) {
return;
}
final double delta = details.primaryDelta!;
final double oldDragExtent = _dragExtent;
if (_dragExtent + delta < 0) {
_dragExtent += delta;
} else if (_dragExtent + delta > 0) {
_dragExtent += delta;
}
if (oldDragExtent.sign != _dragExtent.sign) {
setState(_updateMoveAnimation);
}
if (!_animateController.isAnimating) {
_animateController.value = _dragExtent.abs() / context.size!.height;
}
}
void _handleDragEnd(DragEndDetails details) {
if (!_isActive || _animateController.isAnimating) {
return;
}
_dragUnderway = false;
if (_animateController.isCompleted) {
return;
}
if (!_animateController.isDismissed) {
// if the dragged value exceeded the dismissThreshold, call onDismissed
// else animate back to initial position.
if (_animateController.value > widget.dismissThreshold) {
widget.onDismissed?.call();
} else {
_animateController.reverse();
}
}
}
@override
Widget build(BuildContext context) {
final Widget content = DecoratedBoxTransition(
decoration: _opacityAnimation,
child: SlideTransition(
position: _moveAnimation,
child: ScaleTransition(
scale: _scaleAnimation,
child: widget.child,
),
),
);
return GestureDetector(
behavior: HitTestBehavior.translucent,
onVerticalDragStart: widget.enabled ? _handleDragStart : null,
onVerticalDragUpdate: widget.enabled ? _handleDragUpdate : null,
onVerticalDragEnd: widget.enabled ? _handleDragEnd : null,
child: content,
);
}
}