绘画¶
CustomPaint
是一个提供给开发者自定义绘画操作的控件。它可以将绘制逻辑委托给一个 CustomPainter
对象。
CustomPaint
控件有两个属性:painter
和 foregroundPainter
,它们都可以接收一个 CustomPainter
对象。
-
painter
: 这个属性接受一个CustomPainter
对象,用于在子控件被绘制之前进行绘制操作。你可以在这里实现你的绘制逻辑,比如画线、画圈、画路径等。这个绘画层位于CustomPaint
控件的子控件下面,因此任何在这里绘制的内容都会被子控件覆盖。 -
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: ... // 子控件
)
在这个例子中,MyCustomPainter
和 MyForegroundCustomPainter
是开发者定义的类,它们分别扩展了 CustomPainter
类并实现了必要的方法。通过这种方式,可以在 CustomPaint
控件的背景和前景层进行复杂的绘制操作。
折线图¶
- 创建一个自定义的
CustomPainter
类:负责绘制折线图。 - 在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组件可以左右滑动,可以将其包装在ListView
或SingleChildScrollView
中。这两个组件都可以提供滚动功能。在这种情况下,由于ChartBar组件可能会占据大量水平空间,所以我们应该使用ListView的水平变体ListView.builder
。