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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 09:15:57