dreampad/lib/app/shared/widgets/swipe_next_page_container.dart
2023-11-29 20:37:45 +08:00

247 lines
7.2 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
),
);
}
}