Flutter CustomPainter实现OpenHarmony幸运大转盘

📅 2026/8/3 4:52:53 👁️ 阅读次数 📝 编程学习
Flutter CustomPainter实现OpenHarmony幸运大转盘

1. 项目背景与核心需求

在移动应用开发领域,Flutter因其跨平台特性和高效的渲染性能已成为主流选择之一。而OpenHarmony作为新兴的分布式操作系统,其生态建设正处于快速发展阶段。将Flutter应用于OpenHarmony平台,能够充分利用两者的优势,为开发者提供更灵活的选择。

幸运大转盘作为一种常见的互动营销组件,在电商、游戏、社交等各类应用中都有广泛应用。其核心难点在于:

  • 转盘区域的精确划分与动态渲染
  • 旋转动画的流畅性与物理效果模拟
  • 奖品概率的算法实现
  • 跨平台适配的一致性

本系列教程的第一部分已经完成了基础环境搭建和项目初始化,本篇将重点讲解如何使用Flutter的CustomPainter实现转盘的绘制与交互绑定。

2. 转盘绘制核心原理

2.1 CustomPainter的工作机制

CustomPainter是Flutter中用于自定义绘制的核心类,它通过重写paint()和shouldRepaint()方法来实现绘制逻辑。对于转盘组件,我们需要重点关注:

class WheelPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { // 绘制逻辑实现 } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { // 性能优化判断 } }

关键参数说明:

  • canvas:提供drawArc、drawLine等基础绘图API
  • size:组件可用绘制区域
  • shouldRepaint:决定是否需要重绘,对性能影响显著

2.2 数学基础准备

转盘绘制需要用到一些基础几何知识:

  1. 角度计算:
final double sectorAngle = 2 * pi / itemCount; // 每个扇区的弧度
  1. 坐标转换:
Offset getOffset(double radius, double angle) { return Offset( radius * cos(angle), radius * sin(angle), ); }
  1. 颜色渐变:
final gradient = SweepGradient( colors: colors, stops: stops, transform: GradientRotation(-pi/2), // 调整起始角度 );

3. 完整实现步骤

3.1 转盘基础结构

首先定义转盘的数据结构:

class WheelItem { final String label; final Color color; final double weight; // 权重用于概率计算 const WheelItem({ required this.label, required this.color, this.weight = 1.0, }); }

然后实现绘制逻辑:

void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final radius = min(size.width, size.height) / 2; // 绘制背景圆 canvas.drawCircle(center, radius, Paint()..color = Colors.white); // 绘制扇形 for (var i = 0; i < items.length; i++) { final startAngle = i * sectorAngle; final paint = Paint() ..shader = gradient.createShader(Rect.fromCircle( center: center, radius: radius, )); canvas.drawArc( Rect.fromCircle(center: center, radius: radius), startAngle, sectorAngle, true, paint, ); // 绘制文字 final textAngle = startAngle + sectorAngle / 2; final textOffset = getOffset(radius * 0.6, textAngle); _drawText(canvas, items[i].label, textOffset, textAngle); } }

3.2 动画控制实现

添加旋转动画控制器:

class _WheelState extends State<WheelWidget> with SingleTickerProviderStateMixin { late AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(seconds: 3), ); _controller.addStatusListener((status) { if (status == AnimationStatus.completed) { _handleResult(); } }); } void _spin() { final random = Random().nextDouble(); final target = 5 + random * 5; // 5-10圈 _controller.animateTo( target, curve: Curves.decelerate, ); } }

3.3 奖品概率算法

实现基于权重的随机选择:

int getSelectedIndex() { final totalWeight = items.fold(0.0, (sum, item) => sum + item.weight); var random = Random().nextDouble() * totalWeight; for (var i = 0; i < items.length; i++) { if (random < items[i].weight) { return i; } random -= items[i].weight; } return 0; }

4. OpenHarmony适配要点

4.1 平台特性处理

在OpenHarmony上需要特别注意:

  1. 性能优化:
@override bool shouldRepaint(WheelPainter oldDelegate) { return oldDelegate.items != items || oldDelegate.currentAngle != currentAngle; }
  1. 触摸事件处理:
GestureDetector( onTap: _spin, child: CustomPaint( painter: WheelPainter( items: items, currentAngle: _angle, ), ), )
  1. 字体渲染差异:
void _drawText(Canvas canvas, String text, Offset offset, double angle) { final textPainter = TextPainter( text: TextSpan( text: text, style: TextStyle( fontSize: 16, fontFamily: 'HarmonyOS Sans', // 使用系统字体 ), ), textDirection: TextDirection.ltr, ); textPainter.layout(); final textOffset = offset - Offset(textPainter.width/2, textPainter.height/2); canvas.save(); canvas.translate(textOffset.dx, textOffset.dy); canvas.rotate(angle + pi/2); // 文字朝向调整 textPainter.paint(canvas, Offset.zero); canvas.restore(); }

5. 实战经验与优化建议

5.1 性能优化技巧

  1. 避免不必要的重绘
@override bool shouldRepaint(WheelPainter oldDelegate) { return oldDelegate.currentAngle != currentAngle; // 仅当角度变化时重绘 }
  1. 使用RepaintBoundary
RepaintBoundary( child: CustomPaint( painter: WheelPainter(), ), )
  1. 动画帧率控制
_controller = AnimationController( vsync: this, duration: const Duration(seconds: 3), lowerBound: 0, upperBound: 2 * pi * 10, // 10圈 animationBehavior: AnimationBehavior.preserve, );

5.2 常见问题解决

  1. 文字模糊问题
  • 确保使用物理像素计算
  • 添加抗锯齿处理:
Paint()..isAntiAlias = true
  1. 动画卡顿处理
  • 检查是否在主线程执行耗时操作
  • 考虑使用isolate处理复杂计算
  1. 跨平台渲染差异
  • 测试不同DPI设备的表现
  • 准备多套字体大小方案

5.3 扩展功能建议

  1. 添加惯性效果:
curve: Curves.easeOut,
  1. 实现奖品高亮:
canvas.drawArc( //... paint: Paint()..color = selectedIndex == i ? highlightColor : normalColor, );
  1. 增加音效反馈:
flutter_sound: ^8.4.0

6. 完整代码结构

推荐的项目结构:

lib/ ├── models/ │ └── wheel_item.dart ├── painters/ │ └── wheel_painter.dart ├── widgets/ │ └── wheel_widget.dart └── main.dart

关键文件示例 - wheel_widget.dart:

class WheelWidget extends StatefulWidget { final List<WheelItem> items; const WheelWidget({required this.items}); @override _WheelWidgetState createState() => _WheelWidgetState(); } class _WheelWidgetState extends State<WheelWidget> with SingleTickerProviderStateMixin { late AnimationController _controller; double _angle = 0; @override void initState() { _controller = AnimationController( vsync: this, duration: const Duration(seconds: 3), )..addListener(() { setState(() { _angle = _controller.value; }); }); super.initState(); } void _spin() { if (_controller.isAnimating) return; final random = Random().nextDouble() * 5 + 5; _controller.animateTo( random, curve: Curves.decelerate, ); } @override Widget build(BuildContext context) { return GestureDetector( onTap: _spin, child: CustomPaint( painter: WheelPainter( items: widget.items, currentAngle: _angle, ), ), ); } }

在实现过程中,我发现Flutter在OpenHarmony上的渲染性能表现优异,但在字体渲染和触摸反馈方面存在一些细微差异,需要通过实际设备测试进行针对性优化。对于商业级应用,建议增加更多的错误边界处理和日志记录机制。