Flutter 中创建一个绘图画布

2024-06-13 10:33:13 浏览数 (2)

原文链接:Creating a Drawing Canvas in Flutter - 原文作者 Zaki

本文采用意译的方式

1_qTFwGWxqLCCaxg93EhBuig.webp1_qTFwGWxqLCCaxg93EhBuig.webp

Flutter 中创建绘图应用程序是一个有益的过程,可以将用户交互和图像渲染相结合。在本文,我们将手把手构建一个简单的绘图画布,在画布上用户可以在画布上使用手指自由绘画并选择不同颜色的画笔。

最终效果

final_APP.webpfinal_APP.webp

步骤一:设置 Flutter 环境

在开始编码前,我们需要确保自己系统上安装了 Flutter。我们可以从 Flutter 官方站点下载并安装 Flutter

步骤二:创建一个新的 Flutter 项目

打开我们的终端,然后跑下面的命令行来创建一个新的 Flutter 项目:

代码语言:javascript复制
flutter create drawing_app

导航到我们项目目录:

代码语言:javascript复制
cd drawing_app

步骤三:添加依赖

对于我们 drawing_app 项目,我们需要 flutter_colorpicker 包,以允许用户来挑选颜色。在 pubspec.yaml 的属性 dependencies 下添加下面内容:

代码语言:javascript复制
dependencies:
  flutter:
    sdk: flutter
  flutter_colorpicker: any

运行 flutter pub get 来安装新的依赖。

步骤四:主要应用入口

打开 main.dart 文件,然后设置程序的主要入口:

代码语言:javascript复制
import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart'

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

步骤五:创建 MyApp 挂件

定义 MyApp 关键,它将主页设置在 MaterialApp 中:

代码语言:javascript复制
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Drawing App',
      home: DrawingPage(),
    );
  }
}

步骤六:设置 DrawingPage 挂件

创建有状态的挂件 DrawingPage,在那里绘制画布:

代码语言:javascript复制
class DrawingPage extends StatefulWidget {
  const DrawingPage({super.key});
  
  @override
  _DrawingPageState createState() => _DrawingPageState();
}

步骤七:管理绘制状态

_DrawingPageState 中,管理绘制点,选定颜色和描边宽度的状态:

代码语言:javascript复制
class _DrawingPageState extends State<DrawingPage> {
  List<DrawingPoints> points = [];
  Color selectedColor = Colors.black;
  double strokeWidth = 4.0;
  List<Color> colorOptions = [
    Colors.black,
    Colors.red,
    Colors.green,
    Colors.blue,
    Colors.yellow,
    Colors.purple,
    Colors.orange,
    Colors.brown,
  ]; // 自定义颜色列表

步骤八:构建 UI

定义一个 AppBar 来进行控制,和 GestureDetector 来处理绘制手势:

代码语言:javascript复制
@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Draw on Canvas"),
        actions: <Widget>[
          IconButton(
            icon: const Icon(Icons.clear),
            onPressed: () => setState(() => points.clear()),
          ),
          IconButton(
            icon: const Icon(Icons.color_lens),
            onPressed: pickColor,
          ),
        ],
      ),
      body: GestureDetector(
        onPanUpdate: (details) {
          RenderBox? renderBox = context.findRenderObject() as RenderBox?;
          if (renderBox != null) {
            setState(() {
              points.add(
                DrawingPoints(
                  points: renderBox.globalToLocal(details.localPosition),
                  paint: Paint()
                    ..strokeCap = StrokeCap.round
                    ..isAntiAlias = true
                    ..color = selectedColor
                    ..strokeWidth = strokeWidth,
                  isPoint: true,
                ),
              );
            });
          }
        },
        onPanEnd: (details) {
          setState(() {
            points.add(DrawingPoints(
                points: Offset.zero, paint: Paint(), isPoint: false));
          });
        },
        child: CustomPaint(
          painter: DrawingPainter(points: points),
          child: Container(),
        ),
      ),
    );
  }

步骤九:处理颜色变更

实现 pickerColor 来显示颜色拾取器,以允许用户更改画笔的颜色:

代码语言:javascript复制
void pickColor() {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: const Text('Choose a color'),
          content: SingleChildScrollView(
            child: BlockPicker(
              pickerColor: selectedColor,
              availableColors: colorOptions, // 在这使用自定义颜色列表
              onColorChanged: changeColor,
            ),
          ),
          actions: <Widget>[
            TextButton(
              child: const Text('Close'),
              onPressed: () {
                Navigator.of(context).pop();
              },
            ),
          ],
        );
      },
    );
  }

void changeColor(Color color) {
    Navigator.of(context).pop(); // 关闭对话框
    setState(() {
      selectedColor = color;
    });
  }
}

步骤十:实现绘制逻辑

使用 CustomPainter 来处理真实的绘制逻辑:

代码语言:javascript复制
class DrawingPoints {
  Paint paint;
  Offset points;
  bool isPoint;

DrawingPoints({
    required this.points,
    required this.paint,
    this.isPoint = true,
  });
}
class DrawingPainter extends CustomPainter {
  List<DrawingPoints> points;
  DrawingPainter({required this.points});
  @override
  void paint(Canvas canvas, Size size) {
    for (int i = 0; i < points.length - 1; i  ) {
      if (points[i].isPoint &&
          points[i   1].isPoint) {
        canvas.drawLine(
            points[i].points, points[i   1].points, points[i].paint);
      }
    }
  }
  @override
  bool shouldRepaint(covariant DrawingPainter oldDelegate) => true;
}

类: DrawingPoints

目的:在画布中展示单个点。

字段:

  • Offset points:表示点在画布上的坐标。
  • Paint paint:指定此点要使用的绘画风格(颜色、绘制等)。
  • bool isPoint:布尔值,决定是否应该将对象视为绘制的点。这可能用于根据上下文或者触摸交互类型以不同方式处理触摸事件(例如,绘制一个点而不是一条线)。
  • Constructor:明确需要提供的 pointspaint。除非指定,否则 isPoints 默认是 true

类: DrawingPainter

目的:自定义画家类是基于 DrawingPoints 列表在画布上绘图。

字段:

  • List<DrawingPoints> pointsDrawingPoints 列表定义我们想要在画布上绘制的点。
  • Constructor:初始化 points 列表数据。
  • 方法:
    • paint(Canvas canvas, Size size):当挂件需要重绘时候调用。如果当前点和下一个点标记为可绘制(isPoint 为真),此方法遍历列表并从每个点到下一个点绘制一条线。
    • 它使用 Canvas 对象中的 drawLine 方法,使用 DrawingPoints 中指定的绘制样式在连续点之间进行连线。
    • shouldRepaint(covariant DrawingPainter oldDelegate):总是返回 true,表明每次都要更新绘画。如果点列表不频繁更改,这不是性能最优的选择,因为即使没有必要也会重新绘制。

paint 方法的逻辑

paint 方法的逻辑本质上是在连续的点之间绘线,这些点应该是 isPointtrue 的点。如果点不是连续的,即 isPointfalse,则跳过绘制到下一个点。这是处理用户手指抬离屏幕然后触屏生成另一个点绘制不连续点的简单方法。

步骤十一:测试应用

在终端上运行 flutter run 来运行我们的程序,或者使用 IDE 的运行按钮。我们应该可以在屏幕上绘制并且更改画笔

0 人点赞