跳转至

绘画

CustomPaint 是一个提供给开发者自定义绘画操作的控件。它可以将绘制逻辑委托给一个 CustomPainter 对象。

CustomPaint 控件有两个属性:painterforegroundPainter,它们都可以接收一个 CustomPainter 对象。

  1. painter: 这个属性接受一个 CustomPainter 对象,用于在子控件被绘制之前进行绘制操作。你可以在这里实现你的绘制逻辑,比如画线、画圈、画路径等。这个绘画层位于 CustomPaint 控件的子控件下面,因此任何在这里绘制的内容都会被子控件覆盖。

  2. foregroundPainter: 这个属性也接受一个 CustomPainter 对象,但它是在子控件被绘制之后进行绘制操作。这意味着通过 foregroundPainter 绘制的内容会覆盖在子控件上面。这可以用来添加高亮效果、覆盖图层或者其他需要显示在子控件之上的绘制效果。

CustomPainter 是一个需要实现的抽象类,至少需要实现以下两个方法:

/// 这个方法包含实际的绘制命令,使用提供的 `Canvas` 对象在给定的 `Size` 范围内进行绘制。
@override
void paint(Canvas canvas, Size size) {
  /// paint:具体怎么画
  /// canvas:画点、线、圆圈、长方形。使用`cavas.`来画
  /// size:画布大小
}
/// 这个方法决定在什么情况下应该重新绘制。例如,如果你的绘制依赖于某些数据模型,当模型改变时,你可能需要返回 `true` 来重绘控件。
/// shouldRepaint:该函数返回true时,画布会重新绘制
/// 上一帧动画可以做对比
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;

下面是一个简单的使用 CustomPaint 的例子:

CustomPaint(
  size: Size(200, 200), // 指定绘制区域的大小
  painter: MyCustomPainter(), // 自定义的绘制逻辑
  foregroundPainter: MyForegroundCustomPainter(), // 自定义的前景绘制逻辑
  child: ... // 子控件
)

在这个例子中,MyCustomPainterMyForegroundCustomPainter 是开发者定义的类,它们分别扩展了 CustomPainter 类并实现了必要的方法。通过这种方式,可以在 CustomPaint 控件的背景和前景层进行复杂的绘制操作。

折线图

  1. 创建一个自定义的CustomPainter类:负责绘制折线图。
  2. 在Widget树中使用CustomPaint widget,并将自定义painter传递给它。
class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Custom Paint Demo'),
      ),
      body: Center(
        child: CustomPaint(
          size: Size(300, 300), // 画布大小
          painter: MyCustomPainter(),
        ),
      ),
    );
  }
}

class MyCustomPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // 定义画笔
    Paint linePaint = Paint()
      ..color = Colors.green // 支出金额线的颜色
      ..strokeWidth = 2.0; // 线宽

    // 支出金额数据
    List<double> expenseData = [133, 32432, 4353, 65476, 5756, 765, 2343, 24314, 43214, 42314, 324, 543];
    // 将支出金额数据转换为点坐标
    List<Offset> expensePoints = [];
    for (int i = 0; i < expenseData.length; i++) {
      expensePoints.add(Offset(
        i * size.width / (expenseData.length - 1), // x坐标
        size.height - (expenseData[i] / 65476) * size.height, // y坐标,使用最大值归一化
      ));
    }
    // 绘制支出金额折线图
    for (int i = 0; i < expensePoints.length - 1; i++) {
      canvas.drawLine(expensePoints[i], expensePoints[i + 1], linePaint);
    }

    // 修改画笔颜色以绘制收入金额线
    linePaint.color = Colors.red; // 收入金额线的颜色

    // 收入金额数据
    List<double> incomeData = [3435, 43543, 64567, 76547, 765, 7654, 756, 765, 2345, 1345, 8675, 54654];
    // 将收入金额数据转换为点坐标
    List<Offset> incomePoints = [];
    for (int i = 0; i < incomeData.length; i++) {
      incomePoints.add(Offset(
        i * size.width / (incomeData.length - 1), // x坐标
        size.height - (incomeData[i] / 76547) * size.height, // y坐标,使用最大值归一化
      ));
    }
    // 绘制收入金额折线图
    for (int i = 0; i < incomePoints.length - 1; i++) {
      canvas.drawLine(incomePoints[i], incomePoints[i + 1], linePaint);
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    // 该函数返回true时,画布会重新绘制
    return false;
  }
}

在上面示例中,创建了一个MyCustomPainter类,它继承自CustomPainter

paint方法中,我们首先定义了画笔,然后分别计算了支出金额和收入金额的点坐标。这些点坐标是根据传入的数据和画布大小计算得出的。接着,我们使用canvas.drawLine方法绘制了两组数据的折线图。

请注意,这个示例假设你的数据点是均匀分布的,而且我们使用了数据中的最大值来归一化数据点到画布的高度。在实际应用中,你可能需要根据实际情况调整坐标的计算方式。

最后,shouldRepaint方法返回false,因为我们的数据点不会改变,所以不需要重绘。如果你的数据是动态变化的,你可能需要在这里返回true

数据较多,需要左右滑动

要使ChartBar组件可以左右滑动,可以将其包装在ListViewSingleChildScrollView中。这两个组件都可以提供滚动功能。在这种情况下,由于ChartBar组件可能会占据大量水平空间,所以我们应该使用ListView的水平变体ListView.builder