Skip to content

Instantly share code, notes, and snippets.

@Ayush783
Created November 18, 2024 07:00
Show Gist options
  • Save Ayush783/161de4d9eb13107717feb1205106dcb4 to your computer and use it in GitHub Desktop.
Save Ayush783/161de4d9eb13107717feb1205106dcb4 to your computer and use it in GitHub Desktop.
Custom page transition in Flutter.
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