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

Flutter中绘制含两点的线段及时间范围选择可视化实现方案咨询

1. 在Flutter中绘制两点线段的实现方法

在Flutter里绘制线段最直接的方式就是用CustomPaint组件配合CustomPainter,下面是一个简单易懂的示例:

首先自定义一个LinePainter,继承自CustomPainter,重写paint方法来实现线段绘制:

class LinePainter extends CustomPainter {
  final Offset startPoint;
  final Offset endPoint;
  final Color lineColor;
  final double lineWidth;

  LinePainter({
    required this.startPoint,
    required this.endPoint,
    this.lineColor = Colors.black,
    this.lineWidth = 2.0,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = lineColor
      ..strokeWidth = lineWidth
      ..strokeCap = StrokeCap.round; // 让线段两端更圆润美观

    canvas.drawLine(startPoint, endPoint, paint);
  }

  @override
  bool shouldRepaint(covariant LinePainter oldDelegate) {
    // 当起点、终点、颜色或宽度变化时触发重绘
    return oldDelegate.startPoint != startPoint ||
        oldDelegate.endPoint != endPoint ||
        oldDelegate.lineColor != lineColor ||
        oldDelegate.lineWidth != lineWidth;
  }
}

然后在页面中使用CustomPaint来展示这条线段:

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('绘制线段示例')),
    body: Center(
      child: CustomPaint(
        size: Size(300, 300), // 定义画布的大小
        painter: LinePainter(
          startPoint: Offset(50, 150), // 起点坐标(x,y)
          endPoint: Offset(250, 150), // 终点坐标(x,y)
          lineColor: Colors.blue,
          lineWidth: 3.0,
        ),
      ),
    ),
  );
}

这样就能在画布上画出一条从(50,150)到(250,150)的蓝色线段了。


2. 随时间选择动态更新的时间范围线段实现思路

结合你说的两个DropdownButton选时间范围的需求,核心思路是用状态管理保存选中的时间,将时间转换为画布上的坐标,最后通过CustomPaint动态绘制线段和标记,具体步骤和代码如下:

第一步:状态管理与基础UI布局

使用StatefulWidget保存选中的开始/结束小时,再布局时间选择器和绘制区域:

class TimeRangeLine extends StatefulWidget {
  const TimeRangeLine({super.key});

  @override
  State<TimeRangeLine> createState() => _TimeRangeLineState();
}

class _TimeRangeLineState extends State<TimeRangeLine> {
  int _startHour = 8; // 默认开始时间8点
  int _endHour = 18; // 默认结束时间18点
  final List<int> _hours = List.generate(24, (index) => index); // 生成0-23小时列表

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('时间范围线段')),
      body: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          children: [
            // 时间选择下拉框
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                DropdownButton<int>(
                  value: _startHour,
                  items: _hours.map((hour) {
                    return DropdownMenuItem(
                      value: hour,
                      child: Text('${hour}时'),
                    );
                  }).toList(),
                  onChanged: (value) {
                    if (value != null) setState(() => _startHour = value);
                  },
                ),
                const Text('至'),
                DropdownButton<int>(
                  value: _endHour,
                  items: _hours.map((hour) {
                    return DropdownMenuItem(
                      value: hour,
                      child: Text('${hour}时'),
                    );
                  }).toList(),
                  onChanged: (value) {
                    if (value != null) setState(() => _endHour = value);
                  },
                ),
              ],
            ),
            const SizedBox(height: 40),
            // 动态绘制的时间范围线段区域
            CustomPaint(
              size: Size(MediaQuery.of(context).size.width - 40, 60),
              painter: TimeRangePainter(
                startHour: _startHour,
                endHour: _endHour,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

第二步:自定义Painter实现动态绘制

编写TimeRangePainter,负责把选中的时间转换成画布坐标,绘制时间轴、范围线段和红色标记:

class TimeRangePainter extends CustomPainter {
  final int startHour;
  final int endHour;
  final double axisHeight = 2.0; // 时间轴线宽
  final double markerRadius = 6.0; // 红色标记圆圈半径

  TimeRangePainter({required this.startHour, required this.endHour});

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.grey
      ..strokeWidth = axisHeight
      ..strokeCap = StrokeCap.round;

    // 1. 绘制完整的0-23小时时间轴
    canvas.drawLine(
      Offset(0, size.height / 2),
      Offset(size.width, size.height / 2),
      paint,
    );

    // 计算每个小时对应的x坐标:总宽度 / 23(0到23共23个间隔)
    final double hourWidth = size.width / 23;
    final double centerY = size.height / 2;

    // 2. 绘制选中的时间范围线段
    final startX = startHour * hourWidth;
    final endX = endHour * hourWidth;
    paint
      ..color = Colors.blue
      ..strokeWidth = 4.0;
    canvas.drawLine(
      Offset(startX, centerY),
      Offset(endX, centerY),
      paint,
    );

    // 3. 绘制开始/结束时间的红色标记圆圈
    paint
      ..color = Colors.red
      ..style = PaintingStyle.fill;
    canvas.drawCircle(Offset(startX, centerY), markerRadius, paint);
    canvas.drawCircle(Offset(endX, centerY), markerRadius, paint);

    // 可选:在时间轴上标记关键小时数(比如每4小时标一次)
    paint
      ..color = Colors.black
      ..strokeWidth = 1.0
      ..style = PaintingStyle.fill;
    final textPainter = TextPainter(textDirection: TextDirection.ltr);
    for (int i = 0; i <= 23; i += 4) {
      final x = i * hourWidth;
      textPainter.text = TextSpan(
        text: '$i',
        style: const TextStyle(fontSize: 12, color: Colors.black),
      );
      textPainter.layout();
      textPainter.paint(canvas, Offset(x - textPainter.width / 2, centerY + 10));
    }
  }

  @override
  bool shouldRepaint(covariant TimeRangePainter oldDelegate) {
    // 当开始/结束时间变化时触发重绘
    return oldDelegate.startHour != startHour || oldDelegate.endHour != endHour;
  }
}

一些扩展建议

  • 如果需要处理跨天的时间范围(比如22点到2点),可以判断startHour > endHour,然后绘制两段线段:从startHour到23点,以及从0点到endHour。
  • 可以优化标记样式,比如给红色圆圈加白色描边,或者换成方形、三角形等其他形状。
  • 可以添加平滑过渡动画,比如标记移动时的缓动效果,只需要结合AnimationController来驱动坐标变化即可。

内容的提问来源于stack exchange,提问作者Ahmad Zaklouta

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 13:42:48