likes
comments
collection
share

Flutter 实战:仿 GitHub 贡献度热力图

作者站长头像
站长
· 阅读数 2

在这篇文章中,我们将深入探索如何在Flutter中创建一个类似GitHub提交热力图的自定义视图。这种热力图是数据可视化的一种流行形式,用于显示例如代码提交频率等时间序列数据的密集程度。通过Flutter,我们可以使用CustomPainterStatelessWidget来实现这种复杂的视图。下面,我们将一步步解析这个过程,展示如何构建这个有趣且实用的组件。

源码仓库:github.com/sinyu1012/f…

Flutter 实战:仿 GitHub 贡献度热力图

1. 组件概览

我们的旅程从定义HeatMap这个StatelessWidget开始。这个组件接受多种参数,例如data(一个DateTimeint的映射),表示数据点,以及colors,一个颜色数组,用来可视化不同数据密度。

class HeatMap extends StatelessWidget {
  //...[参数定义]...

  @override
  Widget build(BuildContext context) {
    //...[构建逻辑]...
  }
}

2. 组件布局和自定义

HeatMap组件利用AspectRatio来维持热图的宽高比,并使用CustomPaint来绘制热图。CustomPaint的画笔是HeatMapPainter对象,它是我们的自定义画笔。


return AspectRatio(
  aspectRatio: aspectRatio,
  child: CustomPaint(
    painter: HeatMapPainter(
      data: data,
      colors: colors ??
          [
            Colors.green.shade200,
            Colors.green.shade400,
            Colors.green.shade600,
            Colors.green.shade800,
          ],
      strokeColor: strokeColor ?? Colors.red.shade100,
      textStyle: textStyle ??
          TextStyle(
            color: Colors.black.withOpacity(0.9),
            fontSize: 12,
          ),
      itemPadding: itemPadding,
      itemSize: itemSize,
    ),
  ),
);

3. 自定义绘制逻辑

接下来,我们定义了HeatMapPainter类,这个类是绘制热图的核心。它需要数据集、颜色数组等参数来绘制每个数据点。

class HeatMapPainter extends CustomPainter {
    //...[绘制器的参数]...

    @override
    void paint(Canvas canvas, Size size) {
      var paint = Paint();
      int cols = _calculateColumns(size.width);
      hasDrawnMonth = List.filled(cols, false);
      int totalCount = cols * rows;

      double heatMapWidth = _calculateHeatMapWidth(cols);
      double startX = _calculateStartX(size.width, heatMapWidth);

      Paint strokePaint = createStrokePaint();

      for (int i = 0; i < rows * cols; i++) {
        var dateAtIndex = _calculateDateForIndex(cols, i);
        var value = data[dateAtIndex] ?? 0;

        paint.color = _getColorForValue(value);
        _drawCell(canvas, paint, i, startX, strokePaint, dateAtIndex);
      }
    }
}

4. 绘制单元格

paint方法中,我们遍历并为热图创建每个单元格。每个单元格的颜色根据其数据值决定。特别的,如果日期是今天,会有一个特殊的边框样式。

void _drawCell(Canvas canvas, Paint paint, int index, double startX,
    Paint strokePaint, DateTime dateAtIndex) {
  var col = index ~/ rows;
  var row = index % rows;
  final rect = _calculateCellRect(startX, col, row);

  canvas.drawRRect(
      RRect.fromRectAndRadius(rect, const Radius.circular(4)), paint);

  if (dateAtIndex.isToday) {
    canvas.drawRRect(
        RRect.fromRectAndRadius(rect, const Radius.circular(4)), strokePaint);
  }

  if (dateAtIndex.day == 1 && !hasDrawnMonth[col]) {
    hasDrawnMonth[col] = true;
    _drawMonthText(canvas, dateAtIndex, col, startX);
  }
}

5. 处理日期和文本

此外,我们还处理了日期的计算和月份文本的绘制,这对于用户理解热图的时间跨度非常重要。


void _drawMonthText(Canvas canvas, DateTime date, int col, double startX) {
  String monthText = intl.DateFormat('MMM').format(date);

  TextPainter textPainter = TextPainter(
    text: TextSpan(text: monthText, style: textStyle),
    textDirection: TextDirection.ltr,
  );

  textPainter.layout();

  double xPosition = _calculateTextXPosition(col, startX, textPainter.width);
  Offset textPosition = Offset(
      xPosition, (rows * itemSize) + (rows * itemPadding) + itemPadding);

  textPainter.paint(canvas, textPosition);
}

6. 总结

通过上述的步骤,我们展示了如何在Flutter中创建一个类似GitHub提交热力图的自定义视图。这种视图在展示用户活动、项目进度或任何其他时间序列数据方面非常有用。利用Flutter的CustomPainter,我们可以完全控制视图的外观和行为,创造出既独特又功能强大的数据可视化工具。

源码仓库:github.com/sinyu1012/f…