基于DraggableScrollableSheet实现页面拖拽过渡动画方案咨询
双向拖拽页面过渡实现方案
问题描述
我有两个页面,需求是:
- 向下拖拽第一个页面,完成向第二个页面的过渡
- 第二个页面底部的DraggableScrollableSheet向上拖拽时,能回到第一个页面
目前借助ChatGPT得到的代码仅实现了第一部分功能,无法将Sheet的拖拽动作转化为页面过渡动画。请问该需求是否可行?若可行,应如何实现?
当前代码:
import 'package:flutter/material.dart'; class DragTransitionView extends StatefulWidget { @override State<DragTransitionView> createState() => _DragTransitionViewState(); } class _DragTransitionViewState extends State<DragTransitionView> with SingleTickerProviderStateMixin { late AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: Duration(milliseconds: 300), ); } @override void dispose() { _controller.dispose(); super.dispose(); } void _onFirstPageDragUpdate(DragUpdateDetails details) { // Dragging down increases the controller value if (details.primaryDelta! > 0) { _controller.value += details.primaryDelta! / MediaQuery.of(context).size.height; } } void _onFirstPageDragEnd(DragEndDetails details) { if (_controller.value > 0.5) { _controller.forward(); // Complete transition to the second page } else { _controller.reverse(); // Return to the first page } } @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ // Second Page FadeTransition( opacity: _controller, child: AnimatedBuilder( animation: _controller, builder: (context, child) { return Transform.translate( offset: Offset( 0, MediaQuery.of(context).size.height * (1 - _controller.value)), child: child, ); }, child: Container( color: Colors.blue, child: Stack( children: [ Center( child: Text( 'Second Page', style: TextStyle(color: Colors.white, fontSize: 24), ), ), // DraggableScrollableSheet DraggableScrollableSheet( initialChildSize: 0.3, // Initial height of the sheet minChildSize: 0.1, // Minimum height maxChildSize: 1.0, // Maximum height builder: (BuildContext context, ScrollController scrollController) { return FadeTransition( opacity: _controller, child: Container( color: Colors.white, child: SingleChildScrollView( controller: scrollController, child: Center( child: Text( 'Drag Up from Here', style: TextStyle( color: Colors.black, fontSize: 18), ), ), ), ), ); }, ), ], ), ), ), ), // First Page GestureDetector( onVerticalDragUpdate: _onFirstPageDragUpdate, onVerticalDragEnd: _onFirstPageDragEnd, child: AnimatedBuilder( animation: _controller, builder: (context, child) { return Transform.translate( offset: Offset(0, MediaQuery.of(context).size.height * _controller.value), child: Opacity( opacity: 1 - _controller.value, child: child, ), ); }, child: Container( color: Colors.red, child: Center( child: Text( 'First Page', style: TextStyle(color: Colors.white, fontSize: 24), ), ), ), ), ), ], ), ); } }
可行性说明及实现方案
该需求完全可行,核心思路是将DraggableScrollableSheet的拖拽进度与页面过渡的AnimationController绑定,同时处理拖拽边界和结束时的动画归位逻辑。
修改后的完整代码
import 'package:flutter/material.dart'; class DragTransitionView extends StatefulWidget { @override State<DragTransitionView> createState() => _DragTransitionViewState(); } class _DragTransitionViewState extends State<DragTransitionView> with SingleTickerProviderStateMixin { late AnimationController _controller; // 记录Sheet的当前尺寸比例,用于同步到动画控制器 double _sheetSize = 0.3; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), value: 0.0, // 初始值为0,显示第一页 ); } @override void dispose() { _controller.dispose(); super.dispose(); } // 处理第一页向下拖拽更新 void _onFirstPageDragUpdate(DragUpdateDetails details) { if (details.primaryDelta! > 0 && _controller.value < 1.0) { _controller.value += details.primaryDelta! / MediaQuery.of(context).size.height; // 同步Sheet尺寸,确保页面过渡时Sheet处于初始位置 _sheetSize = 0.3 - (_controller.value * 0.2); setState(() {}); } } // 处理第一页拖拽结束 void _onFirstPageDragEnd(DragEndDetails details) { if (_controller.value > 0.5) { _controller.forward().then((_) { setState(() => _sheetSize = 0.3); }); } else { _controller.reverse().then((_) { setState(() => _sheetSize = 0.3); }); } } // 处理Sheet拖拽更新 void _onSheetDragUpdate(double size) { // 只有当页面完全切换到第二页时,才允许通过Sheet拖拽返回 if (_controller.value == 1.0) { setState(() => _sheetSize = size); // 将Sheet的尺寸变化映射到动画控制器的反向进度 // Sheet从0.3到1.0对应控制器从1.0到0.0 _controller.value = 1.0 - ((size - 0.3) / 0.7); } } // 处理Sheet拖拽结束 void _onSheetDragEnd() { // 根据当前控制器值判断是否完成返回动画 if (_controller.value < 0.5) { _controller.reverse(); } else { _controller.forward().then((_) { setState(() => _sheetSize = 0.3); }); } } @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ // 第二页 AnimatedBuilder( animation: _controller, builder: (context, child) { return Transform.translate( offset: Offset( 0, MediaQuery.of(context).size.height * (1 - _controller.value), ), child: Opacity( opacity: _controller.value, child: child, ), ); }, child: Container( color: Colors.blue, child: Stack( children: [ const Center( child: Text( 'Second Page', style: TextStyle(color: Colors.white, fontSize: 24), ), ), // 可拖拽Sheet DraggableScrollableSheet( initialChildSize: _sheetSize, minChildSize: 0.1, maxChildSize: 1.0, onDragUpdate: (details) => _onSheetDragUpdate(details.extent), onDragEnd: (details) => _onSheetDragEnd(), builder: (BuildContext context, ScrollController scrollController) { return Container( color: Colors.white, child: SingleChildScrollView( controller: scrollController, child: const Center( child: Text( 'Drag Up from Here', style: TextStyle(color: Colors.black, fontSize: 18), ), ), ), ); }, ), ], ), ), ), // 第一页 GestureDetector( onVerticalDragUpdate: _onFirstPageDragUpdate, onVerticalDragEnd: _onFirstPageDragEnd, child: AnimatedBuilder( animation: _controller, builder: (context, child) { return Transform.translate( offset: Offset( 0, MediaQuery.of(context).size.height * _controller.value, ), child: Opacity( opacity: 1 - _controller.value, child: child, ), ); }, child: Container( color: Colors.red, child: const Center( child: Text( 'First Page', style: TextStyle(color: Colors.white, fontSize: 24), ), ), ), ), ), ], ), ); } }
关键改动说明
- 绑定Sheet拖拽与动画控制器:通过
DraggableScrollableSheet的onDragUpdate回调,将Sheet的拖拽进度(extent)映射到AnimationController的反向值,实现向上拖拽Sheet时,页面同步过渡回第一页。 - 同步页面过渡时的Sheet状态:在第一页拖拽过程中,同步调整Sheet的尺寸,避免页面过渡时Sheet位置错乱。
- 优化拖拽结束逻辑:分别处理第一页和Sheet拖拽结束时的动画归位,根据当前进度判断是完成过渡还是回弹到初始状态。
- 移除冗余的FadeTransition:将透明度整合到AnimatedBuilder中,简化层级结构。
内容的提问来源于stack exchange,提问作者MKJ
相关产品推荐
相关产品推荐

