Flutter 中创建一个绘图画布

原文链接:Creating a Drawing Canvas in Flutter - 原文作者 Zaki
本文采用意译的方式

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

最终效果

步骤一:设置 Flutter 环境

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

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

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

bash 复制代码
flutter create drawing_app

导航到我们项目目录:

bash 复制代码
cd drawing_app

步骤三:添加依赖

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

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter
  flutter_colorpicker: any

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

步骤四:主要应用入口

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

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

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

步骤五:创建 MyApp 挂件

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

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

步骤六:设置 DrawingPage 挂件

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

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

步骤七:管理绘制状态

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

dart 复制代码
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 来处理绘制手势:

dart 复制代码
@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 来显示颜色拾取器,以允许用户更改画笔的颜色:

dart 复制代码
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 来处理真实的绘制逻辑:

dart 复制代码
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 的运行按钮。我们应该可以在屏幕上绘制并且更改画笔🖌️的颜色。

总结

现在,我们使用 Flutter 成功地创建了一个基础的绘图应用!这个应用允许我们在屏幕上选择颜色来绘制,并且清空绘制。我们可以通过添加更多的特性来扩展,比如调整画笔的大小,保存绘图或者添加更加复杂的手势。

这个教程为在 Flutter 中创建交互式图形应用程序提供了坚实的基础。尝试更多的功能并自定义来扩展我们应用程序的能力。

相关推荐
ekskef_sef28 分钟前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端
sunshine6411 小时前
【CSS】实现tag选中对钩样式
前端·css·css3
真滴book理喻1 小时前
Vue(四)
前端·javascript·vue.js
蜜獾云1 小时前
npm淘宝镜像
前端·npm·node.js
dz88i81 小时前
修改npm镜像源
前端·npm·node.js
Jiaberrr1 小时前
解锁 GitBook 的奥秘:从入门到精通之旅
前端·gitbook
顾平安3 小时前
Promise/A+ 规范 - 中文版本
前端
聚名网3 小时前
域名和服务器是什么?域名和服务器是什么关系?
服务器·前端
桃园码工3 小时前
4-Gin HTML 模板渲染 --[Gin 框架入门精讲与实战案例]
前端·html·gin·模板渲染
不是鱼3 小时前
构建React基础及理解与Vue的区别
前端·vue.js·react.js