Flutter开发:实现可控制分段长度的U形Widget
实现可自定义分段的U形时间线Widget(Flutter)
直接给你一个基于CustomPainter的可行实现,能精准控制每个彩色分段的长度:
1. 定义分段数据类
先创建数据类存储每个活动分段的颜色和时长:
class ActivitySegment { final Color color; final double duration; // 自定义单位,最终按总时长比例计算长度 ActivitySegment({required this.color, required this.duration}); }
2. 自定义U形时间线Painter
这个Painter会按比例把各分段绘制到U形的左竖边、底部横边、右竖边:
import 'dart:math' as math; import 'package:flutter/material.dart'; class UTimelinePainter extends CustomPainter { final List<ActivitySegment> segments; final double strokeWidth; final double cornerRadius; final double totalDuration; UTimelinePainter({ required this.segments, this.strokeWidth = 20.0, this.cornerRadius = 10.0, required this.totalDuration, }); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth ..strokeCap = StrokeCap.round; // 计算U形各部分总长度 final leftHeight = size.height / 2; final bottomWidth = size.width - strokeWidth; // 留边避免超出画布 final rightHeight = size.height / 2; final totalPathLength = leftHeight + bottomWidth + rightHeight; // 转换每个分段的时长为实际路径长度 final segmentLengths = segments.map((seg) { return (seg.duration / totalDuration) * totalPathLength; }).toList(); double currentOffset = 0.0; for (int i = 0; i < segments.length; i++) { final segmentLength = segmentLengths[i]; paint.color = segments[i].color; Path path = Path(); // 处理左竖边(顶部中点到底部) if (currentOffset < leftHeight) { final startY = (size.height - leftHeight) + currentOffset; final endY = startY + segmentLength; if (endY <= size.height) { path.moveTo(strokeWidth / 2, startY); path.lineTo(strokeWidth / 2, endY); canvas.drawPath(path, paint); currentOffset += segmentLength; continue; } else { // 画到左竖边底部,绘制转角圆角 path.moveTo(strokeWidth / 2, startY); path.lineTo(strokeWidth / 2, size.height - strokeWidth / 2); path.arcTo( Rect.fromLTWH( strokeWidth / 2, size.height - strokeWidth - cornerRadius, cornerRadius * 2, cornerRadius * 2, ), math.pi / 2, math.pi / 2, false, ); canvas.drawPath(path, paint); currentOffset = leftHeight + cornerRadius; } } // 处理底部横边(左到右) if (currentOffset >= leftHeight && currentOffset < leftHeight + bottomWidth) { final startX = strokeWidth + (currentOffset - leftHeight); final endX = startX + segmentLength; if (endX <= size.width - strokeWidth) { path.moveTo(startX, size.height - strokeWidth / 2); path.lineTo(endX, size.height - strokeWidth / 2); canvas.drawPath(path, paint); currentOffset += segmentLength; continue; } else { // 画到横边右端,绘制转角圆角 path.moveTo(startX, size.height - strokeWidth / 2); path.lineTo(size.width - strokeWidth / 2 - cornerRadius, size.height - strokeWidth / 2); path.arcTo( Rect.fromLTWH( size.width - strokeWidth - cornerRadius, size.height - strokeWidth - cornerRadius, cornerRadius * 2, cornerRadius * 2, ), 0, math.pi / 2, false, ); canvas.drawPath(path, paint); currentOffset = leftHeight + bottomWidth + cornerRadius; } } // 处理右竖边(底部到顶部中点) if (currentOffset >= leftHeight + bottomWidth) { final startY = size.height - strokeWidth / 2 - (currentOffset - leftHeight - bottomWidth); final endY = startY - segmentLength; if (endY >= size.height - rightHeight) { path.moveTo(size.width - strokeWidth / 2, startY); path.lineTo(size.width - strokeWidth / 2, endY); canvas.drawPath(path, paint); currentOffset += segmentLength; } } } } @override bool shouldRepaint(covariant UTimelinePainter oldDelegate) { return oldDelegate.segments != segments || oldDelegate.strokeWidth != strokeWidth || oldDelegate.cornerRadius != cornerRadius || oldDelegate.totalDuration != totalDuration; } }
3. 封装成可复用Widget
把Painter封装成Widget,方便页面调用:
class UTimelineWidget extends StatelessWidget { final List<ActivitySegment> segments; final double totalDuration; final double strokeWidth; final double cornerRadius; final double height; const UTimelineWidget({ super.key, required this.segments, required this.totalDuration, this.strokeWidth = 20.0, this.cornerRadius = 10.0, this.height = 200.0, }); @override Widget build(BuildContext context) { return SizedBox( height: height, width: double.infinity, child: CustomPaint( painter: UTimelinePainter( segments: segments, totalDuration: totalDuration, strokeWidth: strokeWidth, cornerRadius: cornerRadius, ), ), ); } }
4. 示例用法
假设总时长为一天1440分钟,各活动分段如下:
// 定义活动分段 final activitySegments = [ ActivitySegment(color: Colors.blue.shade700, duration: 480), // 睡眠8小时 ActivitySegment(color: Colors.green.shade600, duration: 360), // 工作6小时 ActivitySegment(color: Colors.orange.shade500, duration: 120), // 休闲2小时 ActivitySegment(color: Colors.purple.shade500, duration: 480), // 其他8小时 ]; // 在页面中使用 UTimelineWidget( segments: activitySegments, totalDuration: 1440, strokeWidth: 25, cornerRadius: 12, height: 220, )
关键控制逻辑
- 分段长度由
duration和totalDuration的比例决定,修改duration即可调整对应分段长度 - 通过
strokeWidth调整U形粗细,height控制整体高度 cornerRadius控制U形转角的圆角大小
内容的提问来源于stack exchange,提问作者SSL
相关产品推荐
相关产品推荐

