Flutter与OpenHarmony打卡动画效果组件

前言

动画效果是提升应用体验的重要手段。在打卡工具类应用中,恰当的动画可以增强操作反馈、引导用户注意力、传达情感和成就感。本文将详细介绍如何在Flutter和OpenHarmony平台上实现各种实用的动画效果组件。

动画设计需要考虑流畅性、适度性和目的性。过多或过长的动画会影响操作效率,而恰到好处的动画能够让应用更加生动有趣。我们将实现打卡成功动画、数字变化动画、列表动画等常用效果。

Flutter动画效果实现

首先实现打卡成功的庆祝动画:

dart 复制代码
class CheckInCelebration extends StatefulWidget {
  final VoidCallback? onComplete;

  const CheckInCelebration({Key? key, this.onComplete}) : super(key: key);

  @override
  State<CheckInCelebration> createState() => _CheckInCelebrationState();
}

class _CheckInCelebrationState extends State<CheckInCelebration>
    with TickerProviderStateMixin {
  late AnimationController _scaleController;
  late AnimationController _particleController;
  late Animation<double> _scaleAnimation;

  @override
  void initState() {
    super.initState();
    _scaleController = AnimationController(
      duration: const Duration(milliseconds: 600),
      vsync: this,
    );
    _particleController = AnimationController(
      duration: const Duration(milliseconds: 1000),
      vsync: this,
    );
    
    _scaleAnimation = TweenSequence<double>([
      TweenSequenceItem(tween: Tween(begin: 0.0, end: 1.3), weight: 50),
      TweenSequenceItem(tween: Tween(begin: 1.3, end: 1.0), weight: 50),
    ]).animate(CurvedAnimation(parent: _scaleController, curve: Curves.easeOut));
    
    _scaleController.forward();
    _particleController.forward().then((_) => widget.onComplete?.call());
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      alignment: Alignment.center,
      children: [
        // 粒子效果
        ...List.generate(12, (index) => _buildParticle(index)),
        // 中心图标
        ScaleTransition(
          scale: _scaleAnimation,
          child: Container(
            width: 80,
            height: 80,
            decoration: const BoxDecoration(
              color: Colors.green,
              shape: BoxShape.circle,
            ),
            child: const Icon(Icons.check, color: Colors.white, size: 48),
          ),
        ),
      ],
    );
  }

  Widget _buildParticle(int index) {
    final angle = index * (360 / 12) * (pi / 180);
    return AnimatedBuilder(
      animation: _particleController,
      builder: (context, child) {
        final distance = 60 * _particleController.value;
        final opacity = 1 - _particleController.value;
        return Transform.translate(
          offset: Offset(cos(angle) * distance, sin(angle) * distance),
          child: Opacity(
            opacity: opacity,
            child: Container(
              width: 8,
              height: 8,
              decoration: BoxDecoration(
                color: Colors.orange,
                shape: BoxShape.circle,
              ),
            ),
          ),
        );
      },
    );
  }

  @override
  void dispose() {
    _scaleController.dispose();
    _particleController.dispose();
    super.dispose();
  }
}

打卡成功动画包含中心图标的弹性缩放和周围的粒子扩散效果。TweenSequence实现先放大后回弹的效果,12个粒子从中心向外扩散并逐渐消失。这种庆祝动画能够有效增强用户的成就感。

实现数字滚动动画:

dart 复制代码
class AnimatedNumber extends StatefulWidget {
  final int value;
  final TextStyle? style;
  final Duration duration;

  const AnimatedNumber({
    Key? key,
    required this.value,
    this.style,
    this.duration = const Duration(milliseconds: 500),
  }) : super(key: key);

  @override
  State<AnimatedNumber> createState() => _AnimatedNumberState();
}

class _AnimatedNumberState extends State<AnimatedNumber>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<int> _animation;
  int _previousValue = 0;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(duration: widget.duration, vsync: this);
    _updateAnimation();
    _controller.forward();
  }

  @override
  void didUpdateWidget(AnimatedNumber oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.value != widget.value) {
      _previousValue = oldWidget.value;
      _updateAnimation();
      _controller.forward(from: 0);
    }
  }

  void _updateAnimation() {
    _animation = IntTween(begin: _previousValue, end: widget.value).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeOut),
    );
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animation,
      builder: (context, child) {
        return Text(
          '${_animation.value}',
          style: widget.style ?? const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
        );
      },
    );
  }

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

数字滚动动画在数值变化时平滑过渡。IntTween在整数之间进行插值,didUpdateWidget检测数值变化并重新启动动画。这种效果常用于统计数字的展示,让数据变化更加生动。

OpenHarmony动画效果实现

在鸿蒙系统中实现打卡成功动画:

typescript 复制代码
@Component
struct CheckInCelebration {
  @State scale: number = 0
  @State particleDistance: number = 0
  @State particleOpacity: number = 1
  private onComplete: () => void = () => {}

  aboutToAppear() {
    // 中心图标动画
    animateTo({
      duration: 300,
      curve: Curve.EaseOut
    }, () => {
      this.scale = 1.3
    })
    
    setTimeout(() => {
      animateTo({
        duration: 300,
        curve: Curve.EaseOut
      }, () => {
        this.scale = 1
      })
    }, 300)
    
    // 粒子动画
    animateTo({
      duration: 1000,
      curve: Curve.EaseOut,
      onFinish: () => this.onComplete()
    }, () => {
      this.particleDistance = 60
      this.particleOpacity = 0
    })
  }

  build() {
    Stack() {
      // 粒子
      ForEach(Array.from({ length: 12 }), (_, index: number) => {
        this.Particle(index)
      })
      
      // 中心图标
      Column() {
        Image($r('app.media.check'))
          .width(48)
          .height(48)
          .fillColor(Color.White)
      }
      .width(80)
      .height(80)
      .borderRadius(40)
      .backgroundColor('#4CAF50')
      .justifyContent(FlexAlign.Center)
      .scale({ x: this.scale, y: this.scale })
    }
  }

  @Builder
  Particle(index: number) {
    Column()
      .width(8)
      .height(8)
      .borderRadius(4)
      .backgroundColor('#FF9800')
      .opacity(this.particleOpacity)
      .translate({
        x: Math.cos(index * 30 * Math.PI / 180) * this.particleDistance,
        y: Math.sin(index * 30 * Math.PI / 180) * this.particleDistance
      })
  }
}

鸿蒙的庆祝动画使用animateTo实现多个属性的同时动画。setTimeout控制动画的时序,先放大后回弹。粒子通过translate属性实现位移,opacity实现淡出效果。

实现列表项入场动画:

typescript 复制代码
@Component
struct AnimatedListItem {
  @Prop index: number = 0
  @State offsetY: number = 50
  @State opacity: number = 0
  @BuilderParam content: () => void

  aboutToAppear() {
    setTimeout(() => {
      animateTo({
        duration: 300,
        curve: Curve.EaseOut
      }, () => {
        this.offsetY = 0
        this.opacity = 1
      })
    }, this.index * 50)  // 延迟入场
  }

  build() {
    Column() {
      this.content()
    }
    .offset({ y: this.offsetY })
    .opacity(this.opacity)
  }
}

列表项入场动画让列表加载更加生动。每个列表项根据索引延迟入场,形成依次出现的效果。从下方滑入配合淡入,视觉效果流畅自然。

页面转场动画

Flutter中实现自定义页面转场:

dart 复制代码
class SlidePageRoute<T> extends PageRouteBuilder<T> {
  final Widget page;

  SlidePageRoute({required this.page})
      : super(
          pageBuilder: (context, animation, secondaryAnimation) => page,
          transitionsBuilder: (context, animation, secondaryAnimation, child) {
            const begin = Offset(1.0, 0.0);
            const end = Offset.zero;
            const curve = Curves.easeInOut;
            
            var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
            var offsetAnimation = animation.drive(tween);
            
            return SlideTransition(
              position: offsetAnimation,
              child: child,
            );
          },
          transitionDuration: const Duration(milliseconds: 300),
        );
}

// 使用
Navigator.push(context, SlidePageRoute(page: DetailPage()));

自定义页面转场让页面切换更加流畅。SlideTransition实现从右侧滑入的效果,CurveTween添加缓动曲线。这种转场动画比默认的更加现代和流畅。

按钮点击动画

实现按钮点击的缩放反馈:

dart 复制代码
class AnimatedButton extends StatefulWidget {
  final Widget child;
  final VoidCallback onPressed;

  const AnimatedButton({
    Key? key,
    required this.child,
    required this.onPressed,
  }) : super(key: key);

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

class _AnimatedButtonState extends State<AnimatedButton>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _scaleAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: 100),
      vsync: this,
    );
    _scaleAnimation = Tween<double>(begin: 1.0, end: 0.95).animate(_controller);
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTapDown: (_) => _controller.forward(),
      onTapUp: (_) {
        _controller.reverse();
        widget.onPressed();
      },
      onTapCancel: () => _controller.reverse(),
      child: ScaleTransition(
        scale: _scaleAnimation,
        child: widget.child,
      ),
    );
  }

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

按钮点击动画在按下时轻微缩小,松开时恢复。这种微妙的反馈让用户确认操作已被接收,提升了交互的确定性。100毫秒的动画时长足够快速又能被感知。

呼吸灯效果

实现提醒的呼吸灯动画:

dart 复制代码
class BreathingLight extends StatefulWidget {
  final Color color;
  final double size;

  const BreathingLight({
    Key? key,
    this.color = Colors.red,
    this.size = 12,
  }) : super(key: key);

  @override
  State<BreathingLight> createState() => _BreathingLightState();
}

class _BreathingLightState extends State<BreathingLight>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: 1500),
      vsync: this,
    )..repeat(reverse: true);
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return Container(
          width: widget.size,
          height: widget.size,
          decoration: BoxDecoration(
            shape: BoxShape.circle,
            color: widget.color,
            boxShadow: [
              BoxShadow(
                color: widget.color.withOpacity(0.5 * _controller.value),
                blurRadius: 10 * _controller.value,
                spreadRadius: 2 * _controller.value,
              ),
            ],
          ),
        );
      },
    );
  }

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

呼吸灯效果通过阴影的扩散和收缩模拟呼吸节奏。repeat(reverse: true)让动画来回播放,形成持续的呼吸效果。这种动画常用于提醒用户注意某个元素。

总结

本文详细介绍了在Flutter和OpenHarmony平台上实现动画效果组件的完整方案。打卡成功动画增强成就感,数字滚动让数据变化生动,列表入场动画提升加载体验,按钮点击动画确认操作反馈。两个平台都提供了强大的动画API,通过合理的动画设计,可以显著提升应用的用户体验。

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

相关推荐
kirk_wang8 小时前
Flutter艺术探索-Flutter状态管理方案对比:Provider vs Riverpod vs BLoC vs GetX
flutter·移动开发·flutter教程·移动开发教程
wqwqweee9 小时前
Flutter for OpenHarmony 看书管理记录App实战:搜索功能实现
开发语言·javascript·python·flutter·harmonyos
zilikew9 小时前
Flutter框架跨平台鸿蒙开发——书籍推荐APP的开发流程
flutter·华为·harmonyos·鸿蒙
wypywyp9 小时前
2.虚拟机一直显示黑屏,无法打开,可能是分配的硬盘空间不够
linux·运维·服务器
zilikew9 小时前
Flutter框架跨平台鸿蒙开发——桌面宠物APP的开发流程
学习·flutter·harmonyos·鸿蒙·宠物
ITUnicorn10 小时前
Flutter调用HarmonyOS6原生功能:实现智感握持
flutter·华为·harmonyos·harmonyos6·智感握持
2601_9495758610 小时前
Flutter for OpenHarmony二手物品置换App实战 - 商品卡片实现
android·flutter
Fᴏʀ ʏ꯭ᴏ꯭ᴜ꯭.11 小时前
Haproxy会话保持:基于Cookie优化
运维·负载均衡
学习3人组12 小时前
Docker 容器内文件↔本地双向复制备份
运维·docker·容器
时光慢煮12 小时前
基于 Flutter × OpenHarmony 的文件管家 - 构建常用文件夹区域
flutter·华为·开源·openharmony