0Pricing
Flutter Mobile Development · บทเรียน

การทดสอบการชนและการปรับประสิทธิภาพการวาดใหม่

ใช้การทดสอบการชนแบบกำหนดเองและตรรกะ shouldRepaint เพื่อให้การวาดทำงานได้อย่างมีประสิทธิภาพ

การทดสอบการชนและการปรับประสิทธิภาพการวาดใหม่ เป็นบทเรียน Flutter Mobile Development ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Flutter Mobile Development และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Flutter Mobile Development มีบทเรียนทั้งหมด 4 บทเรียน

บางส่วนของบทเรียนนี้ยังไม่ได้รับการแปล และแสดงเป็นภาษาอังกฤษ

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.

คำถามที่พบบ่อย

บทเรียน “การทดสอบการชนและการปรับประสิทธิภาพการวาดใหม่” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การทดสอบการชนและการปรับประสิทธิภาพการวาดใหม่” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Flutter Mobile Development ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Flutter Mobile Development มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การทดสอบการชนและการปรับประสิทธิภาพการวาดใหม่”

ใช้การทดสอบการชนแบบกำหนดเองและตรรกะ shouldRepaint เพื่อให้การวาดทำงานได้อย่างมีประสิทธิภาพ คุณปฏิบัติ Flutter Mobile Development ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Flutter Mobile Development หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Flutter Mobile Development บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “การทดสอบการชนและการปรับประสิทธิภาพการวาดใหม่” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Flutter Mobile Development นี้ได้ไหม

ได้ บทเรียน Flutter Mobile Development ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. API Canvas: เส้นทาง สี และเชดเดอร์
  2. การสร้างวิดเจ็ตกราฟแบบกำหนดเองที่โต้ตอบได้
  3. การตัด การผสม และการประกอบเลเยอร์
  4. การทดสอบการชนและการปรับประสิทธิภาพการวาดใหม่
← กลับไปที่ Flutter Mobile Development