Flutter自定义绘图插件custom_flutter_painter的使用

Flutter 自定义绘图插件 custom_flutter_painter 的使用

概要

Flutter Painter 提供了一个可以用于绘图的 widget。目前它支持:

  • 自由式绘画:使用任意宽度和颜色进行涂鸦。
  • 对象:你可以轻松地移动、缩放和旋转的对象,例如:
    • 文本:带有任意 TextStyle 的文本。
    • 形状:如直线、箭头、椭圆和矩形,带有任意 Paint
    • 图像:可以翻转的图像。
  • 自由式橡皮擦:可以擦除任何你不想在画布上保留的部分。

这些被称为 可绘制对象(drawables)。

你还可以为你的绘图设置背景颜色或背景图像,并导出你的绘图作为图像。

示例

你可以查看示例标签来了解如何使用该包。

示例项目托管在 这里,如果你想亲自尝试!

一个展示示例运行的视频记录:

Flutter Painter 视频演示

使用

首先,你需要一个 PainterController 对象。PainterController 控制不同的可绘制对象、背景以及向 FlutterPainter widget 提供所需的设置。然后,在你的 UI 中使用 FlutterPainter widget 并分配控制器。

基本用法

class ExampleWidget extends StatefulWidget {
  const ExampleWidget({Key? key}) : super(key: key);

  @override
  _ExampleWidgetState createState() => _ExampleWidgetState();
}

class _ExampleWidgetState extends State<ExampleWidget> {
  PainterController controller = PainterController();

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: 300,
      height: 300,
      child: FlutterPainter(
        controller: controller,        
      ),
    );
  }
}

使用 builder 构造函数

FlutterPainter.builder 构造函数使用一个构建器方法,当控制器发生变化时会自动更新,而无需使用 setState、回调或监听器。然而,这将比 StatefulWidget 表现更差,因为它会更频繁地重建,因此建议仅在依赖于 PainterController 的 widget 树简单的情况下使用。

class ExampleWidget extends StatefulWidget {
  const ExampleWidget({Key? key}) : super(key: key);

  @override
  _ExampleWidgetState createState() => _ExampleWidgetState();
}

class _ExampleWidgetState extends State<ExampleWidget> {
  PainterController controller = PainterController();

  @override
  Widget build(BuildContext context) {
    return FlutterPainter.builder(
      controller: controller,
      builder: (context, painter){
        return SizedBox(
          width: 300,
          height: 300,
          child: painter
        );
      }
    ); 
  }
}

注意FlutterPainter 不会定义其自身的大小约束,因此建议使用可以为其子项提供大小约束的 widget,如 SizedBoxAspectRatio

注意:如果多个 UI 部分依赖于 PainterController,你可以使用 ValueListenableBuilder,并将 valueListenable 设置为你的控制器,这样每次控制器更新时都会自动重新构建。这是示例项目中使用的策略。

回调

FlutterPainter 有一些有用的回调,当内部变化发生时会被调用。

  • onDrawableCreated: 当从 FlutterPainter 创建一个可绘制对象时被调用。传递该对象作为参数。
  • onDrawableDeleted: 当从 FlutterPainter 删除一个可绘制对象时被调用。传递该对象作为参数。
  • onSelectedObjectDrawableChanged: 当选择的对象可绘制对象改变时被调用。这对于你想显示一些 UI 来编辑对象的属性很有用。传递所选对象可绘制对象作为参数。
    • 如果可绘制对象被更新(例如移动),则传递的可绘制对象将变得无效。确保使用 PainterController.selectedObjectDrawable 获取所选可绘制对象的最新值。
  • onPainterSettingsChanged: 当 PainterController 的设置从 FlutterPainter 本身更改时被调用。传递新的设置作为参数。

PainterController

PainterController 是 Flutter Painter 运作的核心。它控制 FlutterPainter 的设置、背景以及所有可绘制对象,以及所选对象可绘制对象。

PainterController 上的所有 setter 直接通知你的 FlutterPainter 进行响应并重绘。如果你使用的是 FlutterPainter.builder,构建器会自动调用以构建 widget 树。如果不使用,确保使用 setState 并监听回调。

注意:如果你使用了多个画家,确保每个 FlutterPainter widget 都有自己的 PainterController不要 将相同的控制器用于多个画家。

设置

当前有三种类型的设置:

  • freeStyleSettings: 它们控制用于绘制涂鸦的参数,如宽度和颜色。它还有一个字段可以启用或禁用涂鸦,以防止用户在 FlutterPainter 上绘制。
  • textSettings: 它们主要控制绘制文本的 TextStyle。它还包含一个焦点节点字段,允许你检测用户何时开始和停止编辑文本。
  • objectSettings: 这些设置控制可以移动、缩放和旋转的对象。文本、形状和图像都被视为对象。它控制布局辅助,允许对齐对象和以直角旋转它们,以及有关对象控件的设置,用于缩放、旋转和调整大小。
  • shapeSettings: 这些控制用于创建形状的画笔和形状工厂,以及形状是否一次性绘制还是连续绘制。
  • scaleSettings: 这些设置控制画家上的缩放(放大/缩小)。默认情况下,缩放功能是禁用的。

你可以在 PainterController 的构造函数中通过 settings 参数提供初始设置。

每个设置和子设置都有扩展的 setter 和 getter,你可以使用它们来读取和修改该设置的值。

例如,这是如何修改自由式绘制的描边宽度:

void setStrokeWidth(double value){
  controller.freeStyleStrokeWidth = value;
}

注意:如果你不使用扩展库,则需要注意所有设置对象都是不可变的,不能直接修改,因此为了更改某些设置,你必须创建当前设置的一个副本并应用所需的变化(这类似于复制 ThemeData 的方式)。

背景

你还可以从控制器提供 FlutterPainter widget 的背景。你可以使用颜色或图像作为背景。

使用颜色

void setBackground(){
  // 将背景设置为黑色
  controller.background = Colors.black.backgroundDrawable;
}

使用图像

import 'dart:ui' as ui;
ui.Image? myImage;

void setBackground() async {
  // 从网络获取图像并创建一个 [ui.Image] 对象
  final ui.Image myImage = await NetworkImage('https://picsum.photos/960/720').image;
  // 将背景设置为图像
  controller.background = myImage.backgroundDrawable;
}

背景也可以直接从 PainterController 的构造函数赋值。

可绘制对象

所有绘制在 FlutterPainter 上的可绘制对象都存储和由 PainterController 控制。在大多数使用场景中,你不需要直接与可绘制对象交互。但是,你可以从代码中添加、插入、替换或删除可绘制对象(无需用户实际绘制它们)。

你可以从 PainterController 构造函数提供一个初始的可绘制对象列表,以初始化控制器。你也可以从控制器修改它们,但要小心,使用 PainterController 的方法,而不是直接修改 drawables 列表。

正确做法

void addMyDrawables(List<Drawable> drawables){
  controller.addDrawables(drawables);
}

错误做法

void addMyDrawables(List<Drawable> drawables){
  controller.drawables.addAll(drawables);
}

所选对象可绘制对象

PainterController 还提供了当前选定的 ObjectDrawable,通过 PainterController.selectedObjectDrawable 获取器字段。这个值会随着 UI 的变化保持最新(例如,用户选择了一个新的对象可绘制对象)。你也可以编程式地选择和取消选择一个对象可绘制对象,前提是它在控制器的可绘制对象列表中。

void selectObjectDrawable(ObjectDrawable drawable){
  controller.selectObjectDrawable(drawable);
}

void deselectObjectDrawable(){
  controller.deselectObjectDrawable();
}

所选对象可绘制对象也会在被替换或从控制器中移除时自动更新。

渲染图像

PainterController,你可以将 FlutterPainter 的内容渲染为 PNG 编码的 ui.Image 对象。为此,你需要提供输出图像的大小。所有的绘图都会根据该大小进行缩放。

ui.Image 对象,你可以将其转换为原始字节列表(Uint8List),以便使用 Image.memory 显示它或将它保存为文件。

Uint8List? renderImage(Size size) async {
  final ui.Image renderedImage = await controller.renderImage(size);
  final Uint8List? byteData = await renderedImage.pngBytes;
  return byteData;
}

更多关于Flutter自定义绘图插件custom_flutter_painter的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter自定义绘图插件custom_flutter_painter的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在Flutter中,自定义绘图通常涉及使用CustomPaint widget和Canvas类。虽然custom_flutter_painter不是一个官方的Flutter插件,但假设它是一个用于简化自定义绘图过程的第三方库,我们可以通过创建一个示例来展示如何在Flutter中使用自定义绘图插件。由于无法确切知道custom_flutter_painter的具体API,我将提供一个假设性的示例,展示如何创建一个自定义绘图插件并在Flutter应用中使用它。

假设的custom_flutter_painter插件使用示例

1. 创建自定义绘图逻辑

首先,假设我们有一个自定义的绘图逻辑,我们将这个逻辑封装在一个Dart文件中,比如my_custom_painter.dart

// my_custom_painter.dart
import 'package:flutter/material.dart';

class MyCustomPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.blue
      ..strokeWidth = 4.0
      ..style = PaintingStyle.stroke;

    // 画一个矩形
    final rect = Rect.fromLTWH(50, 50, 200, 100);
    canvas.drawRect(rect, paint);

    // 画一个圆
    final center = Offset(size.width / 2, size.height / 2);
    final radius = 50.0;
    canvas.drawCircle(center, radius, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}

2. 使用CustomPaint widget应用自定义绘图

然后,在Flutter应用的主文件中(比如main.dart),我们使用CustomPaint widget来应用这个自定义绘图。

// main.dart
import 'package:flutter/material.dart';
import 'my_custom_painter.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Custom Painting Example'),
        ),
        body: Center(
          child: CustomPaint(
            painter: MyCustomPainter(),
            size: Size(double.infinity, double.infinity),
          ),
        ),
      ),
    );
  }
}

3. 假设的custom_flutter_painter插件使用(伪代码)

如果我们假设custom_flutter_painter插件提供了一个更高级别的接口来简化自定义绘图,使用方式可能类似于下面这样(注意,这是伪代码,因为custom_flutter_painter的具体API未知):

// 假设的 custom_flutter_painter 使用方式
import 'package:flutter/material.dart';
import 'package:custom_flutter_painter/custom_flutter_painter.dart'; // 假设的导入

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Custom Flutter Painter Example'),
        ),
        body: Center(
          child: CustomFlutterPainter(
            // 假设的插件参数
            shapes: [
              CustomShape(
                type: ShapeType.rectangle,
                position: Offset(50, 50),
                size: Size(200, 100),
                color: Colors.blue,
                strokeWidth: 4.0,
              ),
              CustomShape(
                type: ShapeType.circle,
                center: Offset(300, 300), // 假设中心点
                radius: 50.0,
                color: Colors.blue,
                strokeWidth: 4.0,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

// 注意:CustomShape 和 ShapeType 是假设的类,用于说明插件可能的API。

由于custom_flutter_painter不是官方或广泛认可的库,上述假设性的代码提供了一个框架,说明如果这样一个库存在,它可能会如何被使用。在实际情况中,你需要查阅custom_flutter_painter的具体文档来了解如何正确导入和使用它。如果这是一个你计划创建或找到的第三方库,请确保遵循其提供的API和示例代码。

回到顶部