Last active
November 19, 2025 10:55
-
-
Save hawkkiller/e53639eb09a7fbe347bd4c335b0ad73f to your computer and use it in GitHub Desktop.
This is a cool sliding transition for widgets. It's inspired by the wolt_modal_sheet package, which has the same animation but is coupled with the Bottom Sheet/Pages API. This implementation should be also more performant and less fragile.
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 'package:flutter/material.dart'; | |
| /// A widget that transitions between two children using a fade and slide animation. | |
| class PageTransitionSwitcher extends StatelessWidget { | |
| const PageTransitionSwitcher({ | |
| required this.child, | |
| this.isForwardMove = true, | |
| super.key, | |
| }); | |
| /// The child widget to transition between. | |
| final Widget child; | |
| final bool isForwardMove; | |
| static const _duration = Duration(milliseconds: 350); | |
| static const _sizeCurve = Interval(0 / 350, 300 / 350, curve: Curves.fastOutSlowIn); | |
| static const _opacityIncomingCurve = Interval(150 / 350, 1); | |
| static const _opacityOutgoingCurve = Interval(50 / 350, 150 / 350); | |
| static const _slideCurve = Interval(50 / 350, 1, curve: Curves.fastOutSlowIn); | |
| Key? get _currentChildKey => child.key; | |
| @override | |
| Widget build(BuildContext context) { | |
| return AnimatedSize( | |
| alignment: Alignment.topCenter, | |
| duration: _duration, | |
| curve: _sizeCurve, | |
| child: AnimatedSwitcher( | |
| duration: _duration, | |
| layoutBuilder: (currentChild, previousChildren) => Stack( | |
| clipBehavior: Clip.none, | |
| children: [ | |
| ...previousChildren.map((child) => Positioned(top: 0, left: 0, right: 0, child: child)), | |
| if (currentChild != null) currentChild, | |
| ], | |
| ), | |
| transitionBuilder: (child, animation) => _transitionBuilder( | |
| child, | |
| animation, | |
| currentChildKey: _currentChildKey, | |
| isForwardMove: isForwardMove, | |
| textDirection: Directionality.of(context), | |
| ), | |
| child: child, | |
| ), | |
| ); | |
| } | |
| } | |
| enum _TransitionState { incoming, outgoing } | |
| Widget _transitionBuilder( | |
| Widget child, | |
| Animation<double> animation, { | |
| required Key? currentChildKey, | |
| required bool isForwardMove, | |
| required TextDirection textDirection, | |
| }) { | |
| final isIncoming = child.key == currentChildKey; | |
| final opacityCurve = isIncoming | |
| ? PageTransitionSwitcher._opacityIncomingCurve | |
| : PageTransitionSwitcher._opacityOutgoingCurve; | |
| final reverseOpacityCurve = isIncoming | |
| ? PageTransitionSwitcher._opacityOutgoingCurve | |
| : PageTransitionSwitcher._opacityIncomingCurve; | |
| final state = isIncoming ? _TransitionState.incoming : _TransitionState.outgoing; | |
| return FadeTransition( | |
| opacity: CurvedAnimation( | |
| parent: animation, | |
| curve: opacityCurve, | |
| reverseCurve: reverseOpacityCurve, | |
| ), | |
| child: SlideTransition( | |
| position: _slidePosition( | |
| animation, | |
| state: state, | |
| textDirection: textDirection, | |
| isForwardMove: isForwardMove, | |
| ), | |
| child: child, | |
| ), | |
| ); | |
| } | |
| Animation<Offset> _slidePosition( | |
| Animation<double> animation, { | |
| required _TransitionState state, | |
| required TextDirection textDirection, | |
| required bool isForwardMove, | |
| }) { | |
| final directionMultiplier = | |
| (textDirection == TextDirection.ltr ? 1 : -1) * (isForwardMove ? 1 : -1); | |
| switch (state) { | |
| case _TransitionState.incoming: | |
| final incomingBeginOffset = Offset(.3 * directionMultiplier, 0); | |
| return Tween<Offset>(begin: incomingBeginOffset, end: Offset.zero).animate( | |
| CurvedAnimation( | |
| parent: animation, | |
| curve: PageTransitionSwitcher._slideCurve, | |
| ), | |
| ); | |
| case _TransitionState.outgoing: | |
| final outgoingEndOffset = Offset(.3 * -directionMultiplier, 0); | |
| return Tween<Offset>(begin: outgoingEndOffset, end: Offset.zero).animate( | |
| CurvedAnimation( | |
| parent: animation, | |
| curve: PageTransitionSwitcher._slideCurve, | |
| ), | |
| ); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment