Flutter-自定义画板

效果

功能

  • 支持绘制线、圆、矩形,支持拓展
  • 支持撤回上一步
  • 支持清空画板
  • 支持自定义画笔颜色,宽度

实现

定义绘制类型

/// 类型
enum ShapeType {//线line,//圆circle,//矩形rectangle,//拓展
}

定义绘制抽象类

import 'dart:ui';/// 绘制抽象类
abstract class Shape {void draw(Canvas canvas,List<Offset> points,Paint paint,);
}

实现线、圆、矩形绘制

/// 绘制圆
class CircleShape implements Shape {@overridevoid draw(Canvas canvas, List<Offset> points, Paint paint) {if (points.length >= 2) {double radius = (points[points.length - 1] - points[1]).distance / 2;paint.style = PaintingStyle.stroke;canvas.drawCircle(points[0], radius, paint);}}
}/// 绘制线
class LineShape implements Shape {@overridevoid draw(Canvas canvas, List<Offset> points, Paint paint) {for (int i = 0; i < points.length - 1; i++) {canvas.drawLine(points[i], points[i + 1], paint);}}
}/// 绘制方
class RectangleShape implements Shape {@overridevoid draw(Canvas canvas, List<Offset> points, Paint paint) {if (points.length >= 2) {final rect = Rect.fromPoints(points[0], points[points.length - 1]);paint.style = PaintingStyle.stroke;canvas.drawRect(rect, paint);}}
}

定义工厂类 factory

/// 根据绘制类型返回具体绘制对象
Shape getShape(ShapeType type) {switch (type) {case ShapeType.line:return LineShape();case ShapeType.circle:return CircleShape();case ShapeType.rectangle:return RectangleShape();}
}

定义笔画参数对象

/// 笔画参数对象
class DrawingStroke {Color color;double width;List<Offset> points;ShapeType type;DrawingStroke({this.color = Colors.black,this.width = 2.0,this.points = const [],this.type = ShapeType.line,});
}

定义绘制控制器

/// 绘制控制器
class DrawingController {final _strokes = <DrawingStroke>[];final _listeners = <VoidCallback>[];// 所有绘制笔画数据List<DrawingStroke> get strokes => List.unmodifiable(_strokes);// 画笔颜色Color selectedColor = Colors.black;// 画笔宽度double strokeWidth = 2.0;// 绘制类型ShapeType selectedType = ShapeType.line;// 开始绘制void startDrawing(Offset point) {_strokes.add(DrawingStroke(color: selectedColor,width: strokeWidth,points: [point],type: selectedType,));_notifyListeners();}// 正在绘制void updateDrawing(Offset point) {if (_strokes.isNotEmpty) {_strokes.last.points.add(point);_notifyListeners();}}// 结束当前笔画绘制void endDrawing() {_notifyListeners();}// 撤回一笔void undo() {if (_strokes.isNotEmpty) {_strokes.removeLast();_notifyListeners();}}// 清空数据void clear() {_strokes.clear();_notifyListeners();}// 设置画笔颜色void setColor(Color color) {selectedColor = color;_notifyListeners();}// 设置画笔宽度void setStrokeWidth(double width) {strokeWidth = width;_notifyListeners();}// 设置绘制类型void setDrawingType(ShapeType type) {selectedType = type;_notifyListeners();}void _notifyListeners() {for (var listener in _listeners) {listener();}}void addListener(VoidCallback listener) {_listeners.add(listener);}void removeListener(VoidCallback listener) {_listeners.remove(listener);}
}

定义画板类DrawingBoard

class DrawingBoard extends StatefulWidget {final DrawingController controller;const DrawingBoard({Key? key, required this.controller}) : super(key: key);@overrideState<StatefulWidget> createState() => DrawingBoardState();
}class DrawingBoardState extends State<DrawingBoard> {@overridevoid initState() {super.initState();widget.controller.addListener(_updateState);}void _updateState() {setState(() {});}@overridevoid dispose() {super.dispose();widget.controller.removeListener(_updateState);}@overrideWidget build(BuildContext context) {return LayoutBuilder(builder: (context, size) {return SizedBox(width: size.maxWidth,height: size.maxHeight,child: GestureDetector(onPanStart: (details) {widget.controller.startDrawing(details.localPosition);},onPanUpdate: (details) {widget.controller.updateDrawing(details.localPosition);},onPanEnd: (_) {widget.controller.endDrawing();},child: CustomPaint(painter: DrawingPainter(strokes: widget.controller.strokes),size: Size.infinite,),),);});}
}class DrawingPainter extends CustomPainter {final Paint drawPaint = Paint();DrawingPainter({required this.strokes});List<DrawingStroke> strokes;@overridevoid paint(Canvas canvas, Size size) {for (var stroke in strokes) {drawPaint..color = stroke.color..strokeCap = StrokeCap.round..strokeWidth = stroke.width;Shape shape = getShape(stroke.type);shape.draw(canvas, stroke.points, drawPaint);}}@overridebool shouldRepaint(covariant CustomPainter oldDelegate) {return false;}
}
使用画板
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_xy/xydemo/drawingboard/drawing/type.dart';
import 'package:flutter_xy/xydemo/drawingboard/drawing/view.dart';import 'drawing/controller.dart';class DrawingPage extends StatefulWidget {const DrawingPage({Key? key}) : super(key: key);@overrideDrawingPageState createState() => DrawingPageState();
}class DrawingPageState extends State<DrawingPage> {final _controller = DrawingController();@overridevoid initState() {super.initState();SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft,DeviceOrientation.landscapeRight,]);}@overridevoid dispose() {super.dispose();SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp,DeviceOrientation.portraitDown,]);}@overrideWidget build(BuildContext context) {return Scaffold(body: SafeArea(child: Row(children: [SizedBox(width: 120,child: ListView(scrollDirection: Axis.vertical,padding: const EdgeInsets.symmetric(horizontal: 20),children: [const SizedBox(width: 10),_buildText("操作"),const SizedBox(width: 10),_buildButton('Undo', () => _controller.undo()),const SizedBox(width: 10),_buildButton('Clear', () => _controller.clear()),const SizedBox(width: 10),_buildText("画笔颜色"),const SizedBox(width: 10),_buildColorButton(Colors.red),const SizedBox(width: 10),_buildColorButton(Colors.blue),const SizedBox(width: 10),_buildText("画笔宽度"),const SizedBox(width: 10),_buildStrokeWidthButton(2.0),const SizedBox(width: 10),_buildStrokeWidthButton(5.0),const SizedBox(width: 10),_buildText("画笔类型"),const SizedBox(width: 10),_buildTypeButton(ShapeType.line, '线'),const SizedBox(width: 10),_buildTypeButton(ShapeType.circle, '圆'),const SizedBox(width: 10),_buildTypeButton(ShapeType.rectangle, '方'),],),),Expanded(child: Column(children: [Expanded(child: DrawingBoard(controller: _controller,),),],),),],),),);}Widget _buildText(String text) {return Text(text,style: const TextStyle(fontSize: 12,fontWeight: FontWeight.w600,),);}Widget _buildButton(String text, VoidCallback onPressed) {return ElevatedButton(onPressed: onPressed,child: Text(text),);}Widget _buildColorButton(Color color) {return ElevatedButton(onPressed: () => _controller.setColor(color),style: ElevatedButton.styleFrom(primary: color),child: const SizedBox(width: 30, height: 30),);}Widget _buildStrokeWidthButton(double width) {return ElevatedButton(onPressed: () => _controller.setStrokeWidth(width),child: Text(width.toString()),);}Widget _buildTypeButton(ShapeType type, String label) {return ElevatedButton(onPressed: () => _controller.setDrawingType(type),child: Text(label),);}
}

运行效果如下图:

详情见 github.com/yixiaolunhui/flutter_xy

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2981742.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

网络编程-libuv介绍

官网 https://libuv.org/ 概要 libuv是一个强大的跨平台异步I/O库&#xff0c;主要用于构建高性能、可扩展的网络应用程序。它最初是为Node.js开发的&#xff0c;用于处理Node.js的异步I/O操作&#xff0c;但随着时间的推移&#xff0c;它也被广泛应用于其他系统&#xff0…

Linux--忘记root密码解决办法

Linux忘记密码解决的方法有两种&#xff1a; 方法一&#xff1a; 第一步&#xff1a;打开虚拟机时&#xff0c;疯狂按方向键&#xff0c;让该虚拟机不进入系统停留在开机界面&#xff0c;按方向键使光标停留在第一行&#xff0c;按字母E编辑它&#xff0c;如 按E后&#xff0…

【AIGC】-如何看待AIGC技术?

&#x1f525;博客主页&#xff1a;西瓜WiFi &#x1f3a5;系列专栏&#xff1a;《大语言模型》 很多非常有趣的模型&#xff0c;值得收藏&#xff0c;满足大家的收集癖&#xff01; 如果觉得有用&#xff0c;请三连&#x1f44d;⭐❤️&#xff0c;谢谢&#xff01; 长期不…

Hive服务详解

Hive服务 HiveServer2、Hive Metastore 服务服务共同构成了 Hive 生态系统中的核心功能&#xff0c;分别负责管理元数据和提供数据查询服务&#xff0c;为用户提供了一个方便、高效的方式来访问和操作存储在 Hive 中的数据。 1. Hive 查询服务&#xff08;HiveServer2&#xf…

unity学习(89)——unity塞满c盘!--删除editor下的log文件

卸了一个视频后强制续命打开详细信息&#xff1a; 这个再往下找也是没用的&#xff01; 显示隐藏文件夹后&#xff01;执行如下操作&#xff01; 30个g&#xff01; 其中unity占23g editer占了21g 删除C:\Users\王栋林\AppData\Local\Unity\Editor下的log文件 恢复到之前的水…

SpanBert学习

SpanBERT: Improving Pre-training by Representing and Predicting Spans 核心点 提出了更好的 Span Mask 方案&#xff0c;也再次展示了随机遮盖连续一段字要比随机遮盖掉分散字好&#xff1b;通过加入 Span Boundary Objective (SBO) 训练目标&#xff0c;增强了 BERT 的性…

C++中`Stream-based I/O`是`RAII`机制的

C中Stream-based I/O都是RAII机制的&#xff0c;文件流可以不手动close。 Do I need to manually close an ifstream? https://en.cppreference.com/w/cpp/io Refenence https://github.com/cyrusbehr/tensorrt-cpp-api/issues/55

网络安全主题纪录片

网络安全主题纪录片 文章目录 网络安全主题纪录片第四公民黑客帝国系列龙纹身女孩碟中谍系列虎胆龙威4匿名者终结者2&#xff1a;审判日东方快车谋杀案黑客国家公敌我是谁&#xff1a;没有绝对安全的系统黑客军团速度与激情系列十亿美元大劫案勒索软件的背后黑客的恐惧为什么网…

Linux 终端中的目录切换

目录 ⛳️推荐 前言 理解 Linux 中的路径 利用 cd 命令变更目录 故障解决 文件或目录不存在 非目录错误 特殊目录符号 测试你的知识 ⛳️推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击…

面包屑新玩法,ReactRouter+Ant Design实现动态渲染

在Ant Design中,可以通过Breadcrumb组件结合react-router库实现动态生成面包屑导航。具体步骤如下: 定义路由配置数据结构 我们需要在路由配置中添加额外的面包屑相关信息,例如面包屑标题、icon等。例如: const routes [{path: /,breadcrumbName: 首页},{path: /users,brea…

华为数通HCIA ——企业网络架构以及产品线

一.学习目标&#xff1a;精讲网络技术&#xff0c;可以独立搭建和维护中小企业网络&#xff01; 模拟器&#xff08;华为方向请安装ENSP&#xff0c;Ensp-Lite已有安装包&#xff0c;号称功能更加完善-这意味着要耗费更多的系统资源但是仅对华为内部伙伴申请后方可使用&#x…

Java技术学习|消息队列|初级RabbitMQ

学习材料声明 黑马RabbitMQ快速入门教程&#xff0c;快速掌握rabbitmq、springAMQP消息中间件 是非常初级的学习&#xff0c;听说后续的高级课程会涉及到微服务之类的&#xff0c;所以等学完微服务再回来学。还有redis的高级部分也涉及了微服务&#xff0c;所以也都暂时停止学…

【行为型模式】中介者模式

一、中介者模式概述 中介者模式定义&#xff1a;用一个中介对象来封装一系列的对象交互&#xff0c;中介者使各对象不需要显式地相互引用&#xff0c;从而使其耦合松散&#xff0c;而且可以独立地改变它们之间的交互。中介者模式又称为调停者模式。(对象行为型模式) 中介者模式…

autodl私有云使用方法(成员端使用)

此时找管理员添加进团队&#xff0c;https://private.autodl.com/访问&#xff0c;登录账号。可以看到容器实例。 点击创建实例&#xff0c;根据所需创建。版本号不可以超过最高的CUDA支持&#xff0c;可以自己拉取镜像。 此处需要注意数据盘使用量&#xff0c;密切关注。存取传…

web前端学习笔记2

2. 网页穿上美丽外衣 2.1 什么是CSS CSS (Cascading Style Sheets,层叠样式表),是一种用来为结构化文档(如 HTML 文档或 XML 应用)添加样式(字体、间距和颜色等)的计算机语言,CSS 文件扩展名为 .css。 CSS样式包括对字体、颜色、边距、高度、宽度、背景图片、网页定位…

MySQL数据库精讲001——概述

MySQL数据库精讲001——概述 文章目录 MySQL数据库精讲001——概述1.1 安装1.1.1 版本1.1.2 安装一、下载二、解压三、配置1. 添加环境变量2. 初始化MySQL3. 注册MySQL服务4. 启动MySQL服务5. 修改默认账户密码 四、登录MySQL五、卸载MySQL 1.1.3 连接1.1.4 企业使用方式(了解)…

绝缘栅型场效应管内部工作原理

我们以增强型nmos为例&#xff0c;其他mos管作为拓展理解&#xff1a; 1.各个部分介绍 如图&#xff0c;增强型nmos引出三个极&#xff0c;源极&#xff08;Source&#xff09;&#xff0c;栅极&#xff08;Gate&#xff09;&#xff0c;漏极&#xff08;Drain&#xff09;&am…

微博评论爬取

import requests import csv# 打开CSV文件以写入数据 f open(data.csv, modea, encodingutf-8-sig, newline) csv_writer csv.DictWriter(f, fieldnames[昵称, 性别, 归属地, 内容]) csv_writer.writeheader()# 定义一个函数用于获取评论内容 def GetContent(max_id):# 设置请…

【头文件】对.h文件的理解

目录 &#x1f31e;1. 头文件的概念 &#x1f30a;1.1 头文件的由来 &#x1f30a;1.2 头文件的作用 &#x1f30a;1.3 在.h文件中实现函数也不会出错的原因 &#x1f31e;2. 简单示例 &#x1f30a;2.1 头文件addition.h &#x1f30a;2.2 头文件接口实现addition.cpp …

网络编程 day5

select实现TCP并发服务器&#xff1a; #include<myhead.h> #define SER_IP "192.168.125.199" //服务器IP地址 #define SER_PORT 6666 //服务器端口号int main(int argc, const char *argv[]) {//1、创建套节字&#xff1a;用于接收…