Custom Widgets & Custom Painting

Shadows, Blur & Layer Effects

15 min Lesson 7 of 12

Shadows, Blur & Layer Effects in Flutter Canvas

Flutter's Canvas API provides three powerful primitives for depth and visual richness: drop shadows via canvas.drawShadow, blur effects via Paint.maskFilter, and layer compositing via canvas.saveLayer with BlendMode. Mastering these lets you replicate CSS-like box shadows, glass-blur overlays, and advanced blend modes entirely in Dart.

Drawing Drop Shadows with canvas.drawShadow

Canvas.drawShadow renders a shadow for any Path. It is highly optimised — the engine delegates to Skia's shadow-projection primitives, so it is faster than manually painting a blurred shape.

Basic Drop Shadow Example

import 'package:flutter/material.dart';

class ShadowPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final path = Path()
      ..addRRect(
        RRect.fromRectAndRadius(
          Rect.fromLTWH(40, 40, size.width - 80, size.height - 80),
          const Radius.circular(16),
        ),
      );

    // drawShadow(path, color, elevation, transparentOccluder)
    canvas.drawShadow(path, Colors.black87, 8.0, false);

    // Draw the card on top of the shadow
    final paint = Paint()..color = Colors.white;
    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

The four arguments to drawShadow are:

  • path — the shape whose silhouette casts the shadow.
  • color — shadow colour (alpha is respected).
  • elevation — Material-style virtual Z height in logical pixels; higher values spread the shadow further.
  • transparentOccluder — set true when the shape itself is partially transparent so the shadow is not masked by the shape.
Note: canvas.drawShadow only accepts a Path. To shadow an image or arbitrary widget, use the BoxShadow decoration or the saveLayer + blur technique described below.

Applying Blur Effects with MaskFilter

Setting paint.maskFilter to a MaskFilter.blur applies a Gaussian blur to everything the Paint draws. This is the correct way to produce soft glows, fog-of-war effects, and blurred shadows.

Gaussian Blur Glow Effect

class GlowPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    const radius = 60.0;

    // 1. Paint a blurred glow ring behind the circle
    final glowPaint = Paint()
      ..color = Colors.cyanAccent.withOpacity(0.6)
      ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 20.0);
    canvas.drawCircle(center, radius + 10, glowPaint);

    // 2. Paint the crisp solid circle on top
    final solidPaint = Paint()..color = Colors.cyan;
    canvas.drawCircle(center, radius, solidPaint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

MaskFilter.blur takes two arguments:

  • BlurStylenormal (blurs both inside & outside), inner (inside only), outer (outside only), solid (hard edge with blurred exterior).
  • sigma — the standard deviation of the Gaussian kernel in logical pixels. Larger values produce softer, wider blurs.
Tip: Blur is GPU-intensive. Avoid animating sigma every frame on complex paths. Pre-render blurred layers to a Picture or use ImageFilter.blur via a BackdropFilter widget for cheaper real-time blurs.

Layer Compositing with canvas.saveLayer and BlendMode

canvas.saveLayer(bounds, paint) pushes an off-screen bitmap (a compositing layer) onto the canvas stack. All subsequent draw calls paint onto that off-screen layer. When canvas.restore() is called, the layer is composited back onto the main canvas using the BlendMode and opacity specified in the layer's Paint.

saveLayer with BlendMode.multiply

class LayerBlendPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final rect = Offset.zero & size;

    // Draw the base layer — a gradient background
    final bgPaint = Paint()
      ..shader = LinearGradient(
        colors: [Colors.orange, Colors.deepPurple],
      ).createShader(rect);
    canvas.drawRect(rect, bgPaint);

    // Open a new compositing layer with BlendMode.multiply
    final layerPaint = Paint()..blendMode = BlendMode.multiply;
    canvas.saveLayer(rect, layerPaint);

    // Anything drawn here is composited with multiply blend
    final circlePaint = Paint()..color = Colors.lightBlueAccent;
    canvas.drawCircle(
      Offset(size.width / 2, size.height / 2),
      size.width * 0.35,
      circlePaint,
    );

    // Restore: the circle is blended onto the gradient with multiply
    canvas.restore();
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

Key BlendMode values for layer effects:

  • BlendMode.multiply — multiplies source and destination colours; darkens.
  • BlendMode.screen — inverse-multiply; lightens.
  • BlendMode.overlay — multiply or screen based on the destination brightness.
  • BlendMode.dstIn — clips the destination to the source's alpha; great for masked cutouts.
  • BlendMode.luminosity — applies the source's luminance to the destination's hue and saturation.
Warning: Every saveLayer call allocates an off-screen texture. Nesting multiple saveLayer calls (especially inside animations) can severely impact performance. Always call canvas.restore() for every saveLayer, and prefer canvas.save() (no offscreen texture) when you only need to isolate transforms or clips.

Combining All Three Techniques

Real-world custom painters often combine shadows, blur, and layer effects. A common pattern is to draw a blurred, semi-transparent shadow using saveLayer + MaskFilter, then paint the crisp foreground shape on top with drawShadow for ambient depth.

Summary

In this lesson you learned:

  • canvas.drawShadow(path, color, elevation, transparentOccluder) — fast, Material-elevation-aware shadow for any Path.
  • paint.maskFilter = MaskFilter.blur(style, sigma) — Gaussian blur applied to a Paint's output, enabling glows and soft shadows.
  • canvas.saveLayer(bounds, paint) + canvas.restore() — composites an off-screen layer back onto the canvas using a BlendMode, enabling multiply, screen, overlay, and alpha-mask effects.
  • Performance considerations: minimise saveLayer nesting and avoid re-blurring every frame unnecessarily.