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等基础绘图APIsize组件可用绘制区域shouldRepaint决定是否需要重绘对性能影响显著2.2 数学基础准备转盘绘制需要用到一些基础几何知识角度计算final double sectorAngle 2 * pi / itemCount; // 每个扇区的弧度坐标转换Offset getOffset(double radius, double angle) { return Offset( radius * cos(angle), radius * sin(angle), ); }颜色渐变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 StateWheelWidget 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上需要特别注意性能优化override bool shouldRepaint(WheelPainter oldDelegate) { return oldDelegate.items ! items || oldDelegate.currentAngle ! currentAngle; }触摸事件处理GestureDetector( onTap: _spin, child: CustomPaint( painter: WheelPainter( items: items, currentAngle: _angle, ), ), )字体渲染差异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 性能优化技巧避免不必要的重绘override bool shouldRepaint(WheelPainter oldDelegate) { return oldDelegate.currentAngle ! currentAngle; // 仅当角度变化时重绘 }使用RepaintBoundaryRepaintBoundary( child: CustomPaint( painter: WheelPainter(), ), )动画帧率控制_controller AnimationController( vsync: this, duration: const Duration(seconds: 3), lowerBound: 0, upperBound: 2 * pi * 10, // 10圈 animationBehavior: AnimationBehavior.preserve, );5.2 常见问题解决文字模糊问题确保使用物理像素计算添加抗锯齿处理Paint()..isAntiAlias true动画卡顿处理检查是否在主线程执行耗时操作考虑使用isolate处理复杂计算跨平台渲染差异测试不同DPI设备的表现准备多套字体大小方案5.3 扩展功能建议添加惯性效果curve: Curves.easeOut,实现奖品高亮canvas.drawArc( //... paint: Paint()..color selectedIndex i ? highlightColor : normalColor, );增加音效反馈flutter_sound: ^8.4.06. 完整代码结构推荐的项目结构lib/ ├── models/ │ └── wheel_item.dart ├── painters/ │ └── wheel_painter.dart ├── widgets/ │ └── wheel_widget.dart └── main.dart关键文件示例 - wheel_widget.dartclass WheelWidget extends StatefulWidget { final ListWheelItem items; const WheelWidget({required this.items}); override _WheelWidgetState createState() _WheelWidgetState(); } class _WheelWidgetState extends StateWheelWidget 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上的渲染性能表现优异但在字体渲染和触摸反馈方面存在一些细微差异需要通过实际设备测试进行针对性优化。对于商业级应用建议增加更多的错误边界处理和日志记录机制。