进阶实战 Flutter for OpenHarmony:AnimatedBuilder 组件实战 - 自定义动画系统

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net


一、场景引入:为什么需要自定义动画?

在移动应用开发中,动画是提升用户体验的重要手段。想象一下这样的场景:你打开一个应用,页面加载时显示一个优雅的加载动画;点击一个按钮,按钮会有一个流畅的缩放反馈;滑动列表,列表项会有一个漂亮的入场动画。这些动画效果让应用感觉更加生动、专业,给用户带来愉悦的使用体验。

这就是为什么我们需要 自定义动画AnimatedBuilder 是 Flutter 提供的动画构建器,它允许我们将动画与 UI 组件分离,实现高度可复用的动画效果,同时保持代码的清晰和可维护性。

📱 1.1 动画的典型应用场景

在现代移动应用中,动画的需求非常广泛:

加载与等待动画:当应用需要加载数据时,显示一个旋转的加载指示器或骨架屏动画,让用户知道应用正在工作,而不是卡住了。

交互反馈动画:当用户点击按钮时,按钮会有一个缩放或涟漪效果;当用户滑动开关时,开关会有一个平滑的过渡动画。这些反馈让用户感受到应用在响应他们的操作。

页面转场动画:当用户从一个页面跳转到另一个页面时,页面之间会有一个流畅的过渡效果,如淡入淡出、滑动、缩放等。

数据可视化动画:当数据发生变化时,图表会有一个平滑的过渡动画,让用户清楚地看到数据的变化趋势。

引导与教学动画:当应用需要引导用户完成某个操作时,可以使用动画来吸引用户的注意力,如高亮某个按钮、显示操作提示等。

1.2 Flutter 动画体系概述

Flutter 提供了完整的动画体系,从简单到复杂:

组件/类 功能描述 适用场景 学习成本
AnimatedContainer 动画容器 简单属性动画
AnimatedOpacity 透明度动画 淡入淡出效果
AnimatedPositioned 位置动画 位置变化动画
AnimatedBuilder 自定义动画构建器 复杂自定义动画
AnimationController 动画控制器 控制动画播放
Tween 值插值器 定义动画范围
CurvedAnimation 曲线动画 自定义动画曲线
Hero 共享元素动画 页面转场
Lottie 复杂矢量动画 AE 动画导出

对于复杂自定义动画 场景,AnimatedBuilder 是最佳选择:

高度灵活:你可以完全控制动画的每一个细节,实现任何你想要的动画效果。

性能优秀:AnimatedBuilder 只在动画值变化时重建子组件,不会造成不必要的性能开销。

代码复用:可以将动画逻辑封装成独立的组件,在多个地方复用。

与 Flutter 完美集成:可以与 AnimationController、Tween、Curve 等 Flutter 动画组件无缝配合。

1.3 动画核心概念

理解 Flutter 动画的核心概念是掌握自定义动画的关键:

AnimationController:动画控制器,控制动画的播放、暂停、停止、反向等。它产生一个 0.0 到 1.0 之间的值,表示动画的进度。

Animation:动画对象,它是一个可以在一段时间内产生变化的值。AnimationController 本身就是一个 Animation。

Tween:补间动画,定义动画的起始值和结束值。它将 AnimationController 的 0.0-1.0 值映射到你需要的值范围。

Curve:动画曲线,定义动画的变化速率。Flutter 提供了丰富的预设曲线,如 Curves.easeIn、Curves.bounceOut 等。

AnimatedBuilder:动画构建器,监听 Animation 的变化,在值变化时重建子组件。

Ticker:帧回调机制,每帧调用一次回调函数,用于驱动动画。AnimationController 内部使用 Ticker 来驱动动画。


二、技术架构设计

在正式编写代码之前,我们需要设计一个清晰的架构。良好的架构设计可以让代码更易于理解、维护和扩展。

🏛️ 2.1 动画架构设计

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    动画驱动层                                │
│  ┌─────────────────────────────────────────────────────┐    │
│  │           AnimationController                        │    │
│  │  - 控制动画播放、暂停、停止                           │    │
│  │  - 产生 0.0 - 1.0 的动画进度值                        │    │
│  └─────────────────────────────────────────────────────┘    │
│                              │                               │
│                              ▼                               │
│  ┌─────────────────────────────────────────────────────┐    │
│  │           Tween / CurvedAnimation                    │    │
│  │  - 将进度值映射到目标值范围                           │    │
│  │  - 应用动画曲线                                       │    │
│  └─────────────────────────────────────────────────────┘    │
│                              │                               │
│                              ▼                               │
│  ┌─────────────────────────────────────────────────────┐    │
│  │           Animation<double>                          │    │
│  │  - 最终的动画值                                       │    │
│  └─────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────┘
                               │
                               │ 监听
                               ▼
┌─────────────────────────────────────────────────────────────┐
│                    动画展示层                                │
│  ┌─────────────────────────────────────────────────────┐    │
│  │           AnimatedBuilder                            │    │
│  │  - 监听 Animation 的变化                              │    │
│  │  - 在值变化时重建子组件                               │    │
│  └─────────────────────────────────────────────────────┘    │
│                              │                               │
│                              ▼                               │
│  ┌─────────────────────────────────────────────────────┐    │
│  │           Widget Tree                                │    │
│  │  - 根据动画值构建 UI                                  │    │
│  └─────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────┘

🎯 2.2 动画生命周期

复制代码
创建 AnimationController
      │
      ▼
创建 Tween 和 Curve
      │
      ▼
创建 Animation 对象
      │
      ▼
使用 AnimatedBuilder 监听
      │
      ├──▶ controller.forward()  正向播放
      │
      ├──▶ controller.reverse()  反向播放
      │
      ├──▶ controller.repeat()   循环播放
      │
      └──▶ controller.stop()     停止播放
            │
            ▼
      dispose() 释放资源

📐 2.3 常用动画曲线

Flutter 提供了丰富的动画曲线:

曲线名称 效果描述 典型用途
Curves.linear 线性变化 匀速动画
Curves.easeIn 开始慢,结束快 入场动画
Curves.easeOut 开始快,结束慢 退场动画
Curves.easeInOut 两头慢,中间快 通用动画
Curves.bounceIn 弹跳进入 活泼效果
Curves.bounceOut 弹跳退出 活泼效果
Curves.elasticIn 弹性进入 弹性效果
Curves.elasticOut 弹性退出 弹性效果
Curves.fastOutSlowIn 快出慢进 Material 动画
Curves.slowMiddle 中间慢 特殊效果

三、核心功能实现

🔧 3.1 基础动画控制器

dart 复制代码
import 'package:flutter/material.dart';

class BasicAnimationPage extends StatefulWidget {
  const BasicAnimationPage({super.key});

  @override
  State<BasicAnimationPage> createState() => _BasicAnimationPageState();
}

class _BasicAnimationPageState extends State<BasicAnimationPage> 
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;
  
  @override
  void initState() {
    super.initState();
    
    // 创建动画控制器
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );
    
    // 创建动画(带曲线)
    _animation = CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    );
    
    // 创建补间动画
    _animation = Tween<double>(begin: 0, end: 1).animate(_animation);
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('基础动画')),
      body: Center(
        child: AnimatedBuilder(
          animation: _animation,
          builder: (context, child) {
            return Transform.scale(
              scale: _animation.value,
              child: Container(
                width: 200,
                height: 200,
                color: Colors.blue,
                child: const Center(
                  child: Text(
                    '动画方块',
                    style: TextStyle(color: Colors.white, fontSize: 20),
                  ),
                ),
              ),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          if (_controller.status == AnimationStatus.completed) {
            _controller.reverse();
          } else {
            _controller.forward();
          }
        },
        child: const Icon(Icons.play_arrow),
      ),
    );
  }
}

🎨 3.2 多属性动画

dart 复制代码
/// 多属性动画示例
class MultiPropertyAnimation extends StatefulWidget {
  const MultiPropertyAnimation({super.key});

  @override
  State<MultiPropertyAnimation> createState() => _MultiPropertyAnimationState();
}

class _MultiPropertyAnimationState extends State<MultiPropertyAnimation>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  
  // 多个动画属性
  late Animation<double> _scaleAnimation;
  late Animation<double> _rotationAnimation;
  late Animation<double> _opacityAnimation;
  late Animation<Offset> _positionAnimation;
  late Animation<Color?> _colorAnimation;
  
  @override
  void initState() {
    super.initState();
    
    _controller = AnimationController(
      duration: const Duration(milliseconds: 1500),
      vsync: this,
    );
    
    // 缩放动画
    _scaleAnimation = Tween<double>(begin: 0.5, end: 1.0).animate(
      CurvedAnimation(parent: _controller, curve: Curves.elasticOut),
    );
    
    // 旋转动画
    _rotationAnimation = Tween<double>(begin: 0, end: 2 * 3.14159).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
    );
    
    // 透明度动画
    _opacityAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeIn),
    );
    
    // 位置动画
    _positionAnimation = Tween<Offset>(
      begin: const Offset(0, 0.5),
      end: Offset.zero,
    ).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic),
    );
    
    // 颜色动画
    _colorAnimation = ColorTween(
      begin: Colors.blue,
      end: Colors.purple,
    ).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
    );
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('多属性动画')),
      body: Center(
        child: AnimatedBuilder(
          animation: _controller,
          builder: (context, child) {
            return SlideTransition(
              position: _positionAnimation,
              child: Opacity(
                opacity: _opacityAnimation.value,
                child: Transform.scale(
                  scale: _scaleAnimation.value,
                  child: Transform.rotate(
                    angle: _rotationAnimation.value,
                    child: Container(
                      width: 150,
                      height: 150,
                      decoration: BoxDecoration(
                        color: _colorAnimation.value,
                        borderRadius: BorderRadius.circular(16),
                        boxShadow: [
                          BoxShadow(
                            color: (_colorAnimation.value ?? Colors.blue)
                                .withOpacity(0.4),
                            blurRadius: 20,
                            spreadRadius: 5,
                          ),
                        ],
                      ),
                      child: const Center(
                        child: Text(
                          '组合动画',
                          style: TextStyle(color: Colors.white, fontSize: 18),
                        ),
                      ),
                    ),
                  ),
                ),
              ),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          _controller.forward(from: 0);
        },
        child: const Icon(Icons.refresh),
      ),
    );
  }
}

🔄 3.3 循环动画

dart 复制代码
/// 循环加载动画
class LoadingAnimation extends StatefulWidget {
  final double size;
  final Color color;
  
  const LoadingAnimation({
    super.key,
    this.size = 50,
    this.color = Colors.blue,
  });

  @override
  State<LoadingAnimation> createState() => _LoadingAnimationState();
}

class _LoadingAnimationState extends State<LoadingAnimation>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 1),
      vsync: this,
    )..repeat();
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return Transform.rotate(
          angle: _controller.value * 2 * 3.14159,
          child: SizedBox(
            width: widget.size,
            height: widget.size,
            child: CircularProgressIndicator(
              strokeWidth: 3,
              valueColor: AlwaysStoppedAnimation(widget.color),
            ),
          ),
        );
      },
    );
  }
}

/// 脉冲动画
class PulseAnimation extends StatefulWidget {
  final Widget child;
  final Duration duration;
  
  const PulseAnimation({
    super.key,
    required this.child,
    this.duration = const Duration(milliseconds: 1000),
  });

  @override
  State<PulseAnimation> createState() => _PulseAnimationState();
}

class _PulseAnimationState extends State<PulseAnimation>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;
  
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: widget.duration,
      vsync: this,
    )..repeat(reverse: true);
    
    _animation = Tween<double>(begin: 1.0, end: 1.1).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
    );
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animation,
      builder: (context, child) {
        return Transform.scale(
          scale: _animation.value,
          child: widget.child,
        );
      },
      child: widget.child,
    );
  }
}

🎭 3.4 交错动画

dart 复制代码
/// 交错动画示例
class StaggeredAnimationPage extends StatefulWidget {
  const StaggeredAnimationPage({super.key});

  @override
  State<StaggeredAnimationPage> createState() => _StaggeredAnimationPageState();
}

class _StaggeredAnimationPageState extends State<StaggeredAnimationPage>
    with TickerProviderStateMixin {
  late AnimationController _controller;
  
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('交错动画')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: List.generate(5, (index) {
            return AnimatedBuilder(
              animation: _controller,
              builder: (context, child) {
                // 计算每个元素的延迟
                final delay = index * 0.1;
                final start = delay.clamp(0.0, 1.0);
                final end = (delay + 0.5).clamp(0.0, 1.0);
                
                // 创建区间动画
                final intervalAnimation = Tween<double>(begin: 0, end: 1).animate(
                  CurvedAnimation(
                    parent: _controller,
                    curve: Interval(start, end, curve: Curves.easeOut),
                  ),
                );
                
                return SlideTransition(
                  position: Tween<Offset>(
                    begin: const Offset(1, 0),
                    end: Offset.zero,
                  ).animate(intervalAnimation),
                  child: FadeTransition(
                    opacity: intervalAnimation,
                    child: Container(
                      margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 20),
                      padding: const EdgeInsets.all(16),
                      decoration: BoxDecoration(
                        color: Colors.primaries[index % Colors.primaries.length],
                        borderRadius: BorderRadius.circular(8),
                      ),
                      child: ListTile(
                        title: Text(
                          '列表项 ${index + 1}',
                          style: const TextStyle(color: Colors.white),
                        ),
                        subtitle: Text(
                          '这是第 ${index + 1} 个列表项',
                          style: TextStyle(color: Colors.white.withOpacity(0.8)),
                        ),
                      ),
                    ),
                  ),
                );
              },
            );
          }),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          _controller.forward(from: 0);
        },
        child: const Icon(Icons.play_arrow),
      ),
    );
  }
}

四、完整应用示例

下面是一个完整的动画展示应用:

dart 复制代码
import 'package:flutter/material.dart';

void main() {
  runApp(const AnimationApp());
}

class AnimationApp extends StatelessWidget {
  const AnimationApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '动画展示',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const AnimationShowcasePage(),
    );
  }
}

class AnimationShowcasePage extends StatefulWidget {
  const AnimationShowcasePage({super.key});

  @override
  State<AnimationShowcasePage> createState() => _AnimationShowcasePageState();
}

class _AnimationShowcasePageState extends State<AnimationShowcasePage>
    with TickerProviderStateMixin {
  late AnimationController _rotateController;
  late AnimationController _scaleController;
  late AnimationController _slideController;
  late AnimationController _colorController;
  late AnimationController _staggeredController;
  
  late Animation<double> _rotateAnimation;
  late Animation<double> _scaleAnimation;
  late Animation<Offset> _slideAnimation;
  late Animation<Color?> _colorAnimation;
  
  @override
  void initState() {
    super.initState();
    
    _rotateController = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    )..repeat();
    
    _scaleController = AnimationController(
      duration: const Duration(milliseconds: 800),
      vsync: this,
    )..repeat(reverse: true);
    
    _slideController = AnimationController(
      duration: const Duration(milliseconds: 1000),
      vsync: this,
    );
    
    _colorController = AnimationController(
      duration: const Duration(seconds: 3),
      vsync: this,
    )..repeat(reverse: true);
    
    _staggeredController = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );
    
    _rotateAnimation = Tween<double>(begin: 0, end: 2 * 3.14159).animate(
      CurvedAnimation(parent: _rotateController, curve: Curves.linear),
    );
    
    _scaleAnimation = Tween<double>(begin: 0.8, end: 1.2).animate(
      CurvedAnimation(parent: _scaleController, curve: Curves.easeInOut),
    );
    
    _slideAnimation = Tween<Offset>(
      begin: const Offset(-1, 0),
      end: const Offset(1, 0),
    ).animate(
      CurvedAnimation(parent: _slideController, curve: Curves.easeInOut),
    );
    
    _colorAnimation = ColorTween(
      begin: Colors.blue,
      end: Colors.purple,
    ).animate(_colorController);
  }
  
  @override
  void dispose() {
    _rotateController.dispose();
    _scaleController.dispose();
    _slideController.dispose();
    _colorController.dispose();
    _staggeredController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('🎨 动画展示'),
        centerTitle: true,
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _buildSection('旋转动画', _buildRotateAnimation()),
            _buildSection('缩放动画', _buildScaleAnimation()),
            _buildSection('滑动动画', _buildSlideAnimation()),
            _buildSection('颜色动画', _buildColorAnimation()),
            _buildSection('交错动画', _buildStaggeredAnimation()),
          ],
        ),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            heroTag: 'play',
            onPressed: () {
              _slideController.forward();
              _staggeredController.forward(from: 0);
            },
            child: const Icon(Icons.play_arrow),
          ),
          const SizedBox(width: 8),
          FloatingActionButton(
            heroTag: 'reset',
            onPressed: () {
              _slideController.reset();
              _staggeredController.reset();
            },
            child: const Icon(Icons.refresh),
          ),
        ],
      ),
    );
  }
  
  Widget _buildSection(String title, Widget child) {
    return Container(
      margin: const EdgeInsets.only(bottom: 24),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            title,
            style: const TextStyle(
              fontSize: 18,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 12),
          Container(
            height: 120,
            decoration: BoxDecoration(
              color: Colors.grey.shade100,
              borderRadius: BorderRadius.circular(12),
            ),
            child: Center(child: child),
          ),
        ],
      ),
    );
  }
  
  Widget _buildRotateAnimation() {
    return AnimatedBuilder(
      animation: _rotateAnimation,
      builder: (context, child) {
        return Transform.rotate(
          angle: _rotateAnimation.value,
          child: Container(
            width: 60,
            height: 60,
            decoration: BoxDecoration(
              gradient: LinearGradient(
                colors: [Colors.blue, Colors.purple],
              ),
              borderRadius: BorderRadius.circular(12),
            ),
            child: const Icon(Icons.star, color: Colors.white, size: 32),
          ),
        );
      },
    );
  }
  
  Widget _buildScaleAnimation() {
    return AnimatedBuilder(
      animation: _scaleAnimation,
      builder: (context, child) {
        return Transform.scale(
          scale: _scaleAnimation.value,
          child: Container(
            width: 60,
            height: 60,
            decoration: const BoxDecoration(
              color: Colors.orange,
              shape: BoxShape.circle,
            ),
            child: const Icon(Icons.favorite, color: Colors.white, size: 32),
          ),
        );
      },
    );
  }
  
  Widget _buildSlideAnimation() {
    return AnimatedBuilder(
      animation: _slideAnimation,
      builder: (context, child) {
        return SlideTransition(
          position: _slideAnimation,
          child: Container(
            width: 60,
            height: 60,
            decoration: BoxDecoration(
              color: Colors.green,
              borderRadius: BorderRadius.circular(30),
            ),
            child: const Icon(Icons.arrow_forward, color: Colors.white, size: 32),
          ),
        );
      },
    );
  }
  
  Widget _buildColorAnimation() {
    return AnimatedBuilder(
      animation: _colorAnimation,
      builder: (context, child) {
        return Container(
          width: 80,
          height: 80,
          decoration: BoxDecoration(
            color: _colorAnimation.value,
            borderRadius: BorderRadius.circular(16),
            boxShadow: [
              BoxShadow(
                color: (_colorAnimation.value ?? Colors.blue).withOpacity(0.5),
                blurRadius: 15,
                spreadRadius: 3,
              ),
            ],
          ),
          child: const Icon(Icons.palette, color: Colors.white, size: 40),
        );
      },
    );
  }
  
  Widget _buildStaggeredAnimation() {
    return SizedBox(
      height: 80,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: List.generate(5, (index) {
          return AnimatedBuilder(
            animation: _staggeredController,
            builder: (context, child) {
              final delay = (index * 0.15).clamp(0.0, 1.0);
              final end = (delay + 0.5).clamp(0.0, 1.0);
              final interval = Interval(delay, end, curve: Curves.easeOut);
              final animation = Tween<double>(begin: 0, end: 1).animate(
                CurvedAnimation(parent: _staggeredController, curve: interval),
              );
              
              return Transform.scale(
                scale: animation.value,
                child: Opacity(
                  opacity: animation.value,
                  child: Container(
                    width: 40,
                    height: 40,
                    decoration: BoxDecoration(
                      color: Colors.primaries[index % Colors.primaries.length],
                      shape: BoxShape.circle,
                    ),
                  ),
                ),
              );
            },
          );
        }),
      ),
    );
  }
}

五、进阶动画技巧

🌟 5.1 自定义动画曲线

dart 复制代码
/// 自定义弹跳曲线
class BounceCurve extends Curve {
  final int bounces;
  final double elasticity;
  
  const BounceCurve({
    this.bounces = 3,
    this.elasticity = 0.5,
  });
  
  @override
  double transformInternal(double t) {
    if (t == 0 || t == 1) return t;
    
    final double duration = 1.0 / (bounces + 1);
    double currentTime = 0;
    
    for (int i = 0; i < bounces; i++) {
      final double bounceStart = currentTime;
      final double bounceEnd = currentTime + duration;
      
      if (t >= bounceStart && t < bounceEnd) {
        final double bounceT = (t - bounceStart) / duration;
        final double amplitude = 1.0 - (i * elasticity / bounces);
        return amplitude * (1 - (1 - bounceT) * (1 - bounceT));
      }
      
      currentTime = bounceEnd;
    }
    
    return t;
  }
}

// 使用自定义曲线
_animation = Tween<double>(begin: 0, end: 1).animate(
  CurvedAnimation(
    parent: _controller,
    curve: const BounceCurve(bounces: 4),
  ),
);

🎭 5.2 物理动画

dart 复制代码
/// 弹簧动画
class SpringAnimation extends StatefulWidget {
  const SpringAnimation({super.key});

  @override
  State<SpringAnimation> createState() => _SpringAnimationState();
}

class _SpringAnimationState extends State<SpringAnimation>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;
  
  @override
  void initState() {
    super.initState();
    
    _controller = AnimationController(
      duration: const Duration(milliseconds: 1000),
      vsync: this,
    );
    
    // 使用弹簧描述
    _animation = Tween<double>(begin: 0, end: 1).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Curves.elasticOut,
      ),
    );
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animation,
      builder: (context, child) {
        return Transform.scale(
          scale: _animation.value,
          child: Container(
            width: 100,
            height: 100,
            color: Colors.blue,
            child: const Center(
              child: Text('弹簧', style: TextStyle(color: Colors.white)),
            ),
          ),
        );
      },
    );
  }
}

📊 5.3 数字动画

dart 复制代码
/// 数字滚动动画
class NumberCounterAnimation extends StatefulWidget {
  final int begin;
  final int end;
  final Duration duration;
  final TextStyle? style;
  
  const NumberCounterAnimation({
    super.key,
    required this.begin,
    required this.end,
    this.duration = const Duration(seconds: 1),
    this.style,
  });

  @override
  State<NumberCounterAnimation> createState() => _NumberCounterAnimationState();
}

class _NumberCounterAnimationState extends State<NumberCounterAnimation>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<int> _animation;
  
  @override
  void initState() {
    super.initState();
    
    _controller = AnimationController(
      duration: widget.duration,
      vsync: this,
    );
    
    _animation = IntTween(begin: widget.begin, end: widget.end).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeOut),
    );
    
    _controller.forward();
  }
  
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animation,
      builder: (context, child) {
        return Text(
          _animation.value.toString(),
          style: widget.style ?? const TextStyle(fontSize: 32),
        );
      },
    );
  }
}

六、最佳实践与注意事项

✅ 6.1 性能优化建议

  1. 使用 child 参数:AnimatedBuilder 的 child 参数可以缓存不变的子组件,避免不必要的重建。

  2. 正确释放资源:在 dispose 中释放 AnimationController,避免内存泄漏。

  3. 使用 const 构造函数:对于不变的子组件,使用 const 构造函数。

  4. 避免过度动画:不要在动画中执行耗时操作,如网络请求、复杂计算等。

  5. 合理设置动画时长:动画时长不宜过长或过短,一般 200-500ms 为宜。

⚠️ 6.2 常见问题与解决方案

问题 原因 解决方案
动画不执行 Controller 未启动 调用 forward() 或 repeat()
内存泄漏 未释放 Controller 在 dispose 中调用 dispose()
动画卡顿 主线程阻塞 使用 Isolate 处理耗时操作
状态丢失 Widget 重建 使用 GlobalKey 或状态管理
曲线不生效 Curve 配置错误 检查 CurvedAnimation 配置

📝 6.3 代码规范建议

  1. 分离动画逻辑:将动画逻辑封装成独立的 Widget 或 Mixin。

  2. 使用命名常量:对于动画时长、曲线等,使用命名常量。

  3. 添加注释:复杂的动画逻辑应该添加注释说明。

  4. 错误处理:处理边界情况,如空值、越界等。


七、总结

本文详细介绍了 Flutter 中 AnimatedBuilder 组件的使用方法,从基础概念到高级技巧,帮助你掌握自定义动画的核心能力。

核心要点回顾:

📌 动画基础:理解 AnimationController、Tween、Curve 的概念和用法

📌 AnimatedBuilder 使用:监听动画变化,构建动态 UI

📌 多属性动画:组合多个动画效果,实现复杂动画

📌 交错动画:使用 Interval 实现元素依次入场效果

📌 自定义曲线:实现独特的动画效果

通过本文的学习,你应该能够独立开发各种动画效果,并能够将动画技术应用到更多场景中。


八、参考资料

相关推荐
程序员老刘2 小时前
跨平台开发地图:React Native 0.84 强力发布,Hermes V1 登顶 | 2026年2月
flutter·客户端
松叶似针4 小时前
Flutter三方库适配OpenHarmony【doc_text】— .docx 解析全流程:从 ZIP 解压到 XML 提取
xml·flutter·harmonyos
lqj_本人4 小时前
Flutter三方库适配OpenHarmony【apple_product_name】MethodCallHandler消息处理机制
flutter
西西学代码4 小时前
Flutter---事件处理
flutter
lqj_本人7 小时前
Flutter三方库适配OpenHarmony【apple_product_name】deviceInfo系统API调用
flutter
littlegnal7 小时前
Flutter Android如何延迟加载代码
android·flutter
松叶似针7 小时前
Flutter三方库适配OpenHarmony【doc_text】— onMethodCall 分发与文件路径参数提取
flutter
卢叁8 小时前
Flutter之路由监听器
前端·flutter