hand_drawn_toolkit 0.1.2 copy "hand_drawn_toolkit: ^0.1.2" to clipboard
hand_drawn_toolkit: ^0.1.2 copied to clipboard

A lightweight Flutter package for rendering hand-drawn, sketchy lines, borders, and containers.

Hand Drawn Toolkit #

A lightweight Flutter package for rendering hand-drawn, sketchy lines, borders, and containers with a natural, organic feel.

pub package License: MIT Publisher

Example #

Example UI with hand drawn elements

Features #

  • Realistic hand-drawn borders, dividers, and custom path shapes
  • Smooth, organic wobble via 3-point moving average smoothing
  • Fully customizable styling (irregularity, segments, stroke width)
  • Deterministic seed-based generation — identical parameters always produce identical strokes
  • Internal path caching for efficient repaints
  • Zero external dependencies — only the Flutter SDK

Installation #

Add the package to your pubspec.yaml:

dependencies:
  hand_drawn_toolkit: ^0.1.2

Then run:

flutter pub get

Quick Start #

import 'package:hand_drawn_toolkit/hand_drawn_toolkit.dart';

// Sketchy container around any widget:
HandDrawnContainer(
  child: Text('Looks hand-drawn!'),
)

// Sketchy divider:
HandDrawnDivider()

Usage Guide #

HandDrawnContainer #

Wraps a child widget with a hand-drawn rectangular border and solid background fill:

HandDrawnContainer(
  backgroundColor: Colors.white,
  strokeColor: Colors.black87,
  strokeWidth: 2.0,
  irregularity: 3.5,
  seed: 42,
  padding: EdgeInsets.all(20),
  child: Text('Sketchy!'),
)

Animating the Border #

Use borderOpacity to fade the border in or out without changing the stroke color — useful for entrance animations or interactive states:

HandDrawnContainer(
  borderOpacity: _animationController.value,
  child: MyContent(),
)

Varying the Wobble Pattern #

Each unique seed produces a different jitter pattern. Use this to give adjacent containers distinct borders:

for (int i = 0; i < items.length; i++)
  HandDrawnContainer(
    seed: i * 17 + 3,
    child: ListTile(title: Text(items[i])),
  )

HandDrawnDivider #

A drop-in sketchy replacement for Flutter's Divider, supporting both orientations:

// Horizontal (default)
Column(
  children: [
    Text('Section A'),
    HandDrawnDivider(),
    Text('Section B'),
  ],
)

// Vertical
Row(
  children: [
    Text('Left'),
    HandDrawnDivider(direction: Axis.vertical, height: 40),
    Text('Right'),
  ],
)

With indentation:

HandDrawnDivider(
  indent: 16,
  endIndent: 16,
  color: Colors.grey,
)

Using HandDrawnLinePainter #

For full control, use the painter directly with CustomPaint. The buildPath callback receives a HandDrawnHelpers instance with methods for generating jittered paths:

CustomPaint(
  painter: HandDrawnLinePainter(
    color: Colors.black,
    strokeWidth: 2.0,
    irregularity: 3.5,
    buildPath: (size, helpers) => helpers.rectBorder(size),
  ),
  child: MyWidget(),
)

Use it as a foregroundPainter to draw the stroke on top of content:

CustomPaint(
  foregroundPainter: HandDrawnLinePainter(
    color: Colors.red,
    buildPath: (size, h) => h.lineHorizontal(size),
  ),
  child: MyWidget(),
)

Custom Path Shapes #

Use HandDrawnHelpers.smoothedOffsets() to build arbitrary jittered paths beyond the built-in shapes:

HandDrawnLinePainter(
  color: Colors.deepPurple,
  strokeWidth: 2.5,
  irregularity: 2.0,
  buildPath: (size, h) {
    // A diagonal line from bottom-left to top-right with wobble
    final offsets = h.smoothedOffsets();
    final dx = size.width / h.segments;
    final path = Path()..moveTo(0, size.height);
    for (int i = 1; i <= h.segments; i++) {
      final t = i / h.segments;
      final y = size.height * (1 - t) + offsets[i];
      path.lineTo(dx * i, y);
    }
    return path;
  },
)

Customization #

Core Parameters #

These parameters are shared across all APIs:

Parameter Type Default Description
irregularity double 3.5 Jitter magnitude in pixels (0 = straight, 6+ = very rough)
segments int 24 Points per edge (more = smoother wobble, fewer = chunkier)
seed int 42 Random seed (same seed = same stroke every time)
strokeWidth double 2.0 Stroke thickness in logical pixels
color Color varies Stroke color

HandDrawnContainer Properties #

Parameter Type Default Description
backgroundColor Color Colors.white Solid fill behind content
strokeColor Color Colors.black87 Border stroke color
padding EdgeInsets EdgeInsets.all(20) Inner padding
borderOpacity double 1.0 Border opacity multiplier (0.0–1.0)

HandDrawnDivider Properties #

Parameter Type Default Description
direction Axis Axis.horizontal Line orientation
thickness double 1.5 Stroke width (subtler default than container)
width double? double.infinity Horizontal divider length
height double? null Vertical divider length
indent double 0.0 Start padding
endIndent double 0.0 End padding

How It Works #

  1. Offset generationsmoothedOffsets() creates random perpendicular offsets for each segment point. First and last points are pinned to zero so strokes start and end cleanly.

  2. Smoothing — A 3-point moving average softens harsh spikes while preserving the organic feel, preventing the "angry zigzag" look that raw random offsets produce.

  3. Path assembly — Built-in helpers (lineHorizontal, lineVertical, rectBorder) stitch smoothed offsets into Flutter Path objects. rectBorder uses four independent offset sets so irregularity varies around the perimeter.

  4. CachingHandDrawnLinePainter caches the generated path and only recomputes when the widget size or generation parameters change.

  5. Determinism — All randomness flows through dart:math.Random(seed), so identical parameters always produce identical strokes.

Best Practices #

Tune irregularity to context — borders look good at 2.0–4.0, while dividers work better at 0.5–1.5. The defaults reflect this (3.5 for containers, 1.0 for dividers).

Use unique seeds for adjacent elements to prevent identical wobble patterns lining up:

ListView.builder(
  itemBuilder: (context, index) => HandDrawnContainer(
    seed: index * 17 + 3,
    child: MyListItem(index),
  ),
)

Keep segment count reasonable — 20–30 segments is the sweet spot for most use cases. Going above 50 adds computation without visible improvement at typical widget sizes.

Leverage caching — the painter only recomputes when parameters or size change, so avoid recreating HandDrawnLinePainter instances unnecessarily in build methods. The convenience widgets (HandDrawnContainer, HandDrawnDivider) handle this correctly by default.

License #

MIT License — see LICENSE for details.

Contributing #

Contributions are welcome! Please feel free to submit issues and pull requests.

1
likes
160
points
139
downloads

Publisher

verified publisherresengi.io

Weekly Downloads

A lightweight Flutter package for rendering hand-drawn, sketchy lines, borders, and containers.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on hand_drawn_toolkit