Flutter中如何为AnimatedSwitcher实现SharedAxisTransition滑动过渡
AnimatedSwitcher实现水平轴向滑动过渡方案
完全可以基于AnimatedSwitcher实现和animations包中SharedAxisTransition一致的水平滑动效果,且能保证背景完全静态不参与动画,核心是自定义过渡构建器,不需要依赖Navigator。
实现要点:
- 所有被AnimatedSwitcher切换的子组件必须设置唯一Key(推荐用ValueKey绑定当前页面的唯一标识,比如页面索引、枚举值),否则框架无法识别组件变化,动画不会触发。
- 自定义transitionBuilder时,同步实现位移动画+透明度动画,匹配SharedAxis的动效逻辑:新页面从水平方向滑入同时渐显,旧页面向反方向滑出同时渐隐。
- 用layoutBuilder配置切换时的堆叠逻辑,避免新旧组件切换时出现布局跳动。
对应原有代码结构的修改版本:
Scaffold( body: Stack( children: [ // 静态背景层,永远不会参与动画、不会重建 SizedBox( height: screenHeight, width: screenWidth, child: Container(color: backgroundColor), ), // 用AnimatedSwitcher包裹需要切换的内容层 AnimatedSwitcher( duration: const Duration(milliseconds: 300), reverseDuration: const Duration(milliseconds: 300), // 自定义水平共享轴过渡逻辑 transitionBuilder: (child, animation) { // 判断当前是进入组件还是退出组件 final isEntering = animation.status == AnimationStatus.forward || animation.status == AnimationStatus.completed; // 位移动画:进入组件从右侧20%屏宽位置滑入,退出组件向左侧20%屏宽位置滑出 final slideAnimation = Tween<Offset>( begin: isEntering ? const Offset(0.2, 0) : const Offset(-0.2, 0), end: Offset.zero, ).chain(CurveTween(curve: Curves.fastOutSlowIn)).animate(animation); // 透明度动画:同步实现渐隐渐显 final fadeAnimation = Tween<double>( begin: 0, end: 1, ).chain(CurveTween(curve: Curves.easeOutCubic)).animate(animation); return SlideTransition( position: slideAnimation, child: FadeTransition( opacity: fadeAnimation, child: child, ), ); }, // 切换时堆叠新旧组件,避免布局跳动 layoutBuilder: (currentChild, previousChildren) { return Stack( alignment: Alignment.center, children: [ ...previousChildren, if (currentChild != null) currentChild, ], ); }, // 你需要切换的内容组件,必须加唯一Key child: OtherWidgetsHere( key: ValueKey(currentPageId), // 换页时更新currentPageId即可触发动画 ), ), ], ) )
如果要完全对齐官方animations包的SharedAxisTransition效果,可以直接参考源码调整偏移系数、动画曲线、时长参数,效果完全一致。
不需要让Scaffold跟着页面切换重建,两种常用实现方式:
- 方案1:全局背景挂载
把静态背景放在App最外层,包裹MaterialApp,所有页面的Scaffold背景设为透明,路由配置为非不透明,切换时只有上层页面元素参与动画,背景完全静态。
核心代码示例:@override Widget build(BuildContext context) { return Stack( children: [ // 全局静态背景,永远不参与路由动画 Positioned.fill( child: Container( // 可配置纯色、背景图、渐变等任意背景内容 color: const Color(0xFFF2F3F5), ), ), MaterialApp( theme: ThemeData( // 全局设置Scaffold背景透明,透出下层静态背景 scaffoldBackgroundColor: Colors.transparent, ), onGenerateRoute: (settings) { return PageRouteBuilder( // 关键:设置路由为非不透明,否则无法透出下层背景 opaque: false, transitionDuration: const Duration(milliseconds: 300), pageBuilder: (context, animation, secondaryAnimation) { // 返回对应页面的Scaffold,不要给Scaffold设置不透明背景 return getPageFromSettings(settings); }, transitionsBuilder: (context, animation, secondaryAnimation, child) { // 这里可以直接复用前面写的水平共享轴过渡逻辑, // 也可以使用任意自定义过渡动画,背景不会受影响 final slideAnimation = Tween<Offset>( begin: const Offset(0.2, 0), end: Offset.zero, ).chain(CurveTween(curve: Curves.fastOutSlowIn)).animate(animation); final fadeAnimation = Tween<double>(begin: 0, end: 1) .chain(CurveTween(curve: Curves.easeOutCubic)) .animate(animation); return SlideTransition( position: slideAnimation, child: FadeTransition(opacity: fadeAnimation, child: child), ); }, ); }, ) ], ); } - 方案2:嵌套Navigator
如果不需要全局固定背景,只需要在某个模块内切换页面时固定背景,可以在该模块的Scaffold内嵌套一个Navigator,把背景放在Scaffold的底层,嵌套Navigator管理的页面全部设为透明背景,切换逻辑和方案1一致,背景不会被路由切换影响。
内容的提问来源于stack exchange,提问作者Jiehfeng
相关产品推荐
相关产品推荐

