Flutter-自定义画板

devtools/2024/10/18 10:17:18/

效果

功能

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

实现

定义绘制类型

/// 类型
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://www.ppmy.cn/devtools/14205.html

相关文章

Excel常用函数

目录 1、数学函数2、统计函数3、逻辑函数3、日期函数4、文本函数5、查找与引用函数 注意&#xff1a; 如果有函数不会用可以在Excel插入公式中选择&#xff0c;或查看该函数有关帮助的介绍。 1、数学函数 数学函数作用用法INT()取整MOD()求余数ROUND()四舍五入ABS()取绝对值SQ…

OneFlow新概念清单,AI深度学习的革命性突破(AI写作)

首先&#xff0c;这篇文章是基于笔尖AI写作进行文章创作的&#xff0c;喜欢的宝子&#xff0c;也可以去体验下&#xff0c;解放双手&#xff0c;上班直接摸鱼~ 按照惯例&#xff0c;先介绍下这款笔尖AI写作&#xff0c;宝子也可以直接下滑跳过看正文~ 笔尖Ai写作&#xff1a;…

redis故障中出现的缓存击穿、缓存穿透、缓存雪崩?

一、背景&#xff1a; 在维护redis服务过程中&#xff0c;经常遇见一些redis的名词&#xff0c;例如缓存击穿、缓存穿透、缓存雪崩等&#xff0c;但是不是很理解这些&#xff0c;如下就来解析一下缓存击穿、缓存穿透、缓存雪崩名词。 二、缓存穿透问题&#xff1a; 常见的缓存使…

spring的bean创建流程源码解析

文章目录 IOC 和 DIBeanFactoryApplicationContext实现的接口1、BeanFactory接口2、MessageSource 国际化接口3、ResourcePatternResolver&#xff0c;资源解析接口4、EnvironmentCapable接口&#xff0c;用于获取环境变量&#xff0c;配置信息5、ApplicationEventPublisher 事…

应用在防蓝光显示器中的LED防蓝光灯珠

相比抗蓝光眼镜、防蓝光覆膜、软体降低蓝光强度这些“软”净蓝手段&#xff0c;通过对LED的发光磷粉进行LED背光进行技术革新&#xff0c;可实现硬件“净蓝”。其能够将90%以上的有害蓝光转换为450nm以上的长波低能光线&#xff0c;从硬件的角度解决了蓝光危害眼睛的问题&#…

25计算机考研院校数据分析 | 四川大学

四川大学(Sichuan University)简称“川大”&#xff0c;由中华人民共和国教育部直属&#xff0c;中央直管副部级建制&#xff0c;是世界一流大学建设高校、985工程”、"211工程"重点建设的高水平综合性全国重点大学&#xff0c;入选”2011计划"、"珠峰计划…

真实世界的密码学(一)

原文&#xff1a;annas-archive.org/md5/655c944001312f47533514408a1a919a 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 前言 序言 当你拿起这本书时&#xff0c;你可能会想&#xff0c;为什么又一本关于密码学的书&#xff1f;甚至&#xff0c;为什么我要读这本…

《从零开始的Java世界》10File类与IO流

《从零开始的Java世界》系列主要讲解Javase部分&#xff0c;从最简单的程序设计到面向对象编程&#xff0c;再到异常处理、常用API的使用&#xff0c;最后到注解、反射&#xff0c;涵盖Java基础所需的所有知识点。学习者应该从学会如何使用&#xff0c;到知道其实现原理全方位式…