效果
功能
- 支持绘制线、圆、矩形,支持拓展
- 支持撤回上一步
- 支持清空画板
- 支持自定义画笔颜色,宽度
实现
定义绘制类型
/// 类型 enum ShapeType { //线 line, //圆 circle, //矩形 rectangle, //拓展 }
定义绘制抽象类
import 'dart:ui'; /// 绘制抽象类 abstract class Shape { void draw( Canvas canvas, List<Offset> points, Paint paint, ); }
实现线、圆、矩形绘制
/// 绘制圆 class CircleShape implements Shape { @override void 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 { @override void 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 { @override void 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); @override State<StatefulWidget> createState() => DrawingBoardState(); } class DrawingBoardState extends State<DrawingBoard> { @override void initState() { super.initState(); widget.controller.addListener(_updateState); } void _updateState() { setState(() {}); } @override void dispose() { super.dispose(); widget.controller.removeListener(_updateState); } @override Widget 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; @override void 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); } } @override bool 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); @override DrawingPageState createState() => DrawingPageState(); } class DrawingPageState extends State<DrawingPage> { final _controller = DrawingController(); @override void initState() { super.initState(); SystemChrome.setPreferredOrientations([ DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight, ]); } @override void dispose() { super.dispose(); SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, DeviceOrientation.portraitDown, ]); } @override Widget 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