0Pricing
Flutter Mobile Development · Pelajaran

Clipping, Mode Blend, dan Komposit Layer

Gunakan path clipping dan mode blend untuk menyusun efek visual berlapis yang canggih.

Clipping, Mode Blend, dan Komposit Layer adalah pelajaran Flutter Mobile Development gratis di CoddyKit. Ini adalah pelajaran 3 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar Flutter Mobile Development, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus Flutter Mobile Development mencakup 4 pelajaran total.

Bagian dari pelajaran ini belum diterjemahkan dan ditampilkan dalam bahasa Inggris.

Why Compositing Matters

Advanced visuals in Flutter rarely come from a single shape. Glow rings, frosted overlays, masked thumbnails, and "cut-out" badges are built by layering several draws and controlling how each new layer combines with what's already on the canvas.

Three tools drive this on a Canvas:

  • Clipping — restrict where drawing can land (clipPath, clipRect, clipRRect).
  • Blend modes — control the math that merges a new layer with the pixels beneath it (BlendMode on a Paint).
  • Layers — saveLayer creates an offscreen buffer so a blend mode applies to a whole group, not just one shape.

This lesson wires all three together.

Clipping the Canvas

Inside a CustomPainter.paint, a clip narrows the drawable region. Everything you draw after the clip is masked to that region until the next restore().

  • canvas.clipRect(rect) — rectangular clip.
  • canvas.clipRRect(rrect) — rounded-rectangle clip (great for card thumbnails).
  • canvas.clipPath(path) — arbitrary shape clip.

Always pair clipping with save() / restore() so the clip doesn't leak into later draws.

class ClipPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = const Color(0xFF2196F3);
    canvas.save();
    final rrect = RRect.fromRectAndRadius(
      Offset.zero & size,
      const Radius.circular(24),
    );
    canvas.clipRRect(rrect);
    // Fills the whole canvas, but only the rounded area shows.
    canvas.drawRect(Offset.zero & size, paint);
    canvas.restore();
  }

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

Clipping with an Arbitrary Path

For non-rectangular masks (tickets, waves, badges), build a Path and pass it to clipPath. The classic ticket shape uses two concave arcs on its sides.

Set doAntiAlias: true (the default) for smooth edges. For animated clips that change every frame, doAntiAlias: false can be a touch faster.

Path ticketPath(Size size, double notch) {
  final r = notch;
  return Path()
    ..moveTo(0, 0)
    ..lineTo(size.width, 0)
    ..lineTo(size.width, size.height / 2 - r)
    ..arcToPoint(
      Offset(size.width, size.height / 2 + r),
      radius: Radius.circular(r),
      clockwise: false,
    )
    ..lineTo(size.width, size.height)
    ..lineTo(0, size.height)
    ..lineTo(0, size.height / 2 + r)
    ..arcToPoint(
      Offset(0, size.height / 2 - r),
      radius: Radius.circular(r),
      clockwise: false,
    )
    ..close();
}

What a Blend Mode Actually Does

A BlendMode defines the formula combining the source (what you're drawing now) with the destination (pixels already there). The default is BlendMode.srcOver — normal alpha compositing.

Useful families:

  • Porter-Duff (geometry/masking): srcIn, dstIn, srcOut, dstATop, clear.
  • Separable color math: multiply, screen, overlay, plus (additive glow), difference.

You assign it on the Paint: paint.blendMode = BlendMode.multiply.

Blend Modes Need a Layer

Here's the key gotcha: many blend modes (srcIn, dstIn, multiply over transparency, etc.) only behave correctly when there is an explicit layer to act as the destination.

canvas.saveLayer(bounds, paint) allocates an offscreen buffer. Draws between saveLayer and restore accumulate in that buffer; on restore the buffer is composited back using the layer paint's blend mode.

Without saveLayer, a srcIn draw would treat the entire screen as destination — usually erasing more than you intended.

void paint(Canvas canvas, Size size) {
  final bounds = Offset.zero & size;
  // 1. Open an offscreen layer.
  canvas.saveLayer(bounds, Paint());
  // 2. Destination: a circle.
  canvas.drawCircle(
    size.center(Offset.zero),
    size.shortestSide / 2,
    Paint()..color = const Color(0xFFFFFFFF),
  );
  // 3. Source with srcIn keeps only the overlap with the circle.
  canvas.drawRect(
    bounds,
    Paint()
      ..shader = const LinearGradient(
        colors: [Color(0xFFFF0080), Color(0xFF7928CA)],
      ).createShader(bounds)
      ..blendMode = BlendMode.srcIn,
  );
  // 4. Composite the layer back to the canvas.
  canvas.restore();
}

Masking a Gradient into a Shape

The pattern from the previous scene is the idiom for gradient-filled icons or text masks:

  • Draw the opaque mask shape first (it becomes the destination).
  • Draw the gradient with BlendMode.srcIn — it survives only where the mask is opaque.

Reverse the roles with dstIn: draw the gradient first, then a shape with dstIn to keep only the gradient under that shape. Choose based on which layer you want to draw last.

class GradientTextMask extends CustomPainter {
  GradientTextMask(this.label);
  final String label;

  @override
  void paint(Canvas canvas, Size size) {
    final bounds = Offset.zero & size;
    canvas.saveLayer(bounds, Paint());
    final tp = TextPainter(
      text: TextSpan(
        text: label,
        style: const TextStyle(
          fontSize: 64, fontWeight: FontWeight.bold,
          color: Color(0xFFFFFFFF),
        ),
      ),
      textDirection: TextDirection.ltr,
    )..layout();
    tp.paint(canvas, Offset.zero); // destination = glyphs
    canvas.drawRect(
      bounds,
      Paint()
        ..shader = const LinearGradient(
          colors: [Color(0xFF00C6FF), Color(0xFF0072FF)],
        ).createShader(bounds)
        ..blendMode = BlendMode.srcIn, // gradient fills glyphs
    );
    canvas.restore();
  }

  @override
  bool shouldRepaint(covariant GradientTextMask old) => old.label != label;
}

Additive Glow with BlendMode.plus

BlendMode.plus adds source and destination color channels (clamped to 1.0). Overlapping bright shapes get brighter — perfect for neon glows, sparks, and light bloom.

Combine it with a MaskFilter.blur to soften each shape into a halo. Because plus accumulates, layering several blurred circles produces a convincing glow without any image asset.

void drawGlow(Canvas canvas, Offset c, double r, Color color) {
  final glow = Paint()
    ..color = color
    ..blendMode = BlendMode.plus
    ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 12);
  canvas.drawCircle(c, r, glow);
  // A second, tighter core makes the center read brighter.
  canvas.drawCircle(c, r * 0.5,
    Paint()..color = color..blendMode = BlendMode.plus);
}

Punching Holes with BlendMode.clear

To cut a transparent hole through an already-drawn layer, draw a shape with BlendMode.clear. It erases destination pixels (sets alpha to 0) wherever the source covers.

This must happen inside a saveLayer — otherwise clear would punch through everything behind the canvas, including widgets below. The spotlight/overlay-with-cutout effect is the canonical use.

void paint(Canvas canvas, Size size) {
  final bounds = Offset.zero & size;
  canvas.saveLayer(bounds, Paint());
  // Dim scrim covering everything.
  canvas.drawRect(bounds, Paint()..color = const Color(0xCC000000));
  // Cut a clear circular "spotlight" through the scrim.
  canvas.drawCircle(
    size.center(Offset.zero),
    size.shortestSide / 4,
    Paint()..blendMode = BlendMode.clear,
  );
  canvas.restore();
}

Layer Paint vs Per-Shape Paint

There are two distinct paints in play, and mixing them up is a common bug:

  • The per-shape paint (passed to drawCircle, drawRect…) controls how that one shape blends with the current layer's contents.
  • The layer paint (passed to saveLayer) controls how the entire finished layer blends back when you restore(), and can apply a group opacity or colorFilter.

Want one slider to fade a whole composite group uniformly? Put the opacity on the layer paint, not on each shape — per-shape opacity would let overlaps show through each other.

void paint(Canvas canvas, Size size) {
  final bounds = Offset.zero & size;
  // Group opacity: whole layer fades together.
  final layerPaint = Paint()..color = const Color(0x80FFFFFF);
  canvas.saveLayer(bounds, layerPaint);
  final p = Paint()..color = const Color(0xFFE91E63);
  canvas.drawCircle(const Offset(60, 60), 50, p);
  canvas.drawCircle(const Offset(100, 60), 50, p); // overlap stays solid
  canvas.restore(); // entire pair composited at 50% alpha
}

Clip + Blend + Layer Together

A frosted, rounded thumbnail with a gradient sheen combines all three tools in order:

  • Clip to a rounded rect so nothing spills past the card.
  • saveLayer so the sheen can blend against the photo as a group.
  • Draw the photo, then a soft highlight with BlendMode.softLight or overlay.

Order is everything: clip first, open the layer, draw destination, draw blended source, restore, then restore the clip.

void paint(Canvas canvas, Size size, ui.Image photo) {
  final bounds = Offset.zero & size;
  canvas.save();
  canvas.clipRRect(
    RRect.fromRectAndRadius(bounds, const Radius.circular(20)),
  );
  canvas.saveLayer(bounds, Paint());
  canvas.drawImageRect(
    photo,
    Offset.zero & Size(photo.width.toDouble(), photo.height.toDouble()),
    bounds,
    Paint(),
  );
  canvas.drawRect(
    bounds,
    Paint()
      ..shader = const LinearGradient(
        begin: Alignment.topLeft, end: Alignment.bottomRight,
        colors: [Color(0x88FFFFFF), Color(0x11000000)],
      ).createShader(bounds)
      ..blendMode = BlendMode.softLight,
  );
  canvas.restore(); // close layer
  canvas.restore(); // close clip
}

Performance: Layers Aren't Free

Each saveLayer allocates an offscreen texture and forces an extra compositing pass — the single most expensive thing you can do per frame in a painter. Treat it as a tool to reach for deliberately, not a default.

  • Pass the tightest possible bounds to saveLayer; a full-screen layer is far costlier than a small one.
  • Prefer a plain clipRRect + direct draws when you don't actually need group blending.
  • Cache static composites (e.g. via ui.PictureRecorder into a ui.Image) instead of rebuilding the layer every frame.
  • Reuse Paint objects; avoid allocating in the hot paint path.

Quick Check: Choosing the Right Tool

You want to fill some bold text glyphs with a linear gradient inside a CustomPainter, and nothing outside the glyphs should be tinted. Which approach is correct?

Recap

You can now compose advanced layered effects on a Canvas:

  • Clipping (clipRect/clipRRect/clipPath) restricts where draws land — always wrap in save()/restore().
  • Blend modes set how a source merges with the destination: srcIn/dstIn for masking, plus for additive glow, clear for cut-out holes, softLight/overlay for sheen.
  • saveLayer gives blend modes a real destination buffer and lets you apply group opacity or color filters via the layer paint.
  • Distinguish the layer paint from per-shape paint, draw destination-before-source, and keep layers small and rare for performance.

Reach for layers deliberately, clip when you only need a mask, and cache static composites.

Pertanyaan yang Sering Diajukan

Apakah pelajaran “Clipping, Mode Blend, dan Komposit Layer” gratis?

Ya — teks lengkap “Clipping, Mode Blend, dan Komposit Layer” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus Flutter Mobile Development, upgrade ke CoddyKit PRO. Kursus Flutter Mobile Development mencakup 4 pelajaran total.

Apa yang akan aku pelajari di “Clipping, Mode Blend, dan Komposit Layer”?

Gunakan path clipping dan mode blend untuk menyusun efek visual berlapis yang canggih. Kamu berlatih Flutter Mobile Development dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.

Apakah aku perlu pengalaman untuk memulai Flutter Mobile Development?

Tidak diperlukan pengalaman sebelumnya. Flutter Mobile Development di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 3 dari 4.

Berapa lama pelajaran “Clipping, Mode Blend, dan Komposit Layer” memakan waktu?

Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.

Bisakah aku menulis dan menjalankan kode dalam pelajaran Flutter Mobile Development ini?

Ya. Setiap pelajaran Flutter Mobile Development menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.

Semua pelajaran dalam kursus ini

  1. API Canvas: Path, Paint, dan Shader
  2. Membangun Widget Chart Interaktif Khusus
  3. Clipping, Mode Blend, dan Komposit Layer
  4. Pengujian Hit dan Optimasi Repaint
← Kembali ke Flutter Mobile Development