如何在Flutter中创建这类弧形Widget列表?
Flutter 弧形排列选择Widget实现方案
核心思路
通过三角函数计算每个子Widget在圆弧上的坐标,结合Stack和Transform实现弧形排列,同时处理选中交互效果。
完整实现代码
1. 自定义弧形选择Widget
import 'dart:math'; import 'package:flutter/material.dart'; class ArcSelector extends StatefulWidget { final List<Widget> items; final double radius; final double totalAngle; // 弧度,默认120度(π*2/3) final ValueChanged<int>? onSelected; const ArcSelector({ super.key, required this.items, this.radius = 150, this.totalAngle = pi * 2 / 3, this.onSelected, }); @override State<ArcSelector> createState() => _ArcSelectorState(); } class _ArcSelectorState extends State<ArcSelector> { int _selectedIndex = 0; @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; final centerX = size.width / 2; final centerY = size.height / 2; final itemCount = widget.items.length; final angleStep = widget.totalAngle / (itemCount - 1); // 起始角度,确保圆弧在屏幕居中 final startAngle = pi - widget.totalAngle / 2; return Stack( alignment: Alignment.center, children: [ // 绘制背景圆弧 CustomPaint( painter: ArcBackgroundPainter( radius: widget.radius, totalAngle: widget.totalAngle, color: Colors.grey.shade200, ), size: size, ), // 弧形排列的子项 for (int i = 0; i < itemCount; i++) Positioned( left: centerX + widget.radius * cos(startAngle + angleStep * i) - 30, top: centerY + widget.radius * sin(startAngle + angleStep * i) - 30, child: GestureDetector( onTap: () { setState(() => _selectedIndex = i); widget.onSelected?.call(i); }, child: Transform.scale( scale: _selectedIndex == i ? 1.3 : 1.0, child: Container( width: 60, height: 60, decoration: BoxDecoration( color: _selectedIndex == i ? Colors.blue : Colors.grey.shade300, borderRadius: BorderRadius.circular(30), boxShadow: _selectedIndex == i ? [BoxShadow(color: Colors.blue.withOpacity(0.3), blurRadius: 8, spreadRadius: 2)] : null, ), child: widget.items[i], ), ), ), ), ], ); } } // 背景圆弧绘制器 class ArcBackgroundPainter extends CustomPainter { final double radius; final double totalAngle; final Color color; ArcBackgroundPainter({ required this.radius, required this.totalAngle, required this.color, }); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final startAngle = pi - totalAngle / 2; final paint = Paint() ..color = color ..style = PaintingStyle.stroke ..strokeWidth = 4 ..strokeCap = StrokeCap.round; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), startAngle, totalAngle, false, paint, ); } @override bool shouldRepaint(covariant ArcBackgroundPainter oldDelegate) { return radius != oldDelegate.radius || totalAngle != oldDelegate.totalAngle || color != oldDelegate.color; } }
2. 使用示例
class ArcSelectorDemo extends StatelessWidget { const ArcSelectorDemo({super.key}); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, body: Center( child: ArcSelector( items: List.generate(5, (index) => Center( child: Text( '${index+1}', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), )), onSelected: (index) => print('选中第 ${index+1} 项'), ), ), ); } }
关键细节说明
- 坐标计算:利用
cos/sin函数计算圆弧上的点,startAngle确保圆弧在屏幕水平居中 - 选中交互:通过
Transform.scale实现选中项放大,配合阴影增强视觉层级 - 可配置性:支持自定义圆弧半径、总角度、子项内容,适配不同场景需求
内容的提问来源于stack exchange,提问作者Mayank Prajapati
相关产品推荐
相关产品推荐

