Created
November 18, 2024 07:00
-
-
Save Ayush783/161de4d9eb13107717feb1205106dcb4 to your computer and use it in GitHub Desktop.
Custom page transition in Flutter.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:math'; | |
import 'dart:ui'; | |
import 'package:flutter/material.dart'; | |
class GateOpenPageTransition extends StatefulWidget { | |
const GateOpenPageTransition({ | |
super.key, | |
required this.child, | |
this.backgroundColor = Colors.white, | |
this.imageProvider, | |
this.animationDuration = const Duration(milliseconds: 400), | |
this.axis = ClipAxis.horizontal, | |
}); | |
final Widget child; | |
/// background color of the page | |
final Color backgroundColor; | |
/// Image that is to be displayed in center, Can be left null i.e no image to be shown | |
final ImageProvider? imageProvider; | |
final Duration animationDuration; | |
/// The axis in which the animation will occur | |
final ClipAxis axis; | |
@override | |
State<GateOpenPageTransition> createState() => _GateOpenPageTransitionState(); | |
} | |
class _GateOpenPageTransitionState extends State<GateOpenPageTransition> | |
with SingleTickerProviderStateMixin { | |
late final AnimationController _animationController; | |
bool showChild = false; | |
@override | |
void initState() { | |
_animationController = AnimationController( | |
vsync: this, | |
duration: widget.animationDuration, | |
); | |
WidgetsBinding.instance.addPostFrameCallback( | |
(timeStamp) { | |
_animationController.forward(); | |
_animationController.addStatusListener(_animationStatusListener); | |
}, | |
); | |
super.initState(); | |
} | |
bool hideUpperlayer = false; | |
void _animationStatusListener(AnimationStatus status) { | |
switch (status) { | |
case AnimationStatus.completed: | |
showChild = true; | |
_animationController.reverse(); | |
break; | |
case AnimationStatus.dismissed: | |
hideUpperlayer = true; | |
default: | |
} | |
} | |
@override | |
void dispose() { | |
_animationController.removeStatusListener(_animationStatusListener); | |
_animationController.dispose(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
final size = MediaQuery.sizeOf(context); | |
return Scaffold( | |
backgroundColor: Colors.transparent, | |
body: AnimatedBuilder( | |
animation: _animationController, | |
builder: (context, child) => Stack( | |
children: [ | |
if (showChild) child!, | |
// _BlurEffect(), | |
if (!hideUpperlayer) ...[ | |
BlurEffectWidget( | |
blurX: max(10 * (_animationController.value - 0.7), 0), | |
blurY: max(10 * (_animationController.value - 0.7), 0), | |
child: ColoredBox( | |
color: Colors.black.withOpacity( | |
_animationController.value > 0.7 ? 0.1 : 0)), | |
), | |
Transform.translate( | |
offset: Offset( | |
-size.width * 0.5 * (1 - _animationController.value), 0), | |
child: ClipRect( | |
clipper: ClipHalf( | |
offset: const Offset(0, 0), | |
), | |
child: _buildUpperLayer(context)), | |
), | |
Transform.translate( | |
offset: Offset( | |
size.width * 0.5 * (1 - _animationController.value), 0), | |
child: ClipRect( | |
clipper: ClipHalf( | |
offset: Offset(size.width * 0.5, 0), | |
), | |
child: _buildUpperLayer(context)), | |
), | |
SizedBox.expand( | |
child: ColoredBox( | |
color: Colors.black.withOpacity( | |
max(_animationController.value - 0.7, 0), | |
), | |
), | |
), | |
] | |
], | |
), | |
child: widget.child, | |
), | |
); | |
} | |
Widget _buildUpperLayer(BuildContext context) => SizedBox( | |
height: MediaQuery.sizeOf(context).height, | |
width: MediaQuery.sizeOf(context).width, | |
child: ColoredBox( | |
color: widget.backgroundColor, | |
child: Center( | |
child: widget.imageProvider != null | |
? Image( | |
image: widget.imageProvider!, | |
width: MediaQuery.sizeOf(context).width) | |
: const SizedBox.shrink(), | |
), | |
), | |
); | |
} | |
enum ClipAxis { | |
vertical, | |
horizontal; | |
} | |
class ClipHalf extends CustomClipper<Rect> { | |
final ClipAxis clipAxis; | |
final Offset offset; | |
ClipHalf({ | |
super.reclip, | |
required this.offset, | |
this.clipAxis = ClipAxis.horizontal, | |
}); | |
@override | |
Rect getClip(Size size) { | |
return Rect.fromLTWH( | |
offset.dx, | |
offset.dy, | |
clipAxis == ClipAxis.horizontal ? size.width / 2 : size.width, | |
clipAxis == ClipAxis.horizontal ? size.height : size.height / 2, | |
); | |
} | |
@override | |
bool shouldReclip(covariant CustomClipper<Rect> oldClipper) { | |
return false; | |
} | |
} | |
class BlurEffectWidget extends StatelessWidget { | |
final double blurX; | |
final double blurY; | |
final Widget child; | |
const BlurEffectWidget({ | |
Key? key, | |
this.blurX = 5.0, | |
this.blurY = 5.0, | |
required this.child, | |
}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return Positioned.fill( | |
child: BackdropFilter( | |
filter: ImageFilter.blur(sigmaX: blurX, sigmaY: blurY), | |
child: child, | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment