CustomPainter — 개요, Flutter에서의 paint 및 shouldRepaint 메서드

저자: IT Sectr 게시일: 2026-07-22 읽는 시간: 10 분

CustomPainter는 Flutter SDK의 추상 클래스로, Canvas에서 커스텀 2D 그래픽 렌더링을 완전히 제어할 수 있게 해줍니다. 개발자는 CustomPainter의 하위 클래스를 만들고 paint(Canvas, Size) 및 shouldRepaint(covariant CustomPainter oldDelegate)라는 두 가지 필수 메서드를 구현합니다. Flutter API 문서(2026)에 따르면 CustomPainter는 CustomPaint 위젯과 함께 사용되며, 애니메이션 프레임 또는 상태 변경 시마다 제공된 CustomPainter 객체에 렌더링을 위임합니다.

핵심 사항

  • CustomPainter — paint 및 shouldRepaint 메서드를 통해 Canvas에서 커스텀 렌더링을 정의하는 Flutter 추상 클래스
  • CustomPaint — CustomPainter를 painter 또는 foregroundPainter 매개변수로 받아 결과를 화면에 표시하는 위젯
  • Canvas — 드로잉 메서드(drawLine, drawCircle, drawPath, drawRect, drawArc 등)를 제공하는 객체
  • shouldRepaint — 업데이트 시 객체를 다시 그려야 하는지 여부를 결정하는 최적화 메서드
  • size — 부모 위젯에서 paint(Canvas canvas, Size size)로 전달되는 드로잉 영역의 크기

CustomPainter란?

CustomPainter는 커스텀 2D 그래픽을 구현하기 위한 Flutter의 기본 클래스입니다. Android에서 onDraw를 재정의하는 방식을 대체하여 더 유연하고 선언적인 렌더링 모델을 제공합니다. 개발자는 CustomPainter의 하위 클래스를 만들고 Canvas와 Size를 받는 paint 메서드를 재정의하여 Canvas에서 모든 드로잉 작업을 실행합니다. CustomPaint 위젯은 paint 호출을 관리하고 결과를 위젯 트리에 통합합니다.

CustomPainter와 Canvas를 통한 직접 드로잉의 주요 차이점은 렌더링 로직(paint)과 다시 그리기 트리거(shouldRepaint)의 분리입니다. Flutter 시스템은 부모 위젯이 변경될 때마다 shouldRepaint를 호출합니다. shouldRepaint가 false를 반환하면 paint가 호출되지 않아 리소스를 절약합니다. 이 메커니즘은 RenderObject의 shouldRebuild와 유사하며, 세 개의 트리(Widget, Element, RenderObject)를 가진 Flutter 아키텍처의 일부입니다.

CustomPaintpainter(배경 레이어) 및 foregroundPainter(전경 레이어) 매개변수를 받는 위젯입니다. 두 매개변수 모두 CustomPainter 객체를 받습니다. 배경 painter는 CustomPaint의 자식 위젯 전에 그려지고 foregroundPainter는 이후에 그려집니다. 이를 통해 CustomPaint를 장식적 배경 또는 오버레이가 있는 컨테이너로 사용하면서 내부에 일반 위젯을 포함하는 기능을 잃지 않습니다.

Flutter의 Canvas와 Paint

Canvas는 Flutter의 중앙 드로잉 객체로, 그래픽 표면을 캡슐화합니다. 약 40개의 드로잉 메서드(drawLine, drawCircle, drawRect, drawPath, drawArc, drawImage, drawPicture, drawPoints 등)를 제공합니다. Flutter의 Canvas는 Android Canvas의 유사한 클래스에 해당하지만 Dart 아키텍처 및 Flutter Engine에 맞게 조정되었습니다. Canvas는 원점(0, 0)이 왼쪽 상단 모서리인 좌표계에서 작동합니다.

Paint — paint() 메서드와 혼동하지 마세요 — 드로잉 스타일(색상, 선 두께, 스타일(fill 또는 stroke), 불투명도, 혼합 모드, 안티앨리어싱 마스크)을 정의하는 클래스입니다. Paint는 드로잉 전에 구성되고 Canvas 메서드에 전달됩니다. 중요: Paint는 변경 가능한 객체이지만 한 번 생성하고 필요한 매개변수만 변경하여 재사용하는 것이 좋습니다.

Canvas 메서드용도매개변수
drawCircle원 그리기Offset center, double radius, Paint paint
drawLine선분 그리기Offset p1, Offset p2, Paint paint
drawRect사각형 그리기Rect rect, Paint paint
drawPath임의 경로 그리기Path path, Paint paint
drawArc호 또는 부채꼴 그리기Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint
drawRRect둥근 모서리 사각형RRect rrect, Paint paint
drawOval타원 그리기Rect rect, Paint paint
drawImageImage에서 이미지 그리기Image image, Offset p, Paint paint

Canvas 변환

Canvas는 좌표계 변환을 지원합니다: translate(double dx, double dy)는 원점 이동, rotate(double radians)는 시스템 회전, scale(double sx, double sy)는 크기 조정, skew(double sx, double sy)는 기울이기. 변환 전에 save()를 통해 Canvas 상태를 저장하고 완료 후 restore()를 통해 복원하는 것이 좋습니다. 이는 후속 paint 호출에서 변환이 누적되는 것을 방지합니다.

paint 및 shouldRepaint 메서드

paint(Canvas canvas, Size size)는 모든 드로잉 로직을 구현하는 필수 메서드입니다. canvas 매개변수는 드로잉을 위한 그래픽 표면입니다. size 매개변수는 드로잉에 할당된 영역의 크기(논리 픽셀 단위의 너비와 높이)입니다. Canvas는 원점(0,0)이 드로잉 영역의 왼쪽 상단 모서리에 해당하도록 미리 구성됩니다. 모든 좌표는 이 원점을 기준으로 지정됩니다.

shouldRepaint(covariant CustomPainter oldDelegate)는 다시 그리기 빈도를 최적화하는 필수 메서드입니다. Flutter는 CustomPainter에 영향을 미치는 위젯 트리 업데이트 시마다 shouldRepaint를 호출합니다. 메서드가 false를 반환하면 이미 렌더링된 이미지가 유효한 것으로 간주되고 paint가 호출되지 않습니다. 일반적인 구현: return oldDelegate.someField != someField — 이전 painter와 새 painter의 필드를 비교하여 다시 그리기 필요성을 결정합니다.

애니메이션 또는 지속적으로 변화하는 이미지(시계, 로딩 표시기, 차트)의 경우 shouldRepaint는 상태가 변경될 때마다 true를 반환해야 합니다. 객체가 정적(로고, 아이콘, 배경)인 경우 shouldRepaint는 첫 렌더링 후 false를 반환합니다. Flutter는 자동으로 paint 결과를 캐시하고 크기, 불투명도 또는 shouldRepaint 호출이 변경된 경우에만 다시 그립니다.

Dart에서 CustomPainter를 사용한 커스텀 드로잉 예제

GaugePainter — 애니메이션 채움 기능이 있는 커스텀 원형 게이지를 만들어 보겠습니다. CustomPainter는 현재 값(0.0에서 1.0까지의 진행률)을 받고 그라데이션이 있는 호를 그립니다. shouldRepaint 메서드는 최적화를 위해 진행률 값을 비교합니다. CustomPaint는 드로잉 영역을 제한하기 위해 size 매개변수와 함께 위젯 트리에 배치됩니다.

dart
class GaugePainter extends CustomPainter {
    final double progress;
    final Color startColor;
    final Color endColor;

    GaugePainter({
        required this.progress,
        this.startColor = Colors.blue,
        this.endColor = Colors.cyan,
    });

    @override
    void paint(Canvas canvas, Size size) {
        final center = Offset(size.width / 2,
            size.height / 2);
        final radius = center.dx - 12;
        final bgPaint = Paint()
            ..color = Colors.grey.withOpacity(0.3)
            ..style = PaintingStyle.stroke
            ..strokeWidth = 16
            ..strokeCap = StrokeCap.round;

        final progressPaint = Paint()
            ..shader = LinearGradient(
                colors: [startColor, endColor],
            ).createShader(
                Rect.fromCircle(
                    center, radius))
            ..style = PaintingStyle.stroke
            ..strokeWidth = 16
            ..strokeCap = StrokeCap.round;

        canvas.drawCircle(center, radius, bgPaint);
        canvas.drawArc(
            Rect.fromCircle(center, radius),
            -math.pi / 2,
            2 * math.pi * progress,
            false, progressPaint);
    }

    @override
    bool shouldRepaint(
        GaugePainter oldDelegate) {
        return oldDelegate.progress != progress;
    }
}

복잡한 모양을 위한 Path와 함께 사용하는 CustomPainter

예제는 Path를 사용하여 파형 차트를 그립니다. Path를 사용하면 선, 베지어 곡선 및 호에서 임의의 윤곽선을 만들 수 있습니다. 윤곽선을 만든 후 색상으로 채우거나 선으로 테두리를 그립니다. 예제에서는 파형을 시뮬레이션하는 점 배열로 차트를 만듭니다.

dart
class WavePainter extends CustomPainter {
    final List<double> values;

    WavePainter({required this.values});

    @override
    void paint(Canvas canvas, Size size) {
        final path = Path();
        final dx = size.width /
            (values.length - 1);

        path.moveTo(0, size.height);
        for (var i = 0; i < values.length; i++) {
            final x = i * dx;
            final y = size.height *
                (1 - values[i]);
            path.lineTo(x, y);
        }
        path.lineTo(size.width, size.height);
        path.close();

        final fillPaint = Paint()
            ..color = Colors.blue.withOpacity(0.3)
            ..style = PaintingStyle.fill;

        canvas.drawPath(path, fillPaint);
    }

    @override
    bool shouldRepaint(
        WavePainter oldDelegate) {
        return oldDelegate.values != values;
    }
}

ForegroundPainter와 backgroundPainter의 차이

painter(배경)는 CustomPaint의 자식 위젯 전에 그려집니다. CustomPaint에 자식 위젯(child 매개변수)이 있는 경우 배경 painter가 그 아래에 표시됩니다. 이는 장식(진행 표시줄, 배경 패턴, 워터마크)에 적합합니다. foregroundPainter는 자식 위젯 이후에 그려지며, 즉 그 위에 표시됩니다. 오버레이(하이라이트, 마스크, 측정 눈금자, 주석)에 사용됩니다.

painter와 foregroundPainter의 차이는 Z-순서에 중요합니다. 콘텐츠 뒤에 무언가를 그려야 하는 경우 — painter를 사용합니다. 콘텐츠 위에 그리는 경우 — foregroundPainter를 사용합니다. 경우에 따라 두 painter를 동시에 사용할 수 있습니다: painter를 통한 배경, foregroundPainter를 통한 오버레이 레이블. 이를 통해 서로 다른 그래픽 레이어의 로직을 두 개의 별도 CustomPainter 클래스로 분리할 수 있습니다.

paint 메서드의 size 매개변수는 두 painter 모두 동일하며 CustomPaint에 할당된 영역의 크기에 의해 결정됩니다. 크기는 CustomPaint의 size 매개변수를 통해 명시적으로 설정하거나 부모 컨테이너를 통해 암시적으로 설정할 수 있습니다. 크기가 Size.zero인 경우 paint가 호출되지 않습니다 — Flutter는 크기가 0인 영역의 렌더링을 건너뜁니다. 요소의 나타남을 애니메이션화할 때 이 동작을 고려해야 합니다.

CustomPainter 성능 최적화

CustomPainter 성능의 주요 규칙은 paint 메서드 내에서 객체를 생성하지 않는 것입니다. Canvas, Paint, Path, Rect, Offset 및 기타 드로잉 객체는 CustomPainter 생성자에서 생성되거나 캐시되어야 합니다. paint 내에서 객체를 생성하면 매 프레임마다 할당이 발생하여 초당 60프레임에서 빈번한 가비지 컬렉션과 애니메이션 지터(jank)가 발생합니다.

Path 캐싱은 반복되는 복잡한 경로의 렌더링을 크게 가속화합니다. 프레임 간에 경로가 변경되지 않는 경우(예: 배경 그리드) Path를 한 번 만들고 여러 번 재사용하세요. RepaintBoundary는 상위 위젯 트리가 변경될 때 CustomPainter의 다시 그리기를 방지하는 래퍼 위젯입니다. CustomPainter가 정적인 경우 격리를 위해 RepaintBoundary로 감쌉니다.

빈번한 다시 그리기가 있는 애니메이션 CustomPainter의 경우, 애니메이션 값이 변경될 때마다 true를 반환하는 shouldRepaint와 함께 Ticker 또는 AnimationController를 사용하세요. 불필요하게 매 프레임 다시 그리는 대신 shouldRepaint에서 애니메이션 필드를 비교하는 것이 최적입니다. 복잡한 그라데이션을 위한 Rect 캐싱: 그라데이션 색상이 변경되지 않는 경우 Paint.shader를 통해 Shader를 캐시할 수 있습니다.

자주 묻는 질문

Flutter에서 CustomPainter와 Canvas의 차이점은 무엇인가요?

CustomPainter는 Canvas에 무엇을 어떻게 그릴지 정의하는 계약 클래스입니다. Canvas는 드로잉 객체 자체로, 메서드(drawCircle, drawLine 등)를 제공합니다. CustomPainter는 paint 메서드를 통해 Canvas를 받아 그 사용을 제어합니다. 하나의 Canvas는 다른 시간에 다른 CustomPainter에 의해 사용될 수 있습니다.

CustomPainter를 CustomPaint 없이 사용할 수 있나요?

아니요, CustomPainter는 CustomPaint 없이 의미가 없습니다. CustomPaint는 CustomPainter의 paint 메서드를 호출하고 결과를 화면에 표시하는 위젯입니다. CustomPaint가 없으면 Canvas가 생성되지 않고 paint가 실행되지 않습니다. 그러나 Canvas로 자체 RenderObject를 구현할 수는 있지만 이는 더 낮은 수준의 접근 방식입니다.

CustomPainter를 애니메이션화하는 방법은?

생성자 매개변수를 통해 애니메이션 값을 CustomPainter에 전달하면서 Ticker와 함께 AnimationController를 사용하세요. 값이 변경되면 CustomPaint를 포함하는 위젯에서 setState()를 호출하여 shouldRepaint 호출을 트리거합니다. shouldRepaint가 true를 반환하면 시스템이 새 애니메이션 프레임에 대해 paint를 호출합니다.

데이터가 변경되어도 CustomPainter가 업데이트되지 않는 이유는?

원인은 거의 항상 shouldRepaint가 false를 반환하기 때문입니다. oldDelegate 필드가 새 값과 올바르게 비교되는지 확인하세요. 변경 가능한 객체를 사용하는 경우 객체 참조가 동일하게 유지되므로 shouldRepaint가 변경을 감지하지 못할 수 있습니다. 올바른 비교를 위해 == 또는 불변 객체를 사용하세요.

ReorderableListView에서 애니메이션에 CustomPainter를 사용할 수 있나요?

, CustomPainter는 복잡한 목록에서 커스텀 장식을 렌더링하는 데 사용됩니다. 그러나 항목을 재정렬할 때 ListView 내부의 RepaintBoundary가 각 항목의 렌더링을 캐시한다는 점에 유의하세요. CustomPainter는 가벼워야 하며 드래그 애니메이션을 느리게 하지 않도록 paint에 무거운 작업을 포함해서는 안 됩니다.

요약

  • CustomPainter — paint 및 shouldRepaint 메서드로 커스텀 2D 렌더링을 위한 Flutter 추상 클래스
  • CustomPaint — 그래픽 표시를 위해 painter(배경)와 foregroundPainter(전경)를 받는 위젯
  • Canvas — 드로잉 메서드가 있는 객체: drawLine, drawCircle, drawPath, drawRect, drawArc 등
  • Paint — 드로잉 스타일(색상, 두께, 스타일, 불투명도, 혼합 모드)을 정의하는 클래스
  • shouldRepaint — 렌더링에 영향을 주는 필드가 변경될 때만 true를 반환하는 최적화 메서드
  • paint 내에서 객체 생성 금지 — 60fps를 위해 모든 Paint, Path, Rect를 캐시해야 함
  • RepaintBoundary — 상위 위젯 트리의 다시 그리기로부터 CustomPainter를 격리하는 위젯

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기