Snapshot do README original do repositório-fonte vinculado. Consulte o repositório para o conteúdo mais recente.
Flutter Drawing Board
A powerful and highly customizable Flutter package for creating interactive drawing boards with advanced features.
English | 中文

Breaking Changes: After version 0.3.0, text functionality has been removed. For text features, please use 
Version 0.9.9+ Behavior Changes:
SimpleLine() now uses Bezier curve smoothing by default (useBezierCurve: true), providing smoother lines. To use the old behavior, set useBezierCurve: false.
- JSON import maintains backward compatibility with
useBezierCurve: false as default.
Features
- Rich Drawing Tools - SimpleLine, SmoothLine (brush), StraightLine, Rectangle, Circle, and Eraser
- Advanced Smoothing - Bezier curves and Catmull-Rom spline interpolation for ultra-smooth lines
- Palm Rejection - Prevent accidental palm touches on tablets
- Canvas Operations - Pan, zoom, rotate (90° increments), undo, redo, clear
- Performance Optimized - Point filtering, canvas caching (~70% improvement), optimized eraser (~50% improvement)
- JSON Serialization - Complete save/load support for drawings
- Image Export - Export to PNG and other formats
- Flexible Toolbar System - Built-in toolbars with easy customization
- Highly Extensible - Create custom drawing content types easily
- Configurable History - Limit undo/redo steps to prevent memory growth (default: 100)
Preview
Try it online: Demo
Quick Start
import 'package:flutter_drawing_board/flutter_drawing_board.dart';
class MyDrawingPage extends StatefulWidget {
@override
State<MyDrawingPage> createState() => _MyDrawingPageState();
}
class _MyDrawingPageState extends State<MyDrawingPage> {
final DrawingController _drawingController = DrawingController();
@override
void dispose() {
_drawingController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: DrawingBoard(
controller: _drawingController,
background: Container(color: Colors.white),
),
),
DrawingBar(
controller: _drawingController,
tools: [
DefaultActionItem.slider(),
DefaultActionItem.undo(),
DefaultActionItem.redo(),
DefaultActionItem.turn(),
DefaultActionItem.clear(),
],
),
DrawingBar(
controller: _drawingController,
tools: [
DefaultToolItem.pen(),
DefaultToolItem.brush(),
DefaultToolItem.rectangle(),
DefaultToolItem.circle(),
DefaultToolItem.straightLine(),
DefaultToolItem.eraser(),
],
),
],
),
);
}
}
Usage Guide
1. Drawing Content Types
The package provides several built-in drawing content types:
SimpleLine - Free-form line
Draws smooth free-form lines with optional Bezier curve smoothing.
_drawingController.setPaintContent = SimpleLine(
useBezierCurve: true,
minPointDistance: 2.0,
);
Note: Since v0.9.9, useBezierCurve defaults to true for smoother lines. Set to false for the old straight-line behavior.
SmoothLine - Brush stroke with variable width
Draws brush strokes with pressure-like width variation and advanced smoothing.
_drawingController.setPaintContent = SmoothLine(
brushPrecision: 0.8,
useBezierCurve: true,
minPointDistance: 2.0,
smoothLevel: 1,
);
Smooth Levels:
0: Fast - No additional smoothing
1: Balanced - Basic smoothing (recommended)
2: Ultra-smooth - Catmull-Rom spline interpolation for silky smooth curves
StraightLine - Straight line
_drawingController.setPaintContent = StraightLine();
Rectangle - Rectangle shape
_drawingController.setPaintContent = Rectangle();
Circle - Circle or Ellipse
_drawingController.setPaintContent = Circle(
isEllipse: false,
startFromCenter: true,
);
Eraser
_drawingController.setPaintContent = Eraser();
2. DrawingController API
Creating a Controller
final DrawingController _drawingController = DrawingController(
config: DrawConfig(
contentType: SimpleLine,
strokeWidth: 4.0,
color: Colors.black,
),
maxHistorySteps: 100,
);
Setting Paint Style
_drawingController.setStyle(
color: Colors.blue,
strokeWidth: 6.0,
strokeCap: StrokeCap.round,
strokeJoin: StrokeJoin.round,
blendMode: BlendMode.srcOver,
isAntiAlias: true,
);
Canvas Operations
_drawingController.undo();
_drawingController.redo();
_drawingController.turn();
_drawingController.clear();
bool canUndo = _drawingController.canUndo();
bool canRedo = _drawingController.canRedo();
bool canClear = _drawingController.canClear();
Adding Content Programmatically
_drawingController.addContent(StraightLine.fromJson(jsonData));
_drawingController.addContents([
SimpleLine.fromJson(line1Json),
Rectangle.fromJson(rect1Json),
]);
Exporting Data
Export as Image:
Future<void> _exportImage() async {
final ByteData? data = await _drawingController.getImageData(
format: ui.ImageByteFormat.png,
);
if (data != null) {
final Uint8List bytes = data.buffer.asUint8List();
}
}
Future<void> _exportSurface() async {
final ByteData? data = await _drawingController.getSurfaceImageData();
}
Export as JSON:
void _exportJson() {
final List<Map<String, dynamic>> jsonList = _drawingController.getJsonList();
final String jsonString = const JsonEncoder.withIndent(' ').convert(jsonList);
}
Import from JSON:
void _importJson(List<Map<String, dynamic>> jsonData) {
final contents = jsonData.map((json) {
final type = json['type'] as String;
switch (type) {
case 'SimpleLine':
return SimpleLine.fromJson(json);
case 'StraightLine':
return StraightLine.fromJson(json);
case 'Rectangle':
return Rectangle.fromJson(json);
case 'Circle':
return Circle.fromJson(json);
case 'Eraser':
return Eraser.fromJson(json);
default:
return null;
}
}).whereType<PaintContent>().toList();
_drawingController.addContents(contents);
}
3. DrawingBoard Configuration
DrawingBoard(
controller: _drawingController,
background: Container(color: Colors.white),
onPointerDown: (PointerDownEvent event) { },
onPointerMove: (PointerMoveEvent event) { },
onPointerUp: (PointerUpEvent event) { },
boardPanEnabled: true,
boardScaleEnabled: true,
minScale: 0.2,
maxScale: 20,
panAxis: PanAxis.free,
enablePalmRejection: false,
alignment: Alignment.topCenter,
clipBehavior: Clip.antiAlias,
boardClipBehavior: Clip.hardEdge,
transformationController: _transformationController,
)
4. Toolbar System
DrawingBar
The DrawingBar widget provides a flexible toolbar layout with automatic controller passing.
DrawingBar(
controller: _drawingController,
style: HorizontalToolsBarStyle(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 8.0,
),
tools: [ ],
)
DrawingBar(
controller: _drawingController,
style: VerticalToolsBarStyle(
mainAxisAlignment: MainAxisAlignment.start,
spacing: 8.0,
),
tools: [ ],
)
DrawingBar(
controller: _drawingController,
style: WrapToolsBarStyle(
spacing: 8.0,
runSpacing: 8.0,
),
tools: [ ],
)
Default Tool Items
Pre-built tool buttons for switching drawing content:
DefaultToolItem.pen()
DefaultToolItem.brush()
DefaultToolItem.straightLine()
DefaultToolItem.rectangle()
DefaultToolItem.circle()
DefaultToolItem.eraser()
Custom tool item:
DefaultToolItem(
icon: Icons.star,
content: MyCustomContent,
onTap: (controller) {
},
color: Colors.grey,
activeColor: Colors.blue,
iconSize: 24,
)
Default Action Items
Pre-built action buttons:
DefaultActionItem.slider()
DefaultActionItem.undo()
DefaultActionItem.redo()
DefaultActionItem.turn()
DefaultActionItem.clear()
Custom action item:
DefaultActionItem(
childBuilder: (context, controller) {
return Icon(Icons.save);
},
onTap: (controller) async {
await _saveDrawing(controller);
},
)
5. Advanced Features
Palm Rejection
Prevents accidental palm touches on tablets by detecting large touch areas and rapid successive touches.
DrawingBoard(
controller: _drawingController,
background: Container(color: Colors.white),
enablePalmRejection: true,
)
How it works:
- Rejects touches with size > 15.0 (likely palm)
- Rejects rapid successive touches within 100ms (palm + finger)
History Management
Control memory usage by limiting undo/redo history:
final DrawingController _drawingController = DrawingController(
maxHistorySteps: 50,
);
Custom Colors with Opacity
_drawingController.setStyle(
color: Colors.red.withValues(alpha: 0.5),
);
6. Creating Custom Drawing Content
Extend PaintContent to create your own drawing tools:
class Triangle extends PaintContent {
Triangle();
Triangle.data({
required this.startPoint,
required this.A,
required this.B,
required this.C,
required Paint paint,
}) : super.paint(paint);
factory Triangle.fromJson(Map<String, dynamic> data) {
return Triangle.data(
startPoint: jsonToOffset(data['startPoint'] as Map<String, dynamic>),
A: jsonToOffset(data['A'] as Map<String, dynamic>),
B: jsonToOffset(data['B'] as Map<String, dynamic>),
C: jsonToOffset(data['C'] as Map<String, dynamic>),
paint: jsonToPaint(data['paint'] as Map<String, dynamic>),
);
}
Offset startPoint = Offset.zero;
Offset A = Offset.zero;
Offset B = Offset.zero;
Offset C = Offset.zero;
@override
void startDraw(Offset startPoint) => this.startPoint = startPoint;
@override
void drawing(Offset nowPoint) {
A = Offset(
startPoint.dx + (nowPoint.dx - startPoint.dx) / 2,
startPoint.dy,
);
B = Offset(startPoint.dx, nowPoint.dy);
C = nowPoint;
}
@override
void draw(Canvas canvas, Size size, bool deeper) {
final Path path = Path()
..moveTo(A.dx, A.dy)
..lineTo(B.dx, B.dy)
..lineTo(C.dx, C.dy)
..close();
canvas.drawPath(path, paint);
}
@override
Triangle copy() => Triangle();
@override
Map<String, dynamic> toContentJson() {
return <String, dynamic>{
'startPoint': startPoint.toJson(),
'A': A.toJson(),
'B': B.toJson(),
'C': C.toJson(),
'paint': paint.toJson(),
};
}
}
Add to toolbar:
DrawingBar(
controller: _drawingController,
tools: [
...DefaultToolItem.values,
DefaultToolItem(
icon: Icons.change_history,
content: Triangle,
activeColor: Colors.purple,
),
],
)
Preview:
Performance Optimizations
The package includes several performance optimizations (introduced in v0.9.9+):
- Canvas Cache (~70% improvement) - Avoids redundant image generation through cache version control
- Eraser Optimization (~50% improvement) - Reduces double refresh during eraser operations
- Point Filtering (30-50% reduction) - Filters redundant points via
minPointDistance, reducing data by 30-50%
- Bezier Smoothing - Eliminates jagged lines without sacrificing performance
- Overall - 30-50% improvement in rendering and memory usage
JSON Format Example
[
{
"type": "StraightLine",
"startPoint": {"dx": 114.56, "dy": 117.50},
"endPoint": {"dx": 252.93, "dy": 254.91},
"paint": {
"blendMode": 3,
"color": 4294198070,
"filterQuality": 3,
"invertColors": false,
"isAntiAlias": false,
"strokeCap": 1,
"strokeJoin": 1,
"strokeWidth": 4.0,
"style": 1
}
},
{
"type": "SimpleLine",
"points": [
{"dx": 100.0, "dy": 100.0},
{"dx": 150.0, "dy": 120.0},
{"dx": 200.0, "dy": 110.0}
],
"useBezierCurve": true,
"minPointDistance": 2.0,
"paint": { }
}
]
API Reference
DrawingController
| Method |
Description |
setStyle({...}) |
Update paint style (color, width, etc.) |
setPaintContent(PaintContent) |
Switch drawing tool |
undo() |
Undo last action |
redo() |
Redo last undone action |
turn() |
Rotate canvas 90° |
clear() |
Clear canvas |
addContent(PaintContent) |
Add single content |
addContents(List<PaintContent>) |
Add multiple contents |
getImageData() |
Export as image (with background) |
getSurfaceImageData() |
Export surface image (faster) |
getJsonList() |
Export as JSON |
Built-in Content Types
| Type |
Description |
Parameters |
SimpleLine |
Free-form line |
useBezierCurve, minPointDistance |
SmoothLine |
Brush stroke |
brushPrecision, smoothLevel, useBezierCurve, minPointDistance |
StraightLine |
Straight line |
- |
Rectangle |
Rectangle |
- |
Circle |
Circle/Ellipse |
isEllipse, startFromCenter |
Eraser |
Eraser |
- |
Examples
Check out the example folder for complete examples including:
- Basic drawing board
- Custom drawing content (Triangle, Image)
- Color picker integration
- JSON import/export
- Image export
Contributing
Contributions are welcome! Here's how you can help:
- Report bugs - Open an issue describing the bug and how to reproduce it
- Suggest features - Open an issue describing your feature request
- Submit PRs - Fork the repo, make your changes, and submit a pull request
Please ensure your code follows the existing style and includes tests where applicable.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Community
Join our QQ group: 
Changelog
See CHANGELOG.md for version history.