import 'package:dartx/dartx.dart'; import 'package:dreampad/app/shared/shared.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; class SwipeNextPageContainer extends HookWidget { /// 叠放[children] /// /// [children]可用用[SwipeNextPageHandler]套[Widget]使之成为滑动的[handler] const SwipeNextPageContainer({ super.key, required this.children, }); final List children; // 定义一个便捷方法,方便子树中的widget获取共享数据 static SwipePageHandlerReceiver? of(BuildContext context) { return context.getInheritedWidgetOfExactType(); } @override Widget build(BuildContext context) { return LayoutBuilder(builder: (context, constraint) { final height = constraint.maxHeight; return HookBuilder(builder: (context) { final currentIndex = useState(children.length - 1); final controller = useAnimationController( lowerBound: 0, upperBound: height, duration: const Duration(milliseconds: 500), ); useEffect(() { void onAnimationStateChanged(AnimationStatus status) { if (status == AnimationStatus.completed) { currentIndex.value = currentIndex.value - 1; } } controller.addStatusListener(onAnimationStateChanged); return () { controller.removeStatusListener(onAnimationStateChanged); }; }, [controller]); useEffect(() { controller.value = 0; return null; }, [currentIndex.value]); return SwipePageHandlerReceiver( controller: controller, currentIndex: currentIndex, child: AnimatedBuilder( animation: controller, builder: (context, _) => Stack( fit: StackFit.expand, children: children.mapIndexed((index, child) { final Widget result; if (index > currentIndex.value) { result = const Positioned(child: SizedBox.shrink()); } else if (index < currentIndex.value) { result = Positioned.fill(child: child); } else { final offset = controller.value; result = Positioned( left: 0, right: 0, top: 0 - offset, bottom: 0 + offset, child: child, ); } return SwipePageItemState( index: index, child: result, ); }).toList(), )), ); }); }); } } class SwipePageHandlerReceiver extends InheritedWidget { const SwipePageHandlerReceiver({ super.key, required super.child, required this.controller, required this.currentIndex, }); // 定义一个便捷方法,方便子树中的widget获取共享数据 static SwipePageHandlerReceiver? of(BuildContext context) { return context.getInheritedWidgetOfExactType(); } final AnimationController controller; final ValueNotifier currentIndex; @override bool updateShouldNotify(covariant InheritedWidget oldWidget) { return false; } } class SwipePageItemState extends InheritedWidget { const SwipePageItemState({ super.key, required super.child, required this.index, }); // 定义一个便捷方法,方便子树中的widget获取共享数据 static SwipePageItemState? of(BuildContext context) { return context.getInheritedWidgetOfExactType(); } final int index; @override bool updateShouldNotify(covariant InheritedWidget oldWidget) { return false; } } class SwipeNextPageHandler extends StatelessWidget { const SwipeNextPageHandler({ super.key, required this.child, }); final Widget child; @override Widget build(BuildContext context) { final controller = SwipePageHandlerReceiver .of(context) ?.controller; if (controller == null) { return child; } return Material( color: Colors.transparent, child: InkWell( onTap: () {}, child: GestureDetector( behavior: HitTestBehavior.opaque, onPanEnd: (details) { var velocityY = details.velocity.pixelsPerSecond.dy; if (velocityY.abs() > kMinFlingVelocity) { if (velocityY < 0) { controller.animateTo( controller.upperBound, curve: Curves.decelerate, duration: Duration( milliseconds: (controller.upperBound - controller.value).abs() ~/ (velocityY.abs() / 1000).coerceAtLeast(1), ), ); } else { controller.animateBack( controller.lowerBound, curve: Curves.decelerate, duration: Duration( milliseconds: (controller.value - controller.lowerBound).abs() ~/ (velocityY.abs() / 1000).coerceAtLeast(1), ), ); } } else { if (controller.value > controller.upperBound * 2 / 3) { controller.forward(); } else { controller.reverse(); } } }, onPanCancel: () { controller.animateTo(controller.lowerBound, curve: Curves.easeIn); }, onPanUpdate: (details) { controller.value -= details.delta.dy; }, child: child, ), ), ); } } class SwipePageFadeOutContainer extends HookWidget { const SwipePageFadeOutContainer({ super.key, required this.child, }); final Widget child; @override Widget build(BuildContext context) { return AnimatedBuilder( animation: SwipeNextPageContainer.of(context)!.controller, builder: (context, _) { final controller = SwipeNextPageContainer.of(context)!.controller; return Opacity( opacity: 1 - controller.value / controller.upperBound, child: child, ); }, ); } } class SwipePageCurrentVisible extends HookWidget { const SwipePageCurrentVisible({ super.key, required this.child, }); final Widget child; @override Widget build(BuildContext context) { final currentIndexState = SwipeNextPageContainer.of(context)!.currentIndex; final index = SwipePageItemState.of(context)!.index; final isVisible = useListenableSelector( currentIndexState, () => currentIndexState.value == index); return Visibility( visible: isVisible, child: AnimatedVisibilityWidget( animationWidgetBuilder: AnimatedVisibilityWidget.fadeAnimationWidgetBuilder, duration: const Duration(milliseconds: 800), isVisible: true, isInitAnimated: true, child: child, ), ); } }