Flutter Drawing Board

repository·master·Indexed 42 days ago

https://github.com/fluttercandies/flutter_drawing_board

A customizable Flutter package for creating interactive drawing boards. Features include smoothing via Bezier curves, palm rejection, JSON serialization for persistence, and image export. It provides a DrawingBoard widget for the canvas, a DrawingBar for toolbars, and a DrawingController to programmatically manage actions like undo, redo, rotate, and clear. Supports built-in content types such as SimpleLine, SmoothLine, StraightLine, Rectangle, Circle, and Eraser.

Tokens
13K
Snippets
34
Records
42
Agent score
45%

What's inside Flutter Drawing Board

  1. Overview of Flutter Drawing Board Features

    master

    Flutter Drawing Board is a highly customizable plugin providing:

    • Drawing Tools: Freehand lines, brushes, straight lines, rectangles, circles, and erasers.
    • Smoothing Algorithms: Supports Bezier curves and Catmull-Rom spline interpolation for smooth lines.
    • Canvas Operations: Pan, zoom, rotate (90° increments), undo, redo, and clear.
    • Advanced Capabilities: Palm rejection for tablets, JSON serialization for saving/loading drawings, and image export (e.g., PNG).
    • Customization: Flexible toolbar system and highly extensible architecture for custom drawing content types.
    • Performance: Optimized with point filtering, canvas caching, and eraser optimizations.
  2. Create custom drawing content

    master

    To create a custom drawing tool, extend the PaintContent class and implement the following methods:

    1. startDraw(Offset startPoint): Initialize the drawing state.
    2. drawing(Offset nowPoint): Update the state as the user moves their finger/stylus.
    3. draw(Canvas canvas, Size size, bool deeper): Perform the actual Canvas drawing operations.
    4. copy(): Return a new instance of the content.
    5. toContentJson(): Return a Map<String, dynamic> for serialization.
    6. fromJson(Map<String, dynamic> data): A factory constructor to reconstruct the content from JSON.
    class Triangle extends PaintContent {
      // ... implementation of startDraw, drawing, draw, copy, toContentJson ...
    
      @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);
      }
    }
    
    // Add to toolbar
    DrawingBar(
      controller: _drawingController,
      tools: [
        DefaultToolItem(icon: Icons.change_history, content: Triangle),
      ],
    )
  3. Use built-in drawing content types

    master

    The plugin provides several pre-defined PaintContent types to allow different drawing behaviors. You can switch the current drawing tool by setting the setPaintContent property on your DrawingController.

    Available Content Types:

    • SimpleLine: Smooth freehand lines. Supports Bezier curve smoothing.
    • SmoothLine: Variable thickness brush strokes with advanced smoothing levels.
    • StraightLine: Standard straight lines.
    • Rectangle: Rectangular shapes.
    • Circle: Circles or ellipses.
    • Eraser: Removes drawing content.
    // Example: Switching to a SmoothLine brush
    _drawingController.setPaintContent = SmoothLine(
      brushPrecision: 0.8,
      useBezierCurve: true,
      minPointDistance: 2.0,
      smoothLevel: 1, // 0: Fast, 1: Balanced, 2: Ultra-smooth
    );
    
    // Example: Switching to a Circle
    _drawingController.setPaintContent = Circle(
      isEllipse: true,
      startFromCenter: true,
    );
  4. Install and Quick Start with Flutter Drawing Board

    master

    To use flutter_drawing_board, import the package and initialize a DrawingController. You must manage the lifecycle of the controller by calling dispose() when the widget is removed from the tree.

    To build a drawing interface, use the DrawingBoard widget for the canvas area and DrawingBar widgets to provide user controls like tools (pen, brush, shapes) and actions (undo, redo, clear, rotate).

    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: [
              // Drawing Board
              Expanded(
                child: DrawingBoard(
                  controller: _drawingController,
                  background: Container(color: Colors.white),
                ),
              ),
    
              // Action Bar (slider, undo, redo, rotate, clear)
              DrawingBar(
                controller: _drawingController,
                tools: [
                  DefaultActionItem.slider(),
                  DefaultActionItem.undo(),
                  DefaultActionItem.redo(),
                  DefaultActionItem.turn(),
                  DefaultActionItem.clear(),
                ],
              ),
    
              // Tool Bar (pen, brush, shapes, eraser)
              DrawingBar(
                controller: _drawingController,
                tools: [
                  DefaultToolItem.pen(),
                  DefaultToolItem.brush(),
                  DefaultToolItem.rectangle(),
                  DefaultToolItem.circle(),
                  DefaultToolItem.straightLine(),
                  DefaultToolItem.eraser(),
                ],
              ),
            ],
          ),
        );
      }
    }
  5. Create custom drawing content by extending PaintContent

    master

    To implement a new drawing tool (e.g., a Triangle), extend the PaintContent class and implement the required lifecycle methods:

    1. startDraw(Offset startPoint): Called when the user first touches the screen.
    2. drawing(Offset nowPoint): Called as the user moves their finger/stylus.
    3. draw(Canvas canvas, Size size, bool deeper): The actual rendering logic using the provided canvas and paint.
    4. copy(): Returns a new instance of the content.
    5. toContentJson(): Returns a JSON representation for serialization.
    6. fromJson(Map<String, dynamic> json): A factory constructor to reconstruct the content from JSON.

    Once implemented, you can add your custom content to the DrawingBar by passing the class type to a DefaultToolItem.

    class Triangle extends PaintContent {
      // ... implementation of startDraw, drawing, draw, copy, toContentJson ...
    
      @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);
      }
    }
    
    // Adding to toolbar
    DefaultToolItem(
      icon: Icons.change_history,
      content: Triangle,
      activeColor: Colors.purple,
    )
  6. Customize iOS Launch Screen Assets

    master

    To change the launch screen image for the iOS version of your application, you can use one of two methods:

    1. Direct File Replacement: Replace the existing image files located in the example/ios/Runner/Assets.xcassets/LaunchImage.imageset/ directory with your own assets.
    2. Xcode Interface:
      • Open the iOS workspace using open ios/Runner.xcworkspace.
      • In the Xcode Project Navigator, navigate to Runner/Assets.xcassets.
      • Drag and drop your desired images into the asset catalog to replace the current launch images.
    open ios/Runner.xcworkspace
  7. Quick Start with Flutter Drawing Board

    master

    To integrate the drawing board into your Flutter application, follow these steps:

    1. Import the package: import 'package:flutter_drawing_board/flutter_drawing_board.dart';
    2. Initialize a DrawingController: Create a persistent instance of DrawingController within your StatefulWidget's state.
    3. Dispose the controller: Always call _drawingController.dispose() in your widget's dispose() method to prevent memory leaks.
    4. Implement the UI: Use the DrawingBoard widget for the canvas area and DrawingBar widgets for toolbars and action bars.

    Note on breaking changes:

    • Versions 0.3.0 and later have removed text functionality. If you require text drawing, use the stack_board package instead.
    • In versions 0.9.9+, SimpleLine() uses Bezier curves by default (useBezierCurve: true). For legacy behavior, set useBezierCurve: false.
    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: [
              // The Drawing Canvas
              Expanded(
                child: DrawingBoard(
                  controller: _drawingController,
                  background: Container(color: Colors.white),
                ),
              ),
    
              // Action Bar (Slider, Undo, Redo, Rotate, Clear)
              DrawingBar(
                controller: _drawingController,
                tools: [
                  DefaultActionItem.slider(),
                  DefaultActionItem.undo(),
                  DefaultActionItem.redo(),
                  DefaultActionItem.turn(),
                  DefaultActionItem.clear(),
                ],
              ),
    
              // Tool Bar (Pen, Brush, Shapes, Eraser)
              DrawingBar(
                controller: _drawingController,
                tools: [
                  DefaultToolItem.pen(),
                  DefaultToolItem.brush(),
                  DefaultToolItem.rectangle(),
                  DefaultToolItem.circle(),
                  DefaultToolItem.straightLine(),
                  DefaultToolItem.eraser(),
                ],
              ),
            ],
          ),
        );
      }
    }
  8. Export and Import drawing data via JSON

    master

    You can persist drawings by converting them to and from JSON.

    Exporting:

    Use _drawingController.getJsonList() to get a List<Map<String, dynamic>>. This list contains the type and data for every drawn element.

    Importing:

    To import, iterate through the JSON list and map each entry back to its corresponding PaintContent subclass using its .fromJson() factory. You must manually handle the type switching based on the 'type' key in the JSON.

    Note: Ensure you handle all possible types (e.g., SimpleLine, StraightLine, Rectangle, Circle, Eraser) in your switch statement to avoid losing data.

    // Exporting
    final List<Map<String, dynamic>> jsonList = _drawingController.getJsonList();
    
    // Importing
    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);
    }
  9. Configure DrawingBar layout styles

    master

    You can customize the layout of the DrawingBar by providing different implementations of DrawingBarStyle. There are three supported styles:

    1. HorizontalToolsBarStyle: Arranges tools in a Row.
    2. VerticalToolsBarStyle: Arranges tools in a Column.
    3. WrapToolsBarStyle: Arranges tools in a Wrap widget, allowing them to flow to the next line/column if space is limited.

    Each style allows for fine-grained control over alignment, spacing, and direction.

    // Horizontal layout
    const HorizontalToolsBarStyle(mainAxisAlignment: MainAxisAlignment.center, spacing: 10.0)
    
    // Vertical layout
    const VerticalToolsBarStyle(mainAxisAlignment: MainAxisAlignment.start)
    
    // Auto-wrapping layout
    const WrapToolsBarStyle(direction: Axis.horizontal, spacing: 8.0, runSpacing: 8.0)
  10. Explore Flutter Drawing Board Examples

    master

    For practical implementation patterns, refer to the example folder in the repository. Key implementation patterns include:

    • Implementing a basic drawing board.
    • Creating custom drawing content (e.g., Triangle, Image).
    • Integrating a color picker.
    • Implementing JSON import/export for saving/loading drawings.
    • Implementing image export functionality.
  11. Configure the DrawingBoard widget

    master

    The DrawingBoard widget is the visual container for the drawing surface. It accepts a DrawingController and allows configuration of interaction, background, and layout.

    Configuration Options:

    • controller: The DrawingController instance.
    • background: A widget to serve as the canvas background.
    • boardPanEnabled / boardScaleEnabled: Boolean flags to enable/disable panning and zooming (default: true).
    • minScale / maxScale: Limits for the zoom level.
    • panAxis: Restricts the direction of panning (e.g., PanAxis.free).
    • enablePalmRejection: Enables palm rejection to prevent accidental touches on tablets (default: false).
    • transformationController: An external controller for managing board transformations.
    DrawingBoard(
      controller: _drawingController,
      background: Container(color: Colors.white),
      boardPanEnabled: true,
      boardScaleEnabled: true,
      minScale: 0.2,
      maxScale: 20,
      enablePalmRejection: true,
      panAxis: PanAxis.free,
    )
  12. Export and Import drawing data

    master

    You can persist drawings as images or JSON data.

    Image Export:

    • getImageData(format: ...): Returns the complete image including the background. Use ui.ImageByteFormat.png for standard PNG output.
    • getSurfaceImageData(): Returns only the drawing layer (faster, excludes background).

    JSON Export/Import:

    • getJsonList(): Returns a List<Map<String, dynamic>> representing all drawing contents.
    • To import, you must map the JSON objects back to their respective PaintContent types (e.g., SimpleLine.fromJson(json)) and use addContents().
    // Export as PNG
    Future<void> _exportImage() async {
      final ByteData? data = await _drawingController.getImageData(
        format: ui.ImageByteFormat.png,
      );
      if (data != null) {
        final Uint8List bytes = data.buffer.asUint8List();
      }
    }
    
    // Export as JSON
    void _exportJson() {
      final List<Map<String, dynamic>> jsonList = _drawingController.getJsonList();
      final String jsonString = const JsonEncoder().convert(jsonList);
    }