Files
submanager/lib/widgets/animated_page_transitions.dart

338 lines
11 KiB
Dart

import 'package:flutter/material.dart';
import 'dart:math' as math;
import '../utils/reduce_motion.dart';
/// 슬라이드 + 페이드 전환
class SlidePageRoute<T> extends PageRouteBuilder<T> {
final Widget page;
final AxisDirection direction;
SlidePageRoute({
required this.page,
this.direction = AxisDirection.right,
}) : super(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionDuration: ReduceMotion.platform()
? Duration.zero
: const Duration(milliseconds: 300),
reverseTransitionDuration: ReduceMotion.platform()
? Duration.zero
: const Duration(milliseconds: 300),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
Offset begin;
switch (direction) {
case AxisDirection.right:
begin = const Offset(-1.0, 0.0);
break;
case AxisDirection.left:
begin = const Offset(1.0, 0.0);
break;
case AxisDirection.up:
begin = const Offset(0.0, 1.0);
break;
case AxisDirection.down:
begin = const Offset(0.0, -1.0);
break;
}
const end = Offset.zero;
const curve = Curves.easeOutCubic;
var tween = Tween(begin: begin, end: end).chain(
CurveTween(curve: curve),
);
var offsetAnimation = animation.drive(tween);
var fadeTween = Tween(begin: 0.0, end: 1.0).chain(
CurveTween(curve: curve),
);
var fadeAnimation = animation.drive(fadeTween);
return SlideTransition(
position: offsetAnimation,
child: FadeTransition(
opacity: fadeAnimation,
child: child,
),
);
},
);
}
/// 스케일 + 페이드 전환
class ScalePageRoute<T> extends PageRouteBuilder<T> {
final Widget page;
final Alignment alignment;
ScalePageRoute({
required this.page,
this.alignment = Alignment.center,
}) : super(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionDuration: ReduceMotion.platform()
? Duration.zero
: const Duration(milliseconds: 400),
reverseTransitionDuration: ReduceMotion.platform()
? Duration.zero
: const Duration(milliseconds: 400),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const curve = Curves.elasticOut;
var scaleTween = Tween(begin: 0.0, end: 1.0).chain(
CurveTween(curve: curve),
);
var scaleAnimation = animation.drive(scaleTween);
var fadeTween = Tween(begin: 0.0, end: 1.0).chain(
CurveTween(curve: Curves.easeIn),
);
var fadeAnimation = animation.drive(fadeTween);
return ScaleTransition(
scale: scaleAnimation,
alignment: alignment,
child: FadeTransition(
opacity: fadeAnimation,
child: child,
),
);
},
);
}
/// 회전 + 스케일 전환
class RotatePageRoute<T> extends PageRouteBuilder<T> {
final Widget page;
RotatePageRoute({required this.page})
: super(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionDuration: ReduceMotion.platform()
? Duration.zero
: const Duration(milliseconds: 500),
reverseTransitionDuration: ReduceMotion.platform()
? Duration.zero
: const Duration(milliseconds: 500),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const curve = Curves.easeInOut;
var rotateTween = Tween(begin: -0.5, end: 0.0).chain(
CurveTween(curve: curve),
);
var rotateAnimation = animation.drive(rotateTween);
var scaleTween = Tween(begin: 0.0, end: 1.0).chain(
CurveTween(curve: curve),
);
var scaleAnimation = animation.drive(scaleTween);
return Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateZ(rotateAnimation.value)
..scale(scaleAnimation.value),
child: child,
);
},
);
}
/// 3D 플립 전환
class FlipPageRoute<T> extends PageRouteBuilder<T> {
final Widget page;
final bool horizontal;
FlipPageRoute({
required this.page,
this.horizontal = true,
}) : super(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionDuration: ReduceMotion.platform()
? Duration.zero
: const Duration(milliseconds: 800),
reverseTransitionDuration: ReduceMotion.platform()
? Duration.zero
: const Duration(milliseconds: 800),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
final isAnimatingForward =
animation.status == AnimationStatus.forward;
final flipAnimation = Tween(
begin: 0.0,
end: isAnimatingForward ? -math.pi : math.pi,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeInOut,
));
return AnimatedBuilder(
animation: flipAnimation,
builder: (context, child) {
final isShowingFront = flipAnimation.value.abs() < math.pi / 2;
return Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(horizontal ? flipAnimation.value : 0)
..rotateX(horizontal ? 0 : flipAnimation.value),
child: isShowingFront
? Container()
: Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..rotateY(horizontal ? math.pi : 0)
..rotateX(horizontal ? 0 : math.pi),
child: child,
),
);
},
child: child,
);
},
);
}
/// 컨테이너 트랜스폼 (Material Design)
class ContainerTransformPageRoute<T> extends PageRouteBuilder<T> {
final Widget page;
final Widget startWidget;
final BorderRadius? borderRadius;
ContainerTransformPageRoute({
required this.page,
required this.startWidget,
this.borderRadius,
}) : super(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionDuration: ReduceMotion.platform()
? Duration.zero
: const Duration(milliseconds: 500),
reverseTransitionDuration: ReduceMotion.platform()
? Duration.zero
: const Duration(milliseconds: 500),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return Stack(
children: [
// 배경 페이드
FadeTransition(
opacity: animation,
child: Container(
color: Colors.black.withValues(alpha: 0.3),
),
),
// 컨테이너 확장 애니메이션
AnimatedBuilder(
animation: animation,
builder: (context, _) {
final progress = animation.value;
final scale = 0.5 + (0.5 * progress);
final radius = borderRadius?.topLeft.x ?? 0;
final currentRadius = radius * (1 - progress);
return Transform.scale(
scale: scale,
child: ClipRRect(
borderRadius: BorderRadius.circular(currentRadius),
child: progress < 0.5 ? startWidget : child,
),
);
},
child: child,
),
],
);
},
);
}
/// 커스텀 Hero 애니메이션
class CustomHeroPageRoute<T> extends PageRouteBuilder<T> {
final Widget page;
final String heroTag;
CustomHeroPageRoute({
required this.page,
required this.heroTag,
}) : super(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionDuration: const Duration(milliseconds: 500),
reverseTransitionDuration: const Duration(milliseconds: 500),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: CurvedAnimation(
parent: animation,
curve: const Interval(0.5, 1.0),
),
child: child,
);
},
);
}
/// 공유 축 전환 (Material Design)
class SharedAxisPageRoute<T> extends PageRouteBuilder<T> {
final Widget page;
final SharedAxisTransitionType transitionType;
SharedAxisPageRoute({
required this.page,
required this.transitionType,
}) : super(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionDuration: ReduceMotion.platform()
? Duration.zero
: const Duration(milliseconds: 300),
reverseTransitionDuration: ReduceMotion.platform()
? Duration.zero
: const Duration(milliseconds: 300),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
late final Offset begin;
late final Offset end;
switch (transitionType) {
case SharedAxisTransitionType.horizontal:
begin = const Offset(1.0, 0.0);
end = Offset.zero;
break;
case SharedAxisTransitionType.vertical:
begin = const Offset(0.0, 1.0);
end = Offset.zero;
break;
case SharedAxisTransitionType.scaled:
begin = Offset.zero;
end = Offset.zero;
break;
}
final slideTween = Tween(begin: begin, end: end);
final fadeTween = Tween(begin: 0.0, end: 1.0);
final scaleTween = transitionType == SharedAxisTransitionType.scaled
? Tween(begin: 0.8, end: 1.0)
: Tween(begin: 1.0, end: 1.0);
final slideAnimation = animation.drive(slideTween);
final fadeAnimation = animation.drive(fadeTween);
final scaleAnimation = animation.drive(scaleTween);
return SlideTransition(
position: slideAnimation,
child: FadeTransition(
opacity: fadeAnimation,
child: ScaleTransition(
scale: scaleAnimation,
child: child,
),
),
);
},
);
}
enum SharedAxisTransitionType {
horizontal,
vertical,
scaled,
}