You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 11:55:26