flutter的自定义系列雷达图

自定义是flutter进阶中不可缺少的ui层知识点,这里我们来总结下:

在Flutter中,自定义绘制通常是通过使用CustomPaintCustomPainter来实现的。

  1. 创建CustomPaint组件

首先,需要创建一个CustomPaint组件。CustomPaint是一个Widget,它可以作为其他组件的子组件,用于提供自定义绘制的功能。创建CustomPaint时可以指定其子组件、前景画笔和背景画笔。

示例代码:

CustomPaint(
  painter: BackgroundPainter(),
  foregroundPainter: ForegroundPainter(),
  child: Container(),
)

这里BackgroundPainterForegroundPainter是自定义的画笔类,需要继承CustomPainter

  1. 创建自定义画笔类

接下来,需要创建自定义的画笔类。自定义画笔类需要继承CustomPainter类,并重写paintshouldRepaint方法。paint方法用于实现绘制逻辑,而shouldRepaint方法用于决定在什么情况下需要重绘。

示例代码:

class BackgroundPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // 绘制逻辑
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    // 是否需要重绘
    return true;
  }
}
  1. 实现绘制逻辑

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);
}
  1. 实现shouldRepaint方法

shouldRepaint方法用于决定在什么情况下需要重绘。通常,如果画笔的属性或者绘制数据发生改变时,需要返回true以触发重绘。否则,返回false以避免不必要的重绘。

示例代码:

bool shouldRepaint(CustomPainter oldDelegate) {
  // 可以根据具体情况判断是否需要重绘
  return oldDelegate != this;
}
  1. 添加动画和手势

根据需要,还可以在自定义绘制中添加动画和手势支持。例如,可以使用AnimationControllerTween来创建动画,并在paint方法中根据动画值绘制;可以使用GestureDetector来监听手势事件,并根据手势改变绘制。

总结一下,Flutter中的自定义绘制主要是通过创建CustomPaint组件和自定义画笔类来实现的。在自定义画笔类中,需要重写paintshouldRepaint方法来实现绘制逻辑和判断是否需要重绘。此外,还可以根据需求添加动画和手势支持。

下面我们就一起来实现一个自定义雷达图。

  1. 首先,引入所需的库:
import 'dart:math';
import 'package:flutter/material.dart';
  1. 创建一个自定义的雷达图组件RadarChart
class RadarChart extends StatelessWidget {
  final int numSides; // 边的数量
  final List 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(),
    );
  }
}
  1. 创建自定义绘制类RadarChartPainter,继承自CustomPainter
import 'dart:math';

import 'package:flutter/material.dart';

class RadarChartPainter extends CustomPainter {
  final int numSides;
  final List 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;
  }
}
  1. 使用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类,我们实现了绘制雷达背景、雷达数据和数值文本的功能。

看看我们实现的效果:

flutter的自定义系列雷达图_第1张图片

老子讲:“天下难事必作于易,天下大事必作于细。”,你我共勉之!

你可能感兴趣的:(flutter,android,动画)