如何在Flutter中实现示例动图所示的星光闪烁动画效果?
Flutter 星光闪烁动画实现方案

这个效果由多个随机位置、随机大小、随机闪烁周期的发光光点组成,每个光点独立执行「淡入-淡出+轻微缩放」的循环动画,整体叠加后就形成了自然的星光闪烁效果,不需要引入第三方依赖,用Flutter自带的动画和绘制能力即可实现。
1. 定义星光属性模型
首先创建数据类存储单个星光的配置参数,所有参数初始化时随机生成,保证每个星光的运动规律不同:
class Star { // 星光在画布上的坐标 double x; double y; // 星光基础大小 double size; // 单次闪烁动画的周期(毫秒) int duration; // 动画延迟启动时间(毫秒,避免所有星光同步闪烁) int delay; // 当前动画的透明度值 double opacity = 0; // 当前动画的缩放比例 double scale = 1.0; Star({ required this.x, required this.y, required this.size, required this.duration, required this.delay, }); }
2. 初始化动画控制器与星光列表
在State类中混入SingleTickerProviderStateMixin,初始化时生成随机的星光列表,启动循环动画:
import 'dart:math'; import 'dart:ui'; import 'package:flutter/material.dart'; class TwinkleStars extends StatefulWidget { // 星光数量 final int starCount; // 画布大小 final Size size; const TwinkleStars({ super.key, this.starCount = 30, required this.size, }); @override State<TwinkleStars> createState() => _TwinkleStarsState(); } class _TwinkleStarsState extends State<TwinkleStars> with SingleTickerProviderStateMixin { late AnimationController _controller; final List<Star> _stars = []; final Random _random = Random(); @override void initState() { super.initState(); // 初始化动画控制器,16ms刷新一次匹配屏幕帧率 _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 16), )..repeat(); // 生成随机星光数据 for (int i = 0; i < widget.starCount; i++) { _stars.add(Star( x: _random.nextDouble() * widget.size.width, y: _random.nextDouble() * widget.size.height, size: 2 + _random.nextDouble() * 6, duration: 800 + _random.nextInt(1200), delay: _random.nextInt(1000), )); } // 动画每帧更新星光的透明度和缩放值 _controller.addListener(() { final now = DateTime.now().millisecondsSinceEpoch; for (var star in _stars) { // 计算当前动画进度,循环播放 double progress = ((now + star.delay) % star.duration) / star.duration; // 用easeInOut曲线让淡入淡出更自然 if (progress < 0.5) { star.opacity = Curves.easeIn.transform(progress * 2); star.scale = 1 + progress * 0.3; } else { star.opacity = Curves.easeOut.transform((1 - progress) * 2); star.scale = 1 + (1 - progress) * 0.3; } } setState(() {}); }); } @override void dispose() { _controller.dispose(); super.dispose(); }
3. 自定义Painter绘制星光
用CustomPainter绘制所有星光,此处实现四芒星形匹配示例效果,也可简化为带模糊的圆形光点:
@override Widget build(BuildContext context) { return CustomPaint( size: widget.size, painter: StarPainter(stars: _stars), ); } } class StarPainter extends CustomPainter { final List<Star> stars; // 星光画笔,加模糊效果模拟发光 final Paint _starPaint = Paint() ..imageFilter = ImageFilter.blur(sigmaX: 1.2, sigmaY: 1.2) ..color = Colors.white; StarPainter({required this.stars}); @override void paint(Canvas canvas, Size size) { for (var star in stars) { _starPaint.color = Colors.white.withOpacity(star.opacity); // 绘制四芒星光 Path starPath = Path(); double halfSize = star.size * star.scale / 2; // 上下左右四个顶点 starPath.moveTo(star.x, star.y - halfSize); starPath.lineTo(star.x + halfSize * 0.3, star.y - halfSize * 0.3); starPath.lineTo(star.x + halfSize, star.y); starPath.lineTo(star.x + halfSize * 0.3, star.y + halfSize * 0.3); starPath.lineTo(star.x, star.y + halfSize); starPath.lineTo(star.x - halfSize * 0.3, star.y + halfSize * 0.3); starPath.lineTo(star.x - halfSize, star.y); starPath.lineTo(star.x - halfSize * 0.3, star.y - halfSize * 0.3); starPath.close(); canvas.drawPath(starPath, _starPaint); } } @override bool shouldRepaint(covariant StarPainter oldDelegate) => true; }
使用方法
直接在页面中放置TwinkleStars组件,作为背景层放在Stack里即可实现示例效果:
Stack( children: [ // 深色背景层 Container(color: const Color(0xFF1A1A2E)), // 星光层 const TwinkleStars(size: Size(375, 812)), // 其他上层业务内容 ] )
可选优化项
- 星光数量多的时候可以给
CustomPaint包裹RepaintBoundary,避免和页面其他组件一起重绘,提升性能 - 可以随机给星光设置淡白、淡黄、淡蓝等低饱和度颜色,效果更有层次感
- 给星光增加极慢的横向/纵向位移,模拟星光缓慢飘动的效果
内容的提问来源于stack exchange,提问作者Joel
相关产品推荐
相关产品推荐

