如何在Flutter中实现类似React Native的可拖拽模态页面导航?
默认的fullscreenDialog: true只能实现基础的全屏模态跳转,没法做到背景缩放和拖拽关闭效果。要实现需求,需要自定义PageRoute来控制路由过渡动画和手势交互,以下是具体实现方案:
1. 自定义模态路由类
创建一个继承自PageRouteBuilder的自定义路由,在过渡动画中处理背景页面的缩放,同时添加垂直拖拽手势来触发关闭:
import 'package:flutter/material.dart'; class ZoomDraggableModalRoute<T> extends PageRouteBuilder<T> { final Widget Function(BuildContext) builder; ZoomDraggableModalRoute({required this.builder}) : super( pageBuilder: (context, animation, secondaryAnimation) => builder(context), transitionsBuilder: (context, animation, secondaryAnimation, child) { // 背景页面缩放动画:从1.0缩到0.9 final scaleAnimation = Tween<double>(begin: 1.0, end: 0.9).animate( CurvedAnimation(parent: animation, curve: Curves.easeInOut), ); return Stack( children: [ // 背景页面(原页面)的缩放效果 ScaleTransition( scale: scaleAnimation, child: ModalBarrier(color: Colors.black.withOpacity(0.3)), ), // 模态页面的入场动画:从底部滑入 SlideTransition( position: Tween<Offset>( begin: const Offset(0, 1), end: Offset.zero, ).animate(CurvedAnimation(parent: animation, curve: Curves.easeOut)), child: _DraggableModalWrapper(child: child), ), ], ); }, opaque: false, // 必须设为false才能看到背景缩放效果 barrierDismissible: true, barrierColor: Colors.transparent, ); } // 封装拖拽手势的组件 class _DraggableModalWrapper extends StatefulWidget { final Widget child; const _DraggableModalWrapper({required this.child}); @override State<_DraggableModalWrapper> createState() => _DraggableModalWrapperState(); } class _DraggableModalWrapperState extends State<_DraggableModalWrapper> { double _dragOffset = 0; bool _isDismissing = false; @override Widget build(BuildContext context) { return GestureDetector( onVerticalDragUpdate: (details) { if (_isDismissing) return; setState(() { _dragOffset += details.delta.dy; // 限制拖拽距离不超过屏幕高度的一半 _dragOffset = _dragOffset.clamp(0, MediaQuery.of(context).size.height / 2); }); }, onVerticalDragEnd: (details) { if (_dragOffset > MediaQuery.of(context).size.height / 6) { // 拖拽距离足够,关闭模态 _isDismissing = true; Navigator.pop(context); } else { // 拖拽距离不足,回弹 setState(() => _dragOffset = 0); } }, child: Transform.translate( offset: Offset(0, _dragOffset), child: widget.child, ), ); } }
2. 使用自定义路由跳转
替换原来的MaterialPageRoute,用自定义的ZoomDraggableModalRoute:
Navigator.push( context, ZoomDraggableModalRoute( builder: (context) => SectionCRoute(), ), );
3. 模态页面样式优化
为了更贴近示例效果,建议给SectionCRoute添加顶部圆角和阴影:
class SectionCRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Container( decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), boxShadow: [ BoxShadow(color: Colors.black12, blurRadius: 10, spreadRadius: 2), ], ), child: // 你的页面内容 ); } }
说明
- 背景缩放动画通过
ScaleTransition实现,配合路由的入场动画曲线,自然过渡 - 拖拽关闭通过监听垂直拖拽手势,根据拖拽距离判断是否关闭,同时添加回弹效果
opaque: false是关键,确保路由不会完全遮挡背景页面- 可以根据需求调整缩放比例、拖拽阈值、动画曲线等参数
内容的提问来源于stack exchange,提问作者zmix
相关产品推荐
相关产品推荐

