求助:开发Flutter自定义渐变边框环形计时器组件
Flutter 自定义环形倒计时计时器组件实现方案
直接通过CustomPainter实现完全自定义的计时器,满足你需要的渐变环形、端点标记和中心数值显示需求,代码及说明如下:
完整实现代码
import 'dart:math'; import 'package:flutter/material.dart'; class CircularCountdownTimer extends StatefulWidget { final int duration; // 倒计时总时长(秒) final double strokeWidth; // 环形边框宽度 final Color startColor; // 渐变起始色 final Color endColor; // 渐变结束色 final double indicatorRadius; // 端点标记圆半径 final TextStyle textStyle; // 中心文本样式 const CircularCountdownTimer({ super.key, required this.duration, this.strokeWidth = 8.0, this.startColor = Colors.blue, this.endColor = Colors.purple, this.indicatorRadius = 6.0, this.textStyle = const TextStyle(fontSize: 32, fontWeight: FontWeight.bold), }); @override State<CircularCountdownTimer> createState() => _CircularCountdownTimerState(); } class _CircularCountdownTimerState extends State<CircularCountdownTimer> with TickerProviderStateMixin { late AnimationController _controller; late Animation<double> _progressAnimation; int _remainingSeconds = 0; @override void initState() { super.initState(); _remainingSeconds = widget.duration; _controller = AnimationController( vsync: this, duration: Duration(seconds: widget.duration), ); _progressAnimation = Tween<double>(begin: 1.0, end: 0.0).animate(_controller) ..addListener(() { setState(() { _remainingSeconds = (widget.duration * _progressAnimation.value).round(); }); }) ..addStatusListener((status) { if (status == AnimationStatus.completed) { // 可在此添加倒计时结束后的自定义逻辑 } }); _controller.forward(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SizedBox( width: 200, height: 200, child: Stack( alignment: Alignment.center, children: [ CustomPaint( painter: _CircularTimerPainter( progress: _progressAnimation.value, strokeWidth: widget.strokeWidth, startColor: widget.startColor, endColor: widget.endColor, indicatorRadius: widget.indicatorRadius, ), size: const Size(200, 200), ), Text( '$_remainingSeconds', style: widget.textStyle, ), ], ), ); } } class _CircularTimerPainter extends CustomPainter { final double progress; final double strokeWidth; final Color startColor; final Color endColor; final double indicatorRadius; _CircularTimerPainter({ required this.progress, required this.strokeWidth, required this.startColor, required this.endColor, required this.indicatorRadius, }); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final radius = min(size.width, size.height) / 2 - strokeWidth / 2; // 绘制背景环形(可选,用于区分已完成/未完成部分) final backgroundPaint = Paint() ..color = Colors.grey.shade200 ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth ..strokeCap = StrokeCap.round; canvas.drawCircle(center, radius, backgroundPaint); // 渐变环形画笔 final gradient = SweepGradient( startAngle: -pi / 2, endAngle: 3 * pi / 2, colors: [startColor, endColor], ); final progressPaint = Paint() ..shader = gradient.createShader(Rect.fromCircle(center: center, radius: radius)) ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth ..strokeCap = StrokeCap.round; // 绘制随进度收缩的环形 final sweepAngle = 2 * pi * progress; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), -pi / 2, // 起始角度(顶部) sweepAngle, false, progressPaint, ); // 绘制端点圆形标记 if (progress > 0) { final indicatorAngle = -pi / 2 + sweepAngle; final indicatorOffset = Offset( center.dx + radius * cos(indicatorAngle), center.dy + radius * sin(indicatorAngle), ); final indicatorPaint = Paint() ..color = endColor ..style = PaintingStyle.fill; canvas.drawCircle(indicatorOffset, indicatorRadius, indicatorPaint); } } @override bool shouldRepaint(covariant _CircularTimerPainter oldDelegate) { return progress != oldDelegate.progress || strokeWidth != oldDelegate.strokeWidth || startColor != oldDelegate.startColor || endColor != oldDelegate.endColor || indicatorRadius != oldDelegate.indicatorRadius; } }
使用示例
在页面中直接调用组件:
CircularCountdownTimer( duration: 60, strokeWidth: 10, startColor: Colors.orange, endColor: Colors.red, indicatorRadius: 7, textStyle: const TextStyle( fontSize: 36, fontWeight: FontWeight.bold, color: Colors.black87, ), )
核心实现说明
- 渐变环形:通过
SweepGradient创建环形渐变,解决了原生CircularProgressIndicator无法添加渐变的问题。 - 端点标记:根据当前进度计算标记的坐标,实时跟随环形收缩端点移动。
- 进度控制:用
AnimationController驱动进度动画,同步更新剩余秒数和环形绘制状态。 - 可配置化:将时长、边框宽度、颜色、标记大小等参数封装为组件属性,方便复用和自定义。
扩展方向
- 添加暂停/继续功能:通过
_controller.stop()和_controller.forward()控制动画状态。 - 增加结束回调:给组件添加
VoidCallback参数,用于倒计时结束后的逻辑处理。 - 自定义起始角度:修改
drawArc的起始角度参数,实现从任意位置开始的倒计时。
内容的提问来源于stack exchange,提问作者Pratik Patil
相关产品推荐
相关产品推荐

