본문 바로가기
Flutter

Flutter CustomPainter를 사용한 커스텀 드로잉

by 안될개발 2025. 1. 18.

Flutter CustomPainter를 사용한 커스텀 드로잉

Flutter CustomPainter를 사용한 커스텀 드로잉

Flutter는 기본 위젯 외에도 복잡하고 정교한 그래픽을 구현할 수 있는 강력한 기능을 제공합니다. 그중 하나가 바로 CustomPainter입니다. CustomPainter를 사용하면 캔버스에 직접 드로잉을 수행할 수 있으며, 복잡한 사용자 정의 UI를 구현할 때 유용합니다.

이번 글에서는 CustomPainter의 기본 개념부터 실습 예제까지 다루며, 사용법과 최적화 팁을 안내합니다.

📌 1. CustomPainter란?

CustomPainter는 Flutter에서 저수준 그래픽을 그릴 수 있는 클래스로, 캔버스(Canvas)를 활용해 자유로운 형태의 UI를 만들 수 있습니다. Flutter에서 제공하는 기본 위젯으로 구현하기 어려운 도형, 차트, 애니메이션 등을 구현할 수 있습니다.

✅ 주요 특징

  • 저수준 드로잉: 사각형, 원, 곡선, 텍스트 등 다양한 형태를 직접 그릴 수 있음
  • 고성능: Flutter의 저수준 렌더링 엔진을 활용해 성능 최적화 가능
  • 재사용성: 여러 위젯에서 동일한 CustomPainter를 재활용 가능

✅ CustomPainter의 주요 메서드

메서드 설명
paint(Canvas, Size) 캔버스에 그림을 그리는 메서드
shouldRepaint() 다시 그릴 필요가 있는지 여부를 반환함

📌 2. 기본적인 CustomPainter 구현

✅ CustomPainter 기본 구조

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: CustomPainterExample(),
    );
  }
}

class CustomPainterExample extends StatelessWidget {
  const CustomPainterExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('CustomPainter 예제')),
      body: Center(
        child: CustomPaint(
          size: const Size(200, 200),
          painter: CirclePainter(),
        ),
      ),
    );
  }
}

class CirclePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.fill;

    canvas.drawCircle(Offset(size.width / 2, size.height / 2), 80, paint);
  }

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

✅ 코드 설명

  1. CustomPaint 위젯을 사용해 CirclePainter를 등록
  2. Canvas.drawCircle()로 원 그리기
  3. shouldRepaint()에서 재렌더링 여부 결정

📌 3. 다양한 도형 그리기

✅ 1) 사각형(Rectangle) 그리기

canvas.drawRect(Rect.fromLTWH(50, 50, 100, 100), paint);

✅ 2) 선(Line) 그리기

canvas.drawLine(Offset(0, 0), Offset(100, 100), paint);

✅ 3) 경로(Path) 그리기

final path = Path()
  ..moveTo(50, 50)
  ..lineTo(150, 50)
  ..lineTo(100, 150)
  ..close();
canvas.drawPath(path, paint);

📌 4. CustomPainter 활용 예제

✅ 1) 사용자 입력을 기반으로 드로잉

아래 예제는 화면에서 사용자가 터치한 지점을 따라 선을 그리는 예제입니다.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: DrawingExample(),
    );
  }
}

class DrawingExample extends StatefulWidget {
  const DrawingExample({super.key});

  @override
  State<DrawingExample> createState() => _DrawingExampleState();
}

class _DrawingExampleState extends State<DrawingExample> {
  final List<Offset> _points = [];

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPanUpdate: (details) {
        setState(() {
          _points.add(details.localPosition);
        });
      },
      onPanEnd: (_) => _points.clear(),
      child: CustomPaint(
        size: Size.infinite,
        painter: LinePainter(_points),
      ),
    );
  }
}

class LinePainter extends CustomPainter {
  final List<Offset> points;

  LinePainter(this.points);

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.red
      ..strokeWidth = 4.0
      ..strokeCap = StrokeCap.round;

    for (int i = 0; i < points.length - 1; i++) {
      canvas.drawLine(points[i], points[i + 1], paint);
    }
  }

  @override
  bool shouldRepaint(LinePainter oldDelegate) => true;
}

📌 5. 성능 최적화 팁

  1. shouldRepaint: 불필요한 재렌더링을 방지
  2. Layer 분리: 복잡한 드로잉은 PictureRecorder 사용
  3. Path 캐싱: 동일한 Path는 캐싱하여 성능 최적화

📌 6. 결론

Flutter의 CustomPainter는 정교한 그래픽과 맞춤형 UI를 구현하는 강력한 도구입니다. 간단한 도형부터 복잡한 사용자 입력 기반 드로잉까지 다양한 방식으로 활용할 수 있습니다.

  • 간단한 도형: drawCircle(), drawRect()
  • 사용자 입력: GestureDetector와 결합해 동적 드로잉

프로젝트에서 창의적인 UI를 구현하고 싶다면 CustomPainter를 적극적으로 활용해 보세요!