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
相关产品推荐
相关产品推荐

