0Pricing
Flutter Mobile Development · Ders

Vuruş Testi ve Yeniden Çizim Optimizasyonu

Çizimi verimli tutmak için özel vuruş testi ve shouldRepaint mantığı uygulayın.

Vuruş Testi ve Yeniden Çizim Optimizasyonu, CoddyKit'te ücretsiz bir Flutter Mobile Development dersidir. Bu, 4 dersinin 4. dersidir. Aşağıdan dersin tamamını ücretsiz okuyabilir, sonra tarayıcıda yerleşik kod editörü ve 7/24 yapay zeka koçu ile uygulamalı olarak pratik yapabilirsin. Bu, Flutter Mobile Development öğrenme yolunun bir parçasıdır ve ilerlemeniz web ve CoddyKit uygulaması arasında senkronize olur. Flutter Mobile Development kursu toplamda 4 dersten oluşur.

Bu dersin bazı bölümleri henüz çevrilmemiş olup İngilizce olarak gösterilmektedir.

Why Hit Testing and Repaint Matter

When you draw with CustomPainter you take full control of the canvas. But two costs are easy to overlook: hit testing (deciding which pixels respond to taps) and repainting (re-running your paint code every frame).

  • A naive painter that always returns true from shouldRepaint repaints on every rebuild, burning GPU and battery.
  • By default a custom-painted region is transparent to pointer events, so taps fall through to widgets behind it.

This lesson shows how to make custom drawings both interactive and efficient.

Anatomy of a CustomPainter

A CustomPainter exposes two key overrides: paint for drawing and shouldRepaint for deciding when to re-run paint. There is also an optional hitTest for pointer logic.

  • paint(Canvas canvas, Size size) — your drawing commands.
  • shouldRepaint(covariant CustomPainter old) — return true only when visual inputs changed.
  • hitTest(Offset position) — return true/false/null to control pointer routing.
class CirclePainter extends CustomPainter {
  final Color color;
  final double radius;
  CirclePainter({required this.color, required this.radius});

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = color;
    canvas.drawCircle(size.center(Offset.zero), radius, paint);
  }

  @override
  bool shouldRepaint(covariant CirclePainter old) =>
      old.color != color || old.radius != radius;
}

shouldRepaint: The Core Optimization

shouldRepaint is called when a new painter instance is supplied to CustomPaint. If it returns false, Flutter skips the paint phase and reuses the cached layer.

  • Compare only the fields that affect pixels.
  • Returning true unconditionally defeats the optimization.
  • Returning false when something did change causes stale, frozen visuals.

Compare values, not identity — two painter instances with equal fields should yield false.

@override
bool shouldRepaint(covariant CirclePainter old) {
  // Repaint ONLY when a visual input differs.
  return old.color != color || old.radius != radius;
}

// Anti-pattern: always repaints, no matter what.
// bool shouldRepaint(old) => true;

Repaint Boundaries Isolate Work

Even a correct shouldRepaint can't help if your painter shares a layer with a frequently-rebuilding subtree. CustomPaint automatically wraps itself in a RepaintBoundary when given a child, but for a standalone painter you often add one explicitly.

  • A RepaintBoundary gives the painted region its own compositing layer.
  • Repaints inside the boundary don't force ancestors or siblings to repaint.
  • Use the Flutter DevTools "Highlight Repaints" overlay to see which layers flash.
RepaintBoundary(
  child: CustomPaint(
    size: const Size(200, 200),
    painter: CirclePainter(color: Colors.blue, radius: 60),
  ),
)

Default Hit Testing Behavior

By itself a CustomPaint does not capture pointer events on the painted shapes — its hitTest defaults to returning null, meaning "defer to default box behavior." To make drawings tappable you wrap them in a gesture detector and/or override hitTest.

  • Return true: this position is a hit; consume the event.
  • Return false: not a hit; let it pass to widgets behind.
  • Return null: use the default (the whole bounding box is a hit if opaque).

Implementing Precise hitTest

Override hitTest to restrict taps to the actual drawn shape rather than the rectangular bounds. For a circle, test the distance from the center against the radius.

This lets the corners of the bounding box pass touches through to whatever is behind, which feels natural for non-rectangular art.

class CirclePainter extends CustomPainter {
  final Color color;
  final double radius;
  final Offset center;
  CirclePainter({required this.color, required this.radius, required this.center});

  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawCircle(center, radius, Paint()..color = color);
  }

  @override
  bool hitTest(Offset position) {
    // True only inside the circle, not the whole box.
    return (position - center).distance <= radius;
  }

  @override
  bool shouldRepaint(covariant CirclePainter old) =>
      old.color != color || old.radius != radius || old.center != center;
}

Modeling Distance Without Flutter

The math behind circular hit testing is just the Euclidean distance. Here is a pure-Dart model you can run on any judge to verify the logic before wiring it into hitTest.

A point is inside the circle when its distance to the center is less than or equal to the radius.

import 'dart:math';

bool insideCircle(double px, double py, double cx, double cy, double r) {
  final dx = px - cx;
  final dy = py - cy;
  return sqrt(dx * dx + dy * dy) <= r;
}

void main() {
  const cx = 100.0, cy = 100.0, r = 60.0;
  print(insideCircle(120, 110, cx, cy, r)); // true (inside)
  print(insideCircle(10, 10, cx, cy, r));   // false (corner)
  print(insideCircle(160, 100, cx, cy, r)); // true (on edge)
}

Hit Testing with Path.contains

For arbitrary shapes, build a Path in paint and reuse it in hitTest via Path.contains. Storing the path as a field avoids rebuilding it twice per frame.

  • path.contains(position) returns true when the point lies inside the filled region.
  • Cache the path; rebuild it only when geometry inputs change.
class StarPainter extends CustomPainter {
  final Path _path;
  final Color color;
  StarPainter({required this.color, required Size size})
      : _path = _buildStar(size);

  static Path _buildStar(Size size) {
    final p = Path();
    p.moveTo(size.width / 2, 0);
    p.lineTo(size.width, size.height);
    p.lineTo(0, size.height);
    p.close();
    return p;
  }

  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawPath(_path, Paint()..color = color);
  }

  @override
  bool hitTest(Offset position) => _path.contains(position);

  @override
  bool shouldRepaint(covariant StarPainter old) => old.color != color;
}

Repainting Only on Animation Ticks

For animated painters, pass the driving Listenable (such as an AnimationController) to CustomPainter's repaint super-parameter. The painter then repaints when the animation ticks — without rebuilding the whole widget tree.

  • super(repaint: animation) wires the painter to listen directly.
  • This skips setState and avoids rebuilding siblings every frame.
class PulsePainter extends CustomPainter {
  final Animation<double> animation;
  PulsePainter(this.animation) : super(repaint: animation);

  @override
  void paint(Canvas canvas, Size size) {
    final r = 20 + animation.value * 40;
    canvas.drawCircle(
      size.center(Offset.zero), r, Paint()..color = Colors.teal);
  }

  // No need to compare values: the repaint Listenable drives it.
  @override
  bool shouldRepaint(covariant PulsePainter old) => false;
}

Wiring Gestures to a Painter

Hit testing on the painter decides whether the painted region is opaque to pointers, but you still attach a GestureDetector to react. Combine them: precise hitTest plus a detector gives shape-accurate taps.

  • The detector's onTapDown gives a local Offset.
  • You can re-run the same geometry check to identify which sub-shape was hit.
GestureDetector(
  behavior: HitTestBehavior.deferToChild,
  onTapUp: (details) {
    final local = details.localPosition;
    if ((local - center).distance <= radius) {
      debugPrint('Circle tapped at $local');
    }
  },
  child: CustomPaint(
    size: const Size(200, 200),
    painter: CirclePainter(
        color: Colors.blue, radius: 60, center: const Offset(100, 100)),
  ),
)

A Repaint Optimization Checklist

Bring it together with a practical checklist for performant custom painting:

  • Compare values in shouldRepaint — never blanket-return true.
  • Use repaint: for animations instead of setState.
  • Wrap in RepaintBoundary to isolate the painted layer.
  • Cache Path and Paint objects; don't allocate per frame.
  • Override hitTest so non-rectangular art passes through unused space.
  • Profile with DevTools repaint highlighting and the timeline.

Quick Check: shouldRepaint

Consider what your shouldRepaint implementation should return to keep painting performant.

Recap

You learned how to keep custom-painted Flutter UIs both interactive and fast:

  • shouldRepaint should compare only visual inputs by value, returning true just when they change.
  • hitTest controls pointer routing: precise shape tests let non-rectangular art pass unused taps through.
  • Path.contains and distance math give shape-accurate hit detection; cache the path.
  • RepaintBoundary isolates the painted layer so repaints don't cascade.
  • repaint: wires animations directly to the painter, avoiding full rebuilds.

Together these techniques eliminate wasted frames and make your canvas drawings feel native and responsive.

Sıkça Sorulan Sorular

“Vuruş Testi ve Yeniden Çizim Optimizasyonu” dersi ücretsiz mi?

Evet — “Vuruş Testi ve Yeniden Çizim Optimizasyonu” dersin tüm metni burada web'de ücretsiz olarak okunabilir. Etkileşimli olarak pratik yapmak (yerleşik kod editörü ve 7/24 yapay zeka koçu) ve Flutter Mobile Development kursunun geri kalanını açmak için CoddyKit PRO'ya yükselt. Flutter Mobile Development kursu toplamda 4 dersten oluşur.

“Vuruş Testi ve Yeniden Çizim Optimizasyonu” dersinde ne öğreneceğim?

Çizimi verimli tutmak için özel vuruş testi ve shouldRepaint mantığı uygulayın. Flutter Mobile Development ile uygulamalı kodu tarayıcıda doğrudan çalıştırarak pratik yaparsın ve 7/24 yapay zeka koçu dersi çalışırken sorularını yanıtlar.

Flutter Mobile Development öğrenmeye başlamak için deneyim gerekli mi?

Önceden deneyim gerekmez. CoddyKit'te Flutter Mobile Development, başlangıçtan ileri seviyeye kadar yapılandırıldığı için buradan başlayabilir veya başından başlayıp kendi hızında ilerleme yapabilirsin. Bu, 4 dersinin 4. dersidir.

“Vuruş Testi ve Yeniden Çizim Optimizasyonu” dersi ne kadar sürer?

Çoğu CoddyKit dersi yaklaşık 5–10 dakika sürer. Her biri kısa ve etkileşimli olduğu için sabit ilerleme yaparsın ve web ile uygulama arasında tam olarak bıraktığın yerden devam edebilirsin.

Bu Flutter Mobile Development dersinde kod yazıp çalıştırabilir miyim?

Evet. Her Flutter Mobile Development dersi yerleşik bir kod editörü içerir, bu sayede tarayıcıda gerçek kod yazıp çalıştırabilir ve anlık yapay zeka geri bildirimi alırsın — yerel kurulum gerekli değildir.

Bu kursun tüm dersleri

  1. Canvas API'si: Yollar, Boyamalar ve Gölgelendiriciler
  2. Etkileşimli Özel Grafik Bileşeni Oluşturma
  3. Kırpma, Karışım Kipleri ve Katman Birleştirme
  4. Vuruş Testi ve Yeniden Çizim Optimizasyonu
← Flutter Mobile Development Sayfasına Dön