引言:动画是提升用户体验的 “魔法棒”

在开源鸿蒙(OpenHarmony)Flutter 应用中,优秀的动画能让界面交互更流畅、更具吸引力 —— 从简单的按钮点击反馈,到复杂的页面切换、数据加载动效,动画直接影响用户对应用的感知。

本文将以 “场景化实战” 为核心,从基础的显式动画、隐式动画,到进阶的自定义动画、动画控制器,再到开源鸿蒙特有的分布式协同动画,用 “原理 + 精简代码 + 性能优化” 的方式,带你打造适配全场景的高质量动画效果。

一、动画基础:隐式动画(简单高效)

隐式动画由 Flutter 自动管理动画控制器和插值,API 简洁,适合简单动画场景(如尺寸变化、颜色渐变)。

1.1 常用隐式动画组件

组件作用核心参数
AnimatedContainer容器属性动画(尺寸、颜色等)durationcurvewidthcolor
AnimatedOpacity透明度动画opacityduration
AnimatedSize尺寸变化动画durationchild
AnimatedPadding内边距动画paddingduration

1.2 实战 1:按钮点击颜色 / 尺寸变化

dart

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

  @override
  State<AnimatedButton> createState() => _AnimatedButtonState();
}

class _AnimatedButtonState extends State<AnimatedButton> {
  bool _isPressed = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTapDown: (_) => setState(() => _isPressed = true),
      onTapUp: (_) => setState(() => _isPressed = false),
      onTapCancel: () => setState(() => _isPressed = false),
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 200), // 动画时长
        curve: Curves.easeInOut, // 动画曲线
        width: _isPressed ? 180 : 200, // 点击时宽度缩小
        height: 60,
        decoration: BoxDecoration(
          color: _isPressed ? Colors.blue[700] : Colors.blue, // 点击时颜色加深
          borderRadius: BorderRadius.circular(30),
          boxShadow: _isPressed ? [] : [
            BoxShadow(
              color: Colors.blue.withOpacity(0.3),
              blurRadius: 10,
              offset: const Offset(0, 4),
            )
          ],
        ),
        child: const Center(
          child: Text(
            '点击有动画',
            style: TextStyle(color: Colors.white, fontSize: 18),
          ),
        ),
      ),
    );
  }
}

1.3 实战 2:页面加载淡入动画

dart

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

  @override
  State<LoadingPage> createState() => _LoadingPageState();
}

class _LoadingPageState extends State<LoadingPage> {
  bool _isLoaded = false;

  @override
  void initState() {
    super.initState();
    // 模拟数据加载(2秒后显示内容)
    Future.delayed(const Duration(seconds: 2), () {
      setState(() => _isLoaded = true);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: _isLoaded
            ? AnimatedOpacity(
                opacity: 1.0, // 加载完成后 opacity 从0变为1
                duration: const Duration(milliseconds: 500),
                child: const Text(
                  '数据加载完成!',
                  style: TextStyle(fontSize: 24),
                ),
              )
            : const CircularProgressIndicator(), // 加载中
      ),
    );
  }
}

关键知识点

  • 隐式动画只需修改 “目标值”,Flutter 自动计算从当前值到目标值的过渡;
  • duration控制动画时长,curve控制动画速度曲线(如Curves.easeCurves.bounceIn);
  • 适合无需手动控制动画进度的场景。

二、动画进阶:显式动画(灵活可控)

当需要手动控制动画的启动、暂停、反向或监听进度时,需使用显式动画,核心是AnimationController

2.1 核心概念

  • AnimationController:控制动画的进度、时长、状态(启动、暂停、反向);
  • Animation:存储动画的当前值,由AnimationController驱动;
  • Curve:动画曲线,修改动画的速度变化;
  • Listener:监听动画值变化,触发 UI 重建;
  • StatusListener:监听动画状态变化(如完成、反向)。

2.2 实战 1:旋转动画(手动控制)

dart

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

  @override
  State<RotateAnimation> createState() => _RotateAnimationState();
}

class _RotateAnimationState extends State<RotateAnimation>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    // 初始化动画控制器(vsync绑定到当前组件,避免动画在后台运行)
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 2), // 动画时长2秒
    );

    // 定义动画:从0到2π(360度),使用曲线
    _animation = Tween<double>(begin: 0, end: 2 * pi).animate(
      CurvedAnimation(parent: _controller, curve: Curves.linear),
    )..addListener(() {
        setState(() {}); // 监听值变化,触发UI重建
      });
  }

  @override
  void dispose() {
    _controller.dispose(); // 释放控制器,避免内存泄漏
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // 旋转的容器
            Transform.rotate(
              angle: _animation.value, // 使用动画当前值
              child: Container(
                width: 100,
                height: 100,
                color: Colors.blue,
              ),
            ),
            const SizedBox(height: 30),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: () => _controller.forward(), // 正向播放
                  child: const Text('开始旋转'),
                ),
                const SizedBox(width: 10),
                ElevatedButton(
                  onPressed: () => _controller.stop(), // 暂停
                  child: const Text('暂停'),
                ),
                const SizedBox(width: 10),
                ElevatedButton(
                  onPressed: () => _controller.reverse(), // 反向播放
                  child: const Text('反向'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

2.3 实战 2:组合动画(多属性同时变化)

dart

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

  @override
  State<ComboAnimation> createState() => _ComboAnimationState();
}

class _ComboAnimationState extends State<ComboAnimation>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _scaleAnimation;
  late Animation<double> _opacityAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 500),
    );

    // 缩放动画:从0.5到1.0
    _scaleAnimation = Tween<double>(begin: 0.5, end: 1.0).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeOut),
    );

    // 透明度动画:从0到1
    _opacityAnimation = Tween<double>(begin: 0, end: 1).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeIn),
    );

    // 自动播放动画
    _controller.forward();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: AnimatedBuilder(
          animation: _controller, // 绑定动画控制器
          builder: (context, child) {
            return Opacity(
              opacity: _opacityAnimation.value,
              child: Transform.scale(
                scale: _scaleAnimation.value,
                child: child, // 避免重复创建子组件,提升性能
              ),
            );
          },
          child: Container(
            width: 200,
            height: 200,
            color: Colors.green,
            child: const Center(
              child: Text(
                '组合动画',
                style: TextStyle(color: Colors.white, fontSize: 24),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

关键知识点

  • AnimatedBuilder用于优化动画性能,避免每次动画值变化时重建整个 Widget 树;
  • 多个动画可共享同一个AnimationController,实现同步播放;
  • 必须在dispose中释放AnimationController,避免内存泄漏。

三、动画高阶:自定义动画与开源鸿蒙分布式协同

3.1 自定义动画曲线

通过Curve自定义动画速度变化,满足特殊交互需求:

dart

// 自定义弹跳曲线
class BounceCurve extends Curve {
  @override
  double transform(double t) {
    // t的范围是0到1
    if (t < 0.5) {
      return 4 * t * t * t; // 前半段加速
    } else {
      return 1 - pow(-2 * t + 2, 3) / 2; // 后半段弹跳
    }
  }
}

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

3.2 实战:开源鸿蒙分布式协同动画

在开源鸿蒙多设备场景下,实现 “设备 A 启动动画,设备 B 同步播放” 的协同效果:

步骤 1:定义分布式动画控制器

dart

class DistributedAnimationController extends GetxController
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  final RxnDouble _animationValue = RxnDouble(0);
  late Worker _worker;

  double? get animationValue => _animationValue.value;

  @override
  void onInit() {
    super.onInit();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 2),
    );

    // 监听本地动画值变化,同步到分布式存储
    _worker = ever(_animationValue, (value) {
      if (value != null) {
        DistributedStoreUtil.putString(
          'animation_value',
          value.toString(),
        );
      }
    });

    // 监听分布式存储变化,同步到本地
    _listenToDistributed();
  }

  // 监听分布式动画值
  Future<void> _listenToDistributed() async {
    while (true) {
      await Future.delayed(const Duration(milliseconds: 50));
      String valueStr = await DistributedStoreUtil.getString('animation_value');
      if (valueStr.isNotEmpty) {
        final value = double.parse(valueStr);
        if (_animationValue.value != value) {
          _animationValue.value = value;
          // 同步本地动画进度
          _controller.value = value;
        }
      }
    }
  }

  // 启动动画
  void startAnimation() => _controller.forward();

  @override
  void onClose() {
    _controller.dispose();
    _worker.dispose();
    super.onClose();
  }
}
步骤 2:在组件中使用协同动画

dart

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

  @override
  Widget build(BuildContext context) {
    final controller = Get.put(DistributedAnimationController());

    return Scaffold(
      appBar: AppBar(title: const Text('分布式协同动画')),
      body: Center(
        child: Obx(
          () => Transform.rotate(
            angle: controller.animationValue ?? 0,
            child: Container(
              width: 100,
              height: 100,
              color: Colors.purple,
            ),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => controller.startAnimation(),
        child: const Icon(Icons.play_arrow),
      ),
    );
  }
}

关键知识点

  • 通过分布式存储同步动画进度值(animationValue);
  • 本地动画变化时同步到分布式存储,其他设备监听存储变化并更新本地动画;
  • 协同动画需控制同步频率(如 50ms 一次),避免网络压力过大。

四、动画性能优化(适配开源鸿蒙轻量设备)

开源鸿蒙轻量设备(如智能手表)资源有限,需针对性优化动画性能。

4.1 避免过度绘制

  • 动画组件尽量简化层级,避免多层嵌套;
  • 使用RepaintBoundary隔离动画区域,防止整个页面重绘:

dart

RepaintBoundary(
  child: AnimatedBuilder(
    animation: _controller,
    builder: (context, child) => Transform.rotate(
      angle: _animation.value,
      child: child,
    ),
    child: Container(width: 100, height: 100, color: Colors.blue),
  ),
);

4.2 减少动画复杂度

  • 避免在动画中执行复杂计算或网络请求;
  • 复杂动画拆分为多个简单动画,分阶段播放;
  • 轻量设备上降低动画帧率(如从 60fps 降至 30fps):

dart

_controller = AnimationController(
  vsync: this,
  duration: const Duration(seconds: 2),
  lowerBound: 0,
  upperBound: 1,
  value: 0,
  animationBehavior: AnimationBehavior.preserve,
);
// 降低帧率(需原生支持,鸿蒙设备可通过配置实现)

4.3 使用硬件加速

Flutter 默认启用硬件加速,确保未手动禁用:

dart

MaterialApp(
  theme: ThemeData(
    pageTransitionsTheme: const PageTransitionsTheme(
      builders: {
        TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(),
      },
    ),
  ),
);

五、常见问题(FAQ)

Q1:动画在开源鸿蒙手表上卡顿怎么办?

A1:1. 简化动画效果(如将旋转 + 缩放改为仅旋转);2. 增加动画时长,降低每秒帧数;3. 使用AnimatedBuilder优化重建范围;4. 避免在动画中更新大量 UI 组件。

Q2:如何实现动画循环播放?

A2:通过AnimationStatusListener监听动画完成状态,重新启动:

dart

_animation.addStatusListener((status) {
  if (status == AnimationStatus.completed) {
    _controller.repeat(); // 循环播放
    // 或 _controller.forward(from: 0); // 重新从开始播放
  }
});

Q3:分布式协同动画延迟怎么办?

A3:1. 减少同步频率(如从 50ms 改为 100ms);2. 使用鸿蒙分布式网络优化(如优先 Wi-Fi 连接);3. 预加载动画资源,避免流转时加载耗时。

结语:用动画打造开源鸿蒙全场景优质体验

动画是提升应用交互体验的关键,从简单的隐式动画到灵活的显式动画,再到开源鸿蒙特有的分布式协同动画,每个场景都需要结合设备特性和用户需求进行设计。

通过本文的实战案例,你已经掌握了不同类型动画的开发技巧和性能优化方法,能够在开源鸿蒙手机、平板、手表等设备上实现流畅、吸引人的动画效果。合理运用动画,能让你的应用在全场景生态中脱颖而出。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐