能否实现从屏幕点击位置触发、类似animations包Container transform的AlertDialog动画?
实现从点击位置触发的Container Transform风格AlertDialog动画
完全可以实现这个效果,我们可以通过自定义动画组合(位移+缩放)来模拟类似Container Transform的过渡效果,不用依赖第三方包也能搞定。核心思路是监听点击位置,让弹窗从该位置逐步缩放、位移到屏幕中心,模拟展开的动画;关闭时反向执行动画回到点击位置再隐藏。
具体实现代码
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: const HomePage(), theme: ThemeData(primarySwatch: Colors.blue), ); } } class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin { late AnimationController _animationController; late Animation<Offset> _positionAnimation; late Animation<double> _scaleAnimation; Offset? _tapOffset; bool _isDialogVisible = false; @override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), ); } @override void dispose() { _animationController.dispose(); super.dispose(); } void _showAnimatedDialog(TapDownDetails details) { setState(() { _tapOffset = details.globalPosition; _isDialogVisible = true; }); // 计算从点击位置到屏幕中心的偏移量 final screenSize = MediaQuery.of(context).size; final centerOffset = Offset(screenSize.width / 2, screenSize.height / 2); final startOffset = _tapOffset! - centerOffset; // 初始化动画 _positionAnimation = Tween<Offset>( begin: startOffset / screenSize.width, // 归一化处理偏移 end: Offset.zero, ).animate(CurvedAnimation( parent: _animationController, curve: Curves.easeOutCubic, )); _scaleAnimation = Tween<double>( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _animationController, curve: Curves.easeOutCubic, )); _animationController.forward(); } void _hideAnimatedDialog() { _animationController.reverse().then((_) { setState(() { _isDialogVisible = false; }); }); } @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ // 空白区域的点击触发 GestureDetector( onTapDown: _showAnimatedDialog, child: Container( color: Colors.grey[100], width: double.infinity, height: double.infinity, ), ), // 带动画的自定义弹窗 if (_isDialogVisible) AnimatedBuilder( animation: _animationController, builder: (context, child) { return Transform.translate( offset: _positionAnimation.value * MediaQuery.of(context).size.width, child: Transform.scale( scale: _scaleAnimation.value, child: child, ), ); }, child: Center( child: Container( width: 300, padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.2), blurRadius: 10, spreadRadius: 2, ), ], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( "自定义弹窗", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), const Text("这是从点击位置展开的弹窗,和Container Transform效果类似"), const SizedBox(height: 20), ElevatedButton( onPressed: _hideAnimatedDialog, child: const Text("关闭"), ), ], ), ), ), ), ], ), ); } }
关键细节说明
- 点击位置获取:通过
onTapDown的TapDownDetails拿到全局点击坐标,避免onTap延迟导致的位置偏差。 - 动画组合:同时使用
Transform.translate和Transform.scale实现从点击点到屏幕中心的位移+缩放过渡,曲线用Curves.easeOutCubic让动画更自然。 - 自定义弹窗:因为系统
AlertDialog的动画逻辑封装较深,这里用自定义容器模拟其样式,方便完全控制动画过程。 - 动画生命周期:弹窗关闭时先执行反向动画,动画结束后再隐藏弹窗,保证过渡的完整性。
内容的提问来源于stack exchange,提问作者sub
相关产品推荐
相关产品推荐

