自定义是flutter进阶中不可缺少的ui层知识点,这里我们来总结下:
在Flutter中,自定义绘制通常是通过使用CustomPaint
和CustomPainter
来实现的。
- 创建
CustomPaint
组件
首先,需要创建一个CustomPaint
组件。CustomPaint
是一个Widget,它可以作为其他组件的子组件,用于提供自定义绘制的功能。创建CustomPaint
时可以指定其子组件、前景画笔和背景画笔。
示例代码:
CustomPaint(
painter: BackgroundPainter(),
foregroundPainter: ForegroundPainter(),
child: Container(),
)
这里BackgroundPainter
和ForegroundPainter
是自定义的画笔类,需要继承CustomPainter
。
- 创建自定义画笔类
接下来,需要创建自定义的画笔类。自定义画笔类需要继承CustomPainter
类,并重写paint
和shouldRepaint
方法。paint
方法用于实现绘制逻辑,而shouldRepaint
方法用于决定在什么情况下需要重绘。
示例代码:
class BackgroundPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
// 绘制逻辑
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
// 是否需要重绘
return true;
}
}
- 实现绘制逻辑
在paint
方法中,可以实现自定义的绘制逻辑。paint
方法接收两个参数:Canvas
对象和Size
对象。Canvas
对象提供了各种绘制方法,如绘制线、矩形、圆、文本等;Size
对象表示要绘制的区域的大小。
示例代码:
void paint(Canvas canvas, Size size) {
Paint paint = Paint()
..color = Colors.red
..strokeWidth = 5.0
..style = PaintingStyle.stroke;
canvas.drawLine(Offset(0, 0), Offset(size.width, size.height), paint);
canvas.drawRect(Rect.fromLTWH(0, 0, size.width / 2, size.height / 2), paint);
canvas.drawCircle(Offset(size.width / 2, size.height / 2), 50, paint);
}
- 实现
shouldRepaint
方法
shouldRepaint
方法用于决定在什么情况下需要重绘。通常,如果画笔的属性或者绘制数据发生改变时,需要返回true
以触发重绘。否则,返回false
以避免不必要的重绘。
示例代码:
bool shouldRepaint(CustomPainter oldDelegate) {
// 可以根据具体情况判断是否需要重绘
return oldDelegate != this;
}
- 添加动画和手势
根据需要,还可以在自定义绘制中添加动画和手势支持。例如,可以使用AnimationController
和Tween
来创建动画,并在paint
方法中根据动画值绘制;可以使用GestureDetector
来监听手势事件,并根据手势改变绘制。
总结一下,Flutter中的自定义绘制主要是通过创建CustomPaint
组件和自定义画笔类来实现的。在自定义画笔类中,需要重写paint
和shouldRepaint
方法来实现绘制逻辑和判断是否需要重绘。此外,还可以根据需求添加动画和手势支持。
下面我们就一起来实现一个自定义雷达图。
- 首先,引入所需的库:
import 'dart:math';
import 'package:flutter/material.dart';
- 创建一个自定义的雷达图组件
RadarChart
:
class RadarChart extends StatelessWidget {
final int numSides; // 边的数量
final List<double> values; // 每个角上的数值
final double maxValue; // 最大值,用于归一化数值
RadarChart({required this.numSides, required this.values, this.maxValue = 100.0});
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: RadarChartPainter(numSides: numSides, values: values, maxValue: maxValue),
child: Container(),
);
}
}
- 创建自定义绘制类
RadarChartPainter
,继承自CustomPainter
:
import 'dart:math';
import 'package:flutter/material.dart';
class RadarChartPainter extends CustomPainter {
final int numSides;
final List<double> values;
final double maxValue;
RadarChartPainter(
{required this.numSides, required this.values, this.maxValue = 100.0});
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final radius = min(size.width / 2, size.height / 2);
// 绘制雷达背景
drawRadarBackground(canvas, center, radius);
// 绘制雷达网格
drawRadarGrid(canvas, center, radius);
// 绘制雷达数据
drawRadarData(canvas, center, radius);
// 绘制数值文本
drawTextLabels(canvas, center, radius);
}
void drawRadarBackground(Canvas canvas, Offset center, double radius) {
final bgPaint = Paint()
..color = Colors.grey.withOpacity(0.3)
..style = PaintingStyle.stroke
..strokeWidth = 1.0;
final angleStep = 2 * pi / numSides;
for (int i = 0; i < numSides; i++) {
final x = center.dx + radius * cos(i * angleStep);
final y = center.dy + radius * sin(i * angleStep);
canvas.drawLine(center, Offset(x, y), bgPaint);
}
}
void drawRadarGrid(Canvas canvas, Offset center, double radius) {
final gridPaint = Paint()
..color = Colors.grey.withOpacity(0.3)
..style = PaintingStyle.stroke
..strokeWidth = 0.5;
final int gridLevel = 5; // 网格层数
final angleStep = 2 * pi / numSides;
final double gridRadiusStep = radius / gridLevel;
for (int i = 1; i <= gridLevel; i++) {
final currentRadius = gridRadiusStep * i;
final path = Path();
for (int j = 0; j < numSides; j++) {
final x = center.dx + currentRadius * cos(j * angleStep);
final y = center.dy + currentRadius * sin(j * angleStep);
if (j == 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
}
path.close();
canvas.drawPath(path, gridPaint);
}
}
void drawRadarData(Canvas canvas, Offset center, double radius) {
final dataPaint = Paint()
..color = Colors.blue
..style = PaintingStyle.fill;
final path = Path();
final angleStep = 2 * pi / numSides;
for (int i = 0; i < numSides; i++) {
final normalizedValue = values[i] / maxValue;
final x = center.dx + radius * normalizedValue * cos(i * angleStep);
final y = center.dy + radius * normalizedValue * sin(i * angleStep);
if (i == 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
}
path.close();
canvas.drawPath(path, dataPaint);
}
void drawTextLabels(Canvas canvas, Offset center, double radius) {
final textPainter = TextPainter(textDirection: TextDirection.ltr);
final angleStep = 2 * pi / numSides;
for (int i = 0; i < numSides; i++) {
final value = values[i].toStringAsFixed(1);
final x = center.dx + radius * cos(i * angleStep);
final y = center.dy + radius * sin(i * angleStep);
textPainter.text = TextSpan(
text: value, style: TextStyle(color: Colors.black, fontSize: 12.0));
textPainter.layout();
textPainter.paint(canvas,
Offset(x - textPainter.width / 2, y - textPainter.height / 2));
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
- 使用
RadarChart
组件:
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Radar Chart')),
body: Center(
child: SizedBox(
width: 300,
height: 300,
child: RadarChart(numSides: 5, values: [50, 70, 90, 60, 80]),
),
),
),
),
);
}
我们创建了一个RadarChart
组件,可以自定义边的数量和每个角上的数值。
通过自定义RadarChartPainter
类,我们实现了绘制雷达背景、雷达数据和数值文本的功能。
看看我们实现的效果:
老子讲:“天下难事必作于易,天下大事必作于细。”,你我共勉之!