import 'package:flutter/material.dart'; import 'dart:math' as math; /// 环形进度条播放按钮组件 class CircularProgressButton extends StatelessWidget { final bool isPlaying; final double progress; // 0.0 - 1.0 final VoidCallback onPlayPause; final Function(double)? onSeek; // 点击进度条跳转回调 final double size; final Color primaryColor; final Color backgroundColor; final double strokeWidth; const CircularProgressButton({ Key? key, required this.isPlaying, required this.progress, required this.onPlayPause, this.onSeek, this.size = 36.0, required this.primaryColor, this.backgroundColor = Colors.grey, this.strokeWidth = 2.0, }) : super(key: key); @override Widget build(BuildContext context) { return GestureDetector( onTapDown: (details) { if (onSeek != null) { // 计算点击位置对应的进度 final center = Offset(size / 2, size / 2); final tapPosition = details.localPosition; final dx = tapPosition.dx - center.dx; final dy = tapPosition.dy - center.dy; // 计算角度 (从顶部开始,顺时针) double angle = math.atan2(dy, dx) + math.pi / 2; if (angle < 0) angle += 2 * math.pi; // 转换为进度值 (0.0 - 1.0) final seekProgress = angle / (2 * math.pi); // 检查点击是否在圆环区域内 final distance = math.sqrt(dx * dx + dy * dy); final innerRadius = (size / 2) - strokeWidth - 4; final outerRadius = (size / 2) + 4; if (distance >= innerRadius && distance <= outerRadius) { onSeek!(seekProgress); } else if (distance < innerRadius) { // 点击中心区域,播放/暂停 onPlayPause(); } } else { onPlayPause(); } }, child: SizedBox( width: size, height: size, child: Stack( alignment: Alignment.center, children: [ // 背景圆环 CustomPaint( size: Size(size, size), painter: _CircularProgressPainter( progress: 1.0, color: backgroundColor.withValues(alpha: 0.3), strokeWidth: strokeWidth, ), ), // 进度圆环 CustomPaint( size: Size(size, size), painter: _CircularProgressPainter( progress: progress, color: primaryColor, strokeWidth: strokeWidth, ), ), // 播放/暂停按钮 Icon( isPlaying ? Icons.pause : Icons.play_arrow, color: primaryColor, size: size * 0.5, ), ], ), ), ); } } /// 环形进度条绘制器 class _CircularProgressPainter extends CustomPainter { final double progress; final Color color; final double strokeWidth; _CircularProgressPainter({ required this.progress, required this.color, required this.strokeWidth, }); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final radius = (size.width / 2) - strokeWidth / 2; final paint = Paint() ..color = color ..strokeWidth = strokeWidth ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round; // 绘制圆弧,从顶部开始 (-π/2),顺时针绘制 const startAngle = -math.pi / 2; final sweepAngle = 2 * math.pi * progress; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), startAngle, sweepAngle, false, paint, ); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return oldDelegate is! _CircularProgressPainter || oldDelegate.progress != progress || oldDelegate.color != color || oldDelegate.strokeWidth != strokeWidth; } }