You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter技术问询:如何在自定义半圆周边绘制小圆(附参考图)

实现半圆周边绘制小圆的两种方案

方案一:使用CustomPaint(推荐,精准可控)

CustomPaint可以直接绘制半圆和小圆,坐标计算更准确,适合这种需要几何定位的场景。

完整代码

import 'dart:math';
import 'package:flutter/material.dart';

class HalfCircleWithDots extends StatelessWidget {
  const HalfCircleWithDots({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 150,
      margin: const EdgeInsets.only(left: 70, right: 70),
      child: CustomPaint(
        painter: _HalfCirclePainter(),
      ),
    );
  }
}

class _HalfCirclePainter extends CustomPainter {
  // 半圆的圆角半径(对应原代码的BorderRadius值)
  final double halfCircleRadius = 160;
  // 小圆数量
  final int dotCount = 8;
  // 小圆半径
  final double dotRadius = 8;
  // 半圆渐变
  final Gradient gradient = const RadialGradient(
    center: Alignment(-0.8, -0.6),
    colors: [
      Color.fromRGBO(153, 250, 10, 1),
      Color.fromRGBO(153, 240, 94, 1)
    ],
    radius: 1.0,
  );

  @override
  void paint(Canvas canvas, Size size) {
    // 1. 绘制半圆(带渐变)
    final rect = Rect.fromLTWH(0, 0, size.width, size.height);
    final rrect = RRect.fromRectAndCorners(
      rect,
      topLeft: Radius.circular(halfCircleRadius),
      topRight: Radius.circular(halfCircleRadius),
    );
    final paint = Paint()..shader = gradient.createShader(rect);
    canvas.drawRRect(rrect, paint);

    // 2. 计算半圆的圆心坐标
    // 因为圆角半径160 > 容器高度150,圆心在容器底部下方 (160-150) = 10px处
    final center = Offset(size.width / 2, size.height + (halfCircleRadius - size.height));

    // 3. 绘制周边小圆
    final dotPaint = Paint()..color = Colors.white;
    // 角度范围:从π(左端点)到0(右端点),覆盖上半圆弧
    for (int i = 0; i < dotCount; i++) {
      final angle = pi - (pi * i / (dotCount - 1));
      final dotCenter = Offset(
        center.dx + halfCircleRadius * cos(angle),
        center.dy + halfCircleRadius * sin(angle),
      );
      canvas.drawCircle(dotCenter, dotRadius, dotPaint);
    }
  }

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

使用方式

直接在页面中引用const HalfCircleWithDots()即可。


方案二:使用Stack + Positioned(基于原代码改造)

如果不想自定义绘制,可以在原有的半圆Container外层套Stack,通过三角函数计算每个小圆的位置,用Positioned定位。

完整代码

import 'dart:math';
import 'package:flutter/material.dart';

Widget buildHalfCircleWithDots() {
  const double halfCircleRadius = 160;
  const int dotCount = 8;
  const double dotRadius = 8;
  const double containerHeight = 150;

  return Container(
    height: containerHeight,
    margin: const EdgeInsets.only(left: 70, right: 70),
    child: Stack(
      children: [
        // 原有的半圆容器
        Container(
          height: containerHeight,
          decoration: const BoxDecoration(
            gradient: RadialGradient(
              center: Alignment(-0.8, -0.6),
              colors: [
                Color.fromRGBO(153, 250, 10, 1),
                Color.fromRGBO(153, 240, 94, 1)
              ],
              radius: 1.0,
            ),
            borderRadius: BorderRadius.only(
              topLeft: Radius.circular(halfCircleRadius),
              topRight: Radius.circular(halfCircleRadius),
            ),
          ),
        ),
        // 生成小圆列表
        ...List.generate(dotCount, (index) {
          // 计算当前小圆对应的角度
          final angle = pi - (pi * index / (dotCount - 1));
          // 半圆的圆心坐标(相对于当前Container)
          final centerX = (MediaQuery.of(WidgetsBinding.instance.window).size.width - 140) / 2;
          final centerY = containerHeight + (halfCircleRadius - containerHeight);
          // 小圆的中心坐标
          final dotX = centerX + halfCircleRadius * cos(angle);
          final dotY = centerY + halfCircleRadius * sin(angle);

          return Positioned(
            left: dotX - dotRadius,
            top: dotY - dotRadius,
            child: Container(
              width: dotRadius * 2,
              height: dotRadius * 2,
              decoration: const BoxDecoration(
                color: Colors.white,
                shape: BoxShape.circle,
              ),
            ),
          );
        }),
      ],
    ),
  );
}

注意事项

  • 这里通过MediaQuery获取屏幕宽度来计算容器的实际宽度(减去左右各70的margin),如果容器宽度是固定值,可以直接替换成固定数值。
  • 可以通过调整dotCount、dotRadius参数来修改小圆的数量和大小。

内容的提问来源于stack exchange,提问作者Kevin Chean

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 23:31:07