在这里插入图片描述

上一篇搭好了项目框架,这一篇开始实现核心功能——转盘的绘制。转盘是整个应用的视觉中心,需要用到Flutter的自定义绘制能力。

理解转盘结构

转盘由几部分组成:

  • 扇形区域 — 每个奖品占一个扇形,用不同颜色区分
  • 分隔线 — 白色线条分隔各个扇形
  • 文字标签 — 奖品名称,沿着扇形弧线排列
  • 外圈边框 — 白色边框,增强视觉效果

创建奖品数据模型

首先定义奖品的数据结构。创建lib/models/prize.dart

class Prize {
  final String id;
  final String name;
  final Color color;
  final IconData icon;

  Prize({
    required this.id,
    required this.name,
    required this.color,
    required this.icon,
  });
}

这里定义了奖品的基本属性:id 用于唯一标识,name 是奖品名称,color 是转盘上的颜色,icon 是显示的图标。

自定义绘制转盘

转盘的绘制是通过CustomPainter实现的。在lib/pages/wheel_page.dart中定义WheelPainter类:

class WheelPainter extends CustomPainter {
  final List<Prize> prizes;
  WheelPainter({required this.prizes});

  
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    final radius = size.width / 2;
    final sweepAngle = 2 * pi / prizes.length;

这里计算了几个关键参数:

  • center — 转盘中心点
  • radius — 转盘半径
  • sweepAngle — 每个扇形的圆心角(360度 ÷ 奖品数量)

绘制扇形

接下来遍历每个奖品,绘制对应的扇形:

    for (int i = 0; i < prizes.length; i++) {
      final paint = Paint()..color = prizes[i].color;
      final startAngle = i * sweepAngle - pi / 2;
      canvas.drawArc(
        Rect.fromCircle(center: center, radius: radius),
        startAngle,
        sweepAngle,
        true,
        paint,
      );

这里用drawArc方法绘制扇形。startAngle 减去 pi/2 是为了让第一个扇形从顶部开始。true 参数表示填充扇形。

绘制分隔线

在每个扇形的起始位置绘制一条白色分隔线:

      canvas.drawLine(
        center,
        Offset(
          center.dx + radius * cos(startAngle),
          center.dy + radius * sin(startAngle),
        ),
        Paint()
          ..color = Colors.white
          ..strokeWidth = 2,
      );

分隔线从中心点出发,指向扇形的边界。用三角函数计算端点坐标。

绘制文字标签

奖品名称需要沿着扇形弧线排列,这需要一些几何计算:

      final textAngle = startAngle + sweepAngle / 2;
      final textRadius = radius * 0.65;
      final textCenter = Offset(
        center.dx + textRadius * cos(textAngle),
        center.dy + textRadius * sin(textAngle),
      );

      final textPainter = TextPainter(
        text: TextSpan(
          text: prizes[i].name,
          style: const TextStyle(
            color: Colors.white,
            fontSize: 14,
            fontWeight: FontWeight.bold,
          ),
        ),
        textDirection: TextDirection.ltr,
      )..layout();

textAngle 是扇形的中心角,文字放在这个位置。textRadius 设为半径的65%,这样文字不会太靠近边缘。

旋转文字方向

文字需要沿着扇形方向旋转,这样看起来更自然:

      canvas.save();
      canvas.translate(textCenter.dx, textCenter.dy);
      canvas.rotate(textAngle + pi / 2);
      textPainter.paint(
        canvas,
        Offset(-textPainter.width / 2, -textPainter.height / 2),
      );
      canvas.restore();

先用save()保存画布状态,然后平移到文字位置,旋转画布,绘制文字,最后用restore()恢复画布状态。这样做是为了避免旋转影响其他绘制操作。

绘制外圈边框

最后加上一个白色的外圈边框,增强视觉效果:

    canvas.drawCircle(
      center,
      radius,
      Paint()
        ..color = Colors.white
        ..style = PaintingStyle.stroke
        ..strokeWidth = 4,
    );
  }

  
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

shouldRepaint 返回 false 表示转盘内容不会变化,不需要重新绘制。

在页面中使用转盘

WheelPagebuild方法中,用CustomPaint显示转盘:

CustomPaint(
  size: const Size(300, 300),
  painter: WheelPainter(prizes: _prizeManager.prizes),
)

这里把转盘大小设为300x300,传入奖品列表给WheelPainter

测试转盘

现在可以运行应用看看转盘效果了。如果一切正常,你应该能看到一个彩色的转盘,上面有六个扇形,分别代表不同的奖品。

如果文字显示不全或位置不对,可以调整textRadius的值。比如改成radius * 0.6radius * 0.7试试。

常见问题

文字重叠或显示不全

这通常是因为奖品名称太长。可以在TextPaintermaxLines参数设为1,然后用overflow: TextOverflow.ellipsis截断过长的文字。

扇形颜色不对

检查一下PrizeManager中定义的奖品颜色是否正确。如果想改颜色,直接修改Prize对象的color属性就行。

转盘显示不完整

确保CustomPaintsize足够大,至少要300x300。如果容器太小,转盘会被裁剪。

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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐