如何在Flutter中实现Dribbble展示的模板画廊动画?
Flutter实现模板画廊卡片入场动画方案
核心思路
目标动画的核心特点是卡片从屏幕下方渐显+向上滑入,且按顺序延迟入场。AnimatedList默认侧滑效果不匹配,因此需要通过自定义动画组合实现:
- 卡片初始状态为完全透明+偏移至屏幕下方
- 为每个卡片单独配置动画控制器,通过延迟启动实现序列入场
- 组合偏移动画与透明度动画,搭配自然的曲线过渡
完整Demo代码
import 'package:flutter/material.dart'; void main() => runApp(const TemplateGalleryApp()); class TemplateGalleryApp extends StatelessWidget { const TemplateGalleryApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Template Gallery', theme: ThemeData(primarySwatch: Colors.blue), home: const GalleryScreen(), ); } } class GalleryScreen extends StatefulWidget { const GalleryScreen({super.key}); @override State<GalleryScreen> createState() => _GalleryScreenState(); } class _GalleryScreenState extends State<GalleryScreen> with TickerProviderStateMixin { // 模拟模板数据 final List<String> _templates = List.generate(6, (index) => '模板 ${index + 1}'); // 存储每个卡片的动画控制器 late List<AnimationController> _animationControllers; // 卡片偏移动画 late List<Animation<Offset>> _offsetAnimations; // 卡片透明度动画 late List<Animation<double>> _opacityAnimations; @override void initState() { super.initState(); _initAnimations(); // 依次启动动画,每个卡片延迟200ms for (int i = 0; i < _templates.length; i++) { Future.delayed(Duration(milliseconds: i * 200), () { _animationControllers[i].forward(); }); } } void _initAnimations() { _animationControllers = List.generate( _templates.length, (index) => AnimationController( duration: const Duration(milliseconds: 800), vsync: this, ), ); _offsetAnimations = List.generate( _templates.length, (index) => Tween<Offset>( begin: const Offset(0, 1), // 初始从屏幕下方偏移 end: Offset.zero, ).animate( CurvedAnimation( parent: _animationControllers[index], curve: Curves.easeOutCubic, ), ), ); _opacityAnimations = List.generate( _templates.length, (index) => Tween<double>( begin: 0.0, end: 1.0, ).animate( CurvedAnimation( parent: _animationControllers[index], curve: const Interval(0.2, 1.0, curve: Curves.easeInOut), ), ), ); } @override void dispose() { // 销毁所有动画控制器,避免内存泄漏 for (var controller in _animationControllers) { controller.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('模板画廊')), body: Padding( padding: const EdgeInsets.all(16.0), child: GridView.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 16, mainAxisSpacing: 16, childAspectRatio: 0.75, ), itemCount: _templates.length, itemBuilder: (context, index) { return AnimatedBuilder( animation: _animationControllers[index], builder: (context, child) { return Opacity( opacity: _opacityAnimations[index].value, child: Transform.translate( offset: _offsetAnimations[index].value * MediaQuery.of(context).size.height * 0.3, child: child, ), ); }, child: Container( decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(12), boxShadow: const [ BoxShadow( color: Colors.black12, blurRadius: 8, offset: Offset(0, 4), ), ], ), child: Center( child: Text( _templates[index], style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), ), ), ); }, ), ), ); } }
关键细节调整
- 动画序列:通过
Future.delayed设置递增延迟,实现卡片依次入场的层次感 - 曲线优化:使用
Curves.easeOutCubic让滑动更流畅,透明度动画用Interval让淡入稍晚于滑动开始,更贴近目标效果 - 适配性:基于屏幕高度比例设置偏移量,确保在不同设备上效果一致
- 扩展方向:如果需要更贴近原动画的细节,可在
AnimatedBuilder中添加ScaleTransition实现卡片缩放,或动态调整阴影强度
内容的提问来源于stack exchange,提问作者Fasikaw
相关产品推荐
相关产品推荐

