用 Flutter 实现画板

news/2024/7/20 22:29:26 标签: flutter, ios, xcode, android, macos

本文作者为 360 奇舞团前端开发工程师

一、前期准备

可以按照flutter 官网搭建下相应的环境,这里大概讲解下用 fvm 搭建 flutter 环境。

MacOS 使⽤ fvm 管理多个 flutter 版本

  1. 安装fvm

    # 使用 brew 安装
    brew tap leoafarias/fvm
    brew install fvm
    # 使用 pub package 安装
    dart pub global activate fvm

    安装成功后命令行里会给出 fvm 目前安装路径 $PATH":"$HOME/.pub-cache/bin"

  2. 配置

    # .bash_profile 中添加
    export PATH = "$PATH":"$HOME/.pub-cache/bin"
    # 使.bash_profile 生效
    source .bash_profile
    # 执行 fvm 命令,看是否出现相关 help
  3. fvm 相关命令

  • 配置 fvm 缓存路径fvm config --cache-path <CACHE_PATH>

  • 查看当前安装的版本fvm list

  • 安装指定版本的 flutterfvm install version

  • 删除指定版本的 flutterfvm remove version

  • 指定当前使用哪个版本fvm use version

ps:切换 flutter 版本后需要敲下 fvm flutter doctor。因为每个版本不一样,所以可能需要重新下载运行环境

安装独立的 dart 环境

  1. 安装

brew tap dart-lang/dart
 brew install dart
  1. 更新

brew upgrade dart
  1. 重新安装

brew reinstall dart

创建初始项目

fvm flutter create flutter_painter
cd flutter_painter
fvm flutter run

输入fvm flutter run后控制台会提示想选择用哪种设备进行调试,也可以使用flutter devices直接查询设备,flutter run -d all在所有设备上运行项目,我这里选择了用 web chrome 打开项目fvm flutter run -d Chrome。或者 mac 有 Xcode 的话,使用命令open -a Simulator可以直接打开 iOS 模拟器,然后执行flutter run,在执行前确保没有其他模拟器正在运行,这样项目就可以直接运行在 ios 模拟器上。ad2023dab36a3dc9050c012e81b10b92.png

二、了解 Path 与 CustomPainter

CustomPainter, 跟canvas一样,在 flutter 里我们可以用这个类绘制各种自定义图形。在这个类里我们需要实现 paint() 绘制方法与 shouldRepaint() 判断刷新布局的时是否需要重绘。

class SimplePainter extends CustomPainter {
    @override
    void paint(Canvas canvas, Size size) {
        // 绘制代码
        Paint paint  = Paint()
            ..color = Colors.black
            ..style = PaintingStyle.stroke
            ..strokeWidth = 5.0;
        Path path = Path();
        // 对路径进行相关操纵
        ...
        path.close();
        canvas.drawPath(path, paint);
    }

    @override
    bool shouldRepaint(CustomPainter oldDelegate) {
        return true
    }
}

具体的绘制由 canvas 画布和 paint 画笔来实现的。
canvas 相关方法:

方法功能
drawLine()画直线
drawCircle()画圆
drawOval()画椭圆
drawRect()画矩形
drawPoints()画点
drawArc()画圆弧

Paint 类参数:

属性名类型参考值功能
colorColorsColors.black画笔颜色
strokeWidthdouble5.0画笔粗细
strokeCapStrokeCapStrokeCap.round画笔笔触类型
isAntiAliasbooltrue是否启动抗锯齿

path 对象是需要绘制的元素集合,相关方法:

方法功能
moveTo()绘制的起点移到指定位置
lineTo()从起点绘制一条直线到指定位置
quadraticBezierTo()绘制二阶贝塞尔曲线
addRect()画矩形
addOval()画椭圆

实践

import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(title: '画板', home: SafeArea(child: CanvasPage())));
}

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

  @override
  State<CanvasPage> createState() => _CanvasPageState();
}

class _CanvasPageState extends State<CanvasPage> {
  Path path = Path();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('画板')),
      body: Listener(
          // 落下
          onPointerDown: (e) {
            path.moveTo(e.localPosition.dx, e.localPosition.dy);
            setState(() {});
          },
          // 移动
          onPointerMove: (e) {
            path.lineTo(e.localPosition.dx, e.localPosition.dy);
            setState(() {});
          },
          // 离开
          onPointerUp: (e) {
            path.moveTo(e.localPosition.dx, e.localPosition.dy);
            path.close();
            setState(() {});
          },
          child: CustomPaint(foregroundPainter: CanvasPaint(path: path), child: Container(color: Colors.transparent))),
    );
  }
}

class CanvasPaint extends CustomPainter {
  Path? path;
  Color? color; // 画笔颜色
  double? width;

  CanvasPaint({required this.path, this.color = Colors.black, this.width = 5});

  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint()
      ..color = color!
      ..strokeWidth = width!
      ..strokeCap = StrokeCap.round
      ..style = PaintingStyle.stroke;
    canvas.drawPath(path!, paint);
  }

  // 是否需要重新绘制
  @override
  bool shouldRepaint(covariant CanvasPaint oldDelegate) {
    // return oldDelegate.path != path;
    return true;
  }
}

三、部署到 chrome 插件

  1. 找到 web/index.html 文件并删除所有 <script>...</script> 标记:

30e6d7f7a9d7fc25a73f1bc67c4dbb81.png
  1. <body>下插入<script> 标签

<script src="main.dart.js" type="application/javascript"></script>
  1. 设置扩展程序尺寸,在 html 标签内添加宽高

<html style="height: 600px; width: 300px">
  1. manifest.json中更改配置

{
    "name": "flutter_painter_plugin",
    "short_name": "flutter_painter_plugin",
    "version": "1.0.0",
    "content_security_policy": {
        "extension_pages": "script-src 'self'; object-src 'self'"
    },
    "action": {
        "default_popup": "index.html",
        "default_icon": "icons/Icon-192.png"
    },
    "manifest_version": 3
}
  1. 构建拓展程序,需要满足 csp 限制,生成的文件在build/web文件夹下

fvm flutter build web --web-renderer html --csp
  1. 运行拓展程序

(1)打开谷歌扩展程序页面

chrome://extensions/

(2) 选择开发者模式,选中加载已解压的扩展程序

8af303c326f748c79b30b9c366111d30.png(3) 选择 build/web 文件夹,就可以看到新的扩展程序

b7da0b895ab5b90cc70cfb3a1053fd22.png d2705605b5eabffe181b79d753491038.gif

参考资料

谷歌扩展程序文档
flutter 文档

- END -

关于奇舞团

奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

e2f0ce2162dc61b92c8c410d8eb3b2f1.png


http://www.niftyadmin.cn/n/151258.html

相关文章

(九)版本控制规范

目录 1、Git工作流 2、commit规范 1、Git工作流 初步定的是基于GitFlow工作流来做 &#xff08;1&#xff09;feature分支 进入一个版本的开发之后&#xff0c;每个人自己拉对应的feature分支。feature分支的拉取&#xff0c;按照一个子系统一个feature分支&#xff1b;一个…

Cadence Allegro 导出Missing Fillets Report报告详解

⏪《上一篇》   🏡《上级目录》   ⏩《下一篇》 目录 1,概述2,Missing Fillets Report作用3,Missing Fillets Report示例3.1,PAD and Fillet parameters3.2,Missing Fillets4,Missing Fillets Report导出方法4.1,方法1:4.2,方法2:<

js-闭包(函数)

尾调优化 函数柯里化 防抖 节流 局部作用域,全局,块级,其中局部又叫函数作用域; 函数执行时所在的作用域&#xff0c;是定义时的作用域&#xff0c;而不是调用时所在的作用域。闭包利用的就是函数的作用域原理 function foo() {var x 1;function bar() {console.log(x);}retur…

Vue中MVVM模型详解

Vue中MVVM模型详解前言一、MVVM分析二、MVVM注意事项总结前言 MVVM模型是前端使用的比较多的一个模型&#xff0c;几乎所有的前端框架都是MVVM模型 一、MVVM分析 MVVM分为M&#xff0c;VM&#xff0c; V M&#xff1a;模型(Model) &#xff1a;data中的数据V&#xff1a;视图(V…

深度解析单例模式

饥汉模式 package com.cz.single;/*** author 卓亦苇* version 1.0* 2023/3/11 21:31*/ public class Hungry {private byte[] data1 new byte[1024];private byte[] data2 new byte[1024];private byte[] data3 new byte[1024];private byte[] data4 new byte[1024];priv…

c++11 标准模板(STL)(std::unordered_map)(十三)

定义于头文件 <unordered_map> template< class Key, class T, class Hash std::hash<Key>, class KeyEqual std::equal_to<Key>, class Allocator std::allocator< std::pair<const Key, T> > > class unordered…

nacos架构和原理(五)——Nacos 内核设计之寻址机制

nacos架构和原理&#xff08;五&#xff09;——Nacos 内核设计之寻址机制背景设计内部实现单机寻址文件寻址地址服务器寻址未来可扩展点集群节点自动扩缩容背景 Nacos 支持单机部署以及集群部署&#xff0c;针对单机模式&#xff0c;Nacos 只是自己和自己通信&#xff1b;对于…

[Linux命令] man命令

man命令查看系统帮助手册&#xff08;manual&#xff09; 基本的用法&#xff1a; $ man lsLS(1) User Commands LS(1)NAMEls - list directory contentsSYNOPSISls [OPTION].…