CustomPainter — Canvas로 직접 그리기
CustomPainter — Canvas로 직접 그리기
기본 위젯만으로 표현하기 어려운 커스텀 그래픽, 차트, 게이지 등을 만들 때 CustomPainter를 사용합니다. HTML Canvas나 Android의 Canvas와 비슷한 개념입니다.
CustomPainter 기본 구조
class MyPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
// 여기서 그리기 작업 수행
final paint = Paint()
..color = Colors.blue
..strokeWidth = 2
..style = PaintingStyle.stroke; // stroke: 테두리, fill: 채우기
canvas.drawRect(
Rect.fromLTWH(10, 10, size.width - 20, size.height - 20),
paint,
);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false; // 다시 그릴 필요가 없으면 false
}
}
// 사용
CustomPaint(
painter: MyPainter(),
size: const Size(300, 200),
)
Paint 속성
final paint = Paint()
..color = Colors.blue // 색상
..strokeWidth = 3 // 선 두께
..style = PaintingStyle.fill // fill 또는 stroke
..strokeCap = StrokeCap.round // 선 끝 모양
..strokeJoin = StrokeJoin.round // 선 교차점 모양
..isAntiAlias = true // 안티앨리어싱
..shader = LinearGradient( // 그라데이션
colors: [Colors.blue, Colors.red],
).createShader(Rect.fromLTWH(0, 0, 200, 200))
..maskFilter = const MaskFilter.blur( // 블러
BlurStyle.normal, 5.0,
);
기본 도형 그리기
class ShapesPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue
..style = PaintingStyle.fill;
// 원
canvas.drawCircle(
Offset(size.width / 2, 80), // 중심점
50, // 반지름
paint,
);
// 사각형
paint.color = Colors.red;
canvas.drawRRect(
RRect.fromRectAndRadius(
const Rect.fromLTWH(50, 150, 200, 80),
const Radius.circular(12),
),
paint,
);
// 선
paint
..color = Colors.green
..style = PaintingStyle.stroke
..strokeWidth = 3;
canvas.drawLine(
const Offset(0, 250),
Offset(size.width, 250),
paint,
);
// 타원
paint
..color = Colors.orange
..style = PaintingStyle.fill;
canvas.drawOval(
const Rect.fromLTWH(50, 270, 200, 60),
paint,
);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Path로 복잡한 도형
class PathPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.purple
..style = PaintingStyle.fill;
// 삼각형
final path = Path()
..moveTo(size.width / 2, 20) // 시작점
..lineTo(20, size.height - 20) // 좌하단
..lineTo(size.width - 20, size.height - 20) // 우하단
..close(); // 경로 닫기
canvas.drawPath(path, paint);
// 베지어 곡선
final curvePaint = Paint()
..color = Colors.teal
..style = PaintingStyle.stroke
..strokeWidth = 3;
final curvePath = Path()
..moveTo(0, size.height / 2)
..quadraticBezierTo(
size.width / 2, 0, // 제어점
size.width, size.height / 2, // 끝점
);
canvas.drawPath(curvePath, curvePaint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
실전 예제: 원형 프로그레스바
class CircularProgressPainter extends CustomPainter {
final double progress; // 0.0 ~ 1.0
final Color progressColor;
final Color backgroundColor;
final double strokeWidth;
CircularProgressPainter({
required this.progress,
this.progressColor = Colors.blue,
this.backgroundColor = Colors.grey,
this.strokeWidth = 10,
});
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final radius = (size.width - strokeWidth) / 2;
// 배경 원
final bgPaint = Paint()
..color = backgroundColor.withOpacity(0.3)
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth;
canvas.drawCircle(center, radius, bgPaint);
// 진행률 호
final progressPaint = Paint()
..color = progressColor
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..strokeCap = StrokeCap.round;
canvas.drawArc(
Rect.fromCircle(center: center, radius: radius),
-3.14159 / 2, // 12시 방향에서 시작
2 * 3.14159 * progress, // 진행 각도
false,
progressPaint,
);
}
@override
bool shouldRepaint(covariant CircularProgressPainter oldDelegate) {
return oldDelegate.progress != progress;
}
}
// 사용
class CircularProgress extends StatelessWidget {
final double progress;
const CircularProgress({super.key, required this.progress});
@override
Widget build(BuildContext context) {
return SizedBox(
width: 120,
height: 120,
child: CustomPaint(
painter: CircularProgressPainter(
progress: progress,
progressColor: Colors.blue,
),
child: Center(
child: Text(
'${(progress * 100).toInt()}%',
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
),
),
);
}
}
텍스트 그리기
@override
void paint(Canvas canvas, Size size) {
final textPainter = TextPainter(
text: const TextSpan(
text: 'Canvas 텍스트',
style: TextStyle(
color: Colors.black,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
textDirection: TextDirection.ltr,
);
textPainter.layout(maxWidth: size.width);
textPainter.paint(canvas, const Offset(10, 10));
}
shouldRepaint 최적화
class OptimizedPainter extends CustomPainter {
final double value;
final Color color;
OptimizedPainter({required this.value, required this.color});
@override
void paint(Canvas canvas, Size size) {
// 그리기 로직
}
@override
bool shouldRepaint(covariant OptimizedPainter oldDelegate) {
// 값이 바뀔 때만 다시 그리기
return oldDelegate.value != value || oldDelegate.color != color;
}
}
면접 포인트: shouldRepaint에서 불필요한 리페인트를 방지하는 것이 성능의 핵심입니다. 항상 true를 반환하면 매 프레임마다 다시 그립니다.
정리
CustomPainter로 Canvas에 원, 사각형, 선, 경로 등을 직접 그릴 수 있습니다Paint객체로 색상, 두께, 채우기/테두리, 그라데이션 등을 설정합니다Path로 복잡한 도형과 베지어 곡선을 그릴 수 있습니다shouldRepaint를 적절히 구현하여 불필요한 리페인트를 방지하세요- 차트, 게이지, 커스텀 UI에 활용됩니다
댓글 로딩 중...