dreampad/lib/app/shared/widgets/swipe_next_page_container.dart

247 lines
7.2 KiB
Dart
Raw Normal View History

2023-11-29 20:37:45 +08:00
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<Widget> children;
// 定义一个便捷方法方便子树中的widget获取共享数据
static SwipePageHandlerReceiver? of(BuildContext context) {
return context.getInheritedWidgetOfExactType<SwipePageHandlerReceiver>();
}
@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<SwipePageHandlerReceiver>();
}
final AnimationController controller;
final ValueNotifier<int> 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,
),
);
}
}