Flutter CustomPaint

Flutter CustomPaint 完全解析

CustomPaint 是 Flutter 中用于自定义绘制的核心组件,它允许你通过代码直接在画布上绘制图形、文字、路径等,实现各种自定义的 UI 效果(比如图表、自定义形状、手绘效果等)。

一、核心概念与基础用法

1. 核心组件
  • CustomPaint:承载绘制内容的容器
  • CustomPainter:绘制逻辑的核心类(需要继承并实现 paintshouldRepaint 方法)
  • Canvas:画布,提供各种绘制方法(画圆、画线、画路径等)
  • Size:绘制区域的尺寸
2. 基础示例(绘制一个带边框的圆形)

dart

复制代码
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('CustomPaint 示例')),
        body: Center(
          // 核心容器:CustomPaint
          child: CustomPaint(
            size: const Size(200, 200), // 绘制区域大小
            painter: MyCustomPainter(), // 自定义绘制逻辑
          ),
        ),
      ),
    );
  }
}

// 自定义绘制类:继承 CustomPainter
class MyCustomPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // 1. 创建画笔(设置颜色、宽度、样式等)
    final paint = Paint()
      ..color = Colors.blue // 填充色
      ..style = PaintingStyle.fill; // 填充模式(stroke 为描边)

    // 2. 绘制圆形(圆心在画布中心,半径为画布宽度的一半)
    canvas.drawCircle(
      Offset(size.width / 2, size.height / 2), // 圆心坐标
      size.width / 2, // 半径
      paint, // 画笔
    );

    // 3. 绘制边框(重新设置画笔样式)
    final borderPaint = Paint()
      ..color = Colors.red
      ..style = PaintingStyle.stroke
      ..strokeWidth = 4; // 描边宽度

    canvas.drawCircle(
      Offset(size.width / 2, size.height / 2),
      size.width / 2,
      borderPaint,
    );
  }

  // 决定是否需要重绘(true=重绘,false=不重绘)
  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    // 简单场景直接返回 false(无状态绘制)
    // 有状态时对比新旧数据,数据变化返回 true
    return false;
  }
}

二、常用绘制方法

Canvas 提供了丰富的绘制 API,以下是最常用的:

表格

方法 功能 示例
drawCircle 绘制圆形 canvas.drawCircle(Offset(100,100), 50, paint)
drawRect 绘制矩形 canvas.drawRect(Rect.fromLTWH(50,50,100,80), paint)
drawLine 绘制直线 canvas.drawLine(Offset(0,0), Offset(200,200), paint)
drawPath 绘制自定义路径 见下文示例
drawText 绘制文字(需配合 TextPainter) 见下文示例
示例 1:绘制自定义路径(三角形)

dart

复制代码
@override
void paint(Canvas canvas, Size size) {
  final paint = Paint()
    ..color = Colors.green
    ..style = PaintingStyle.fill;

  // 创建路径
  final path = Path()
    ..moveTo(size.width / 2, 0) // 起点(顶部)
    ..lineTo(0, size.height) // 连线到左下角
    ..lineTo(size.width, size.height) // 连线到右下角
    ..close(); // 闭合路径(回到起点)

  canvas.drawPath(path, paint);
}
示例 2:绘制文字

dart

复制代码
@override
void paint(Canvas canvas, Size size) {
  // 创建 TextPainter
  final textPainter = TextPainter(
    text: const TextSpan(
      text: '自定义文字',
      style: TextStyle(color: Colors.black, fontSize: 24),
    ),
    textDirection: TextDirection.ltr,
  );

  // 布局文字(计算尺寸)
  textPainter.layout();

  // 绘制文字(居中显示)
  textPainter.paint(
    canvas,
    Offset(
      (size.width - textPainter.width) / 2,
      (size.height - textPainter.height) / 2,
    ),
  );
}

三、进阶技巧

1. 有状态绘制(动态更新)

如果需要根据数据动态更新绘制内容,需:

  • CustomPainter 中接收数据参数
  • shouldRepaint 对比新旧数据,变化时返回 true

示例:

dart

复制代码
class DynamicPainter extends CustomPainter {
  final double progress; // 动态参数(比如进度值 0-1)

  DynamicPainter({required this.progress});

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.orange;
    // 绘制进度条(宽度随 progress 变化)
    canvas.drawRect(
      Rect.fromLTWH(0, 0, size.width * progress, size.height),
      paint,
    );
  }

  @override
  bool shouldRepaint(DynamicPainter oldDelegate) {
    // 对比进度值,变化则重绘
    return oldDelegate.progress != progress;
  }
}

// 使用时通过 setState 更新
// setState(() => _progress += 0.1);
// CustomPaint(painter: DynamicPainter(progress: _progress))
2. 叠加绘制(foregroundPainter)

CustomPaint 提供 foregroundPainter 参数,可在子组件上层绘制:

dart

复制代码
CustomPaint(
  size: const Size(200, 200),
  painter: MyBackgroundPainter(), // 底层绘制
  foregroundPainter: MyForegroundPainter(), // 上层绘制
  child: const Center(child: Text('子组件')), // 中间的子组件
)
3. 性能优化
  • 避免在 paint 方法中创建对象(比如 PaintPath),建议提前初始化
  • shouldRepaint 尽量精准(避免不必要的重绘)
  • 复杂绘制可使用 RepaintBoundary 隔离重绘区域

四、前置条件

使用 CustomPaint 无需额外依赖,只需导入 Flutter 核心包:

dart

复制代码
import 'package:flutter/material.dart';

总结

  1. CustomPaint 是 Flutter 自定义绘制的核心,通过 CustomPainter 实现绘制逻辑,Canvas 提供具体绘制方法;
  2. 核心步骤:定义 CustomPainter 实现 paint(绘制逻辑)和 shouldRepaint(重绘判断),再通过 CustomPaint 组件承载;
  3. 进阶场景可通过接收动态参数实现状态更新,或使用 foregroundPainter 实现多层绘制,注意性能优化(减少不必要的对象创建和重绘)。
相关推荐
快手技术5 小时前
快手广告系统全面迈入生成式推荐时代!GR4AD:从Token到Revenue的全链路重构
前端·后端
前端Hardy5 小时前
大厂都在偷偷用的 Cursor Rules 封装!告别重复 Prompt,AI 编程效率翻倍
前端·javascript·面试
kyriewen5 小时前
Vite:比Webpack快100倍的“闪电侠”,原理竟然这么简单?
前端·javascript·vite
竹林8185 小时前
RainbowKit快速集成多链钱包连接:从“连不上”到丝滑切换的踩坑实录
前端·javascript
小小小小宇6 小时前
前端看go并发
前端
Jasmine_llq6 小时前
《B3840 [GESP202306 二级] 找素数》
开发语言·c++·试除法·顺序输入输出算法·素数判定算法·枚举遍历算法·布尔标记算法
前端Hardy6 小时前
Cursor Rules 完全指南(2026 最新版)
前端·javascript·面试
程序员陆业聪6 小时前
微前端状态管理的真相:Module Federation + 跨应用通信实战
前端
梁山好汉(Ls_man)6 小时前
鸿蒙_ArkTS解决Duplicate function implementation错误
开发语言·华为·typescript·harmonyos·鸿蒙
xiaoshuaishuai86 小时前
Git二分法定位Bug
开发语言·python