개발노트

64.[Flutter] CustomPainter로 도형 그리기 (Apple Watch 앱 따라해보기) 본문

앱 개발/Flutter

64.[Flutter] CustomPainter로 도형 그리기 (Apple Watch 앱 따라해보기)

mroh1226 2024. 2. 2. 16:50
반응형

CustomPainter

CustomPainter는 Flutter 프레임워크에서 사용자 지정 그래픽을 그리는 데 사용되는 클래스입니다. 이 클래스를 사용하면 사용자가 직접 화면에 그래픽을 그릴 수 있습니다. CustomPainter를 사용하면 사용자 지정 그래픽을 그리기 위해 주어진 화면 영역에 대한 접근이 가능합니다.

CustomPainter를 사용하는 일반적인 프로세스는 다음과 같습니다:

  1. CustomPainter 클래스 구현: 사용자 정의 그래픽을 그리기 위해 CustomPainter 클래스를 확장하여 사용자 지정 그래픽을 정의합니다. CustomPainter 클래스는 paint() 메서드를 구현해야 합니다. 이 메서드에서 그래픽을 그립니다.
  2. CustomPainter 인스턴스 생성: CustomPainter 클래스의 인스턴스를 생성하고 사용자 지정 그래픽을 그릴 때 필요한 모든 매개 변수를 전달합니다.
  3. 그리기 위한 위젯에 CustomPainter 적용: CustomPainter 인스턴스를 사용하여 그릴 위젯에 적용합니다. 주로 CustomPaint 위젯을 사용하여 CustomPainter를 적용합니다. CustomPaint 위젯은 painter 속성을 통해 CustomPainter를 받습니다.
  4. 화면에 그래픽 그리기: CustomPainter에서 구현한 그리기 로직에 따라 화면에 그래픽이 그려집니다. Flutter 프레임워크는 이를 자동으로 처리합니다.

CustomPainter를 사용하면 다양한 종류의 그래픽을 그릴 수 있습니다. 이를 통해 사용자 지정 시각적 요소를 만들거나 복잡한 애니메이션을 구현할 수 있습니다. 주로 CustomPainter는 사용자 지정 그래픽, 차트, 애니메이션 및 UI 효과를 구현하는 데 사용됩니다.


CustomPaint  로 간단한 도형 그리기 예시.

CustomPainter로 오징어 게임 명함 그리기

 

CanvasScreen.dart (StatefulWidget 부분)
import 'package:flutter/material.dart';

class CanvasScreen extends StatefulWidget {
  static String routeName = "canvus";
  static String routeURL = "canvus";
  const CanvasScreen({super.key});

  @override
  State<CanvasScreen> createState() => _CanvasScreenState();
}

class _CanvasScreenState extends State<CanvasScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        elevation: 0,
        title: const Text("Canvas"),
      ),
      body: Center(
        child: CustomPaint(painter: Painter(), size: const Size(400, 400)),
      ),
    );
  }
}

 

Painter.cs (CustomPainter 부분)
class Painter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);

    //사각형
    final rect = Rect.fromCenter(
        center: center, width: 400, height: 200); //사각형 정보를 갖는 객체
    canvas.drawRect(
        rect, //Rect 사각형 객체
        Paint()
          ..color = Colors.black
          ..style = PaintingStyle.stroke
          ..strokeWidth = 5);

    //원
    canvas.drawCircle(
        Offset((size.width / 2) / 3, (size.height / 2)), //중심점 위치
        50, //반지름 길이
        Paint()
          ..color = Colors.blue //색상
          ..style = PaintingStyle.stroke //비어있는 스타일
          ..strokeWidth = 10); //두께

    //삼각형 (점을 잇는 직선으로 그리기)
    canvas.drawPath(
        Path()
          ..moveTo(size.width / 2.1, size.height / 2.6) //첫 시작점
          ..lineTo(size.width / 2 / 1.4, size.height / 1.6) //다음 점 위치
          ..lineTo(size.width / 1.65, size.height / 1.6) //다음 점 위치
          ..lineTo(size.width / 2.1, size.height / 2.6) //다음 점 위치
          ..close(),
        //다음 점이 없음을 알리고 moveTo(시작점)을 이어줌
        Paint()
          ..color = Colors.pinkAccent
          ..style = PaintingStyle.stroke
          ..strokeWidth = 10);

    //사각형
    canvas.drawRect(
        Rect.fromLTRB(size.width / 1.4, size.height / 2.6, size.width / 1.05,
            size.height / 1.6),
        Paint()
          ..color = Colors.orange //도형 색상
          ..style = PaintingStyle.stroke //도형 색 채우기 없음
          ..strokeWidth = 10); //테두리 넓이

    //canvas.drawLine();  직선
    //canvas.drawRect(); 사각형
    //canvas.drawPath(); 경로로 그리기
    //canvas.drawCircle();  원
    //canvas.drawArc();  곡선
    //canvas.drawParagraph(); //텍스트 문단
    //canvas.drawImage(); 이미지
    //문자 그리기는 TextPainter 사용
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;	//return 값에 따라 그림을 다시 그림 (false면 한번만 그림)
    // true 이기 때문에 CustomPainter 인스턴스가 생성될 때마다 항상 그림을 다시 그려야 함을 의미함
  }
}

CustomPaint  와 Animation 응용 예시. (움직이는 도형)

CustomPainterAnimationController를 응용하여 Apple Watch 앱을 만들어 봅니다.
- 출처: 노마드코더:https://nomadcoders.co/

완성된 모습

 

AppleWatchScreen.dart (StatefulWidget 부분)
import 'dart:math';
import 'package:flutter/material.dart';

class AppleWatchScreen extends StatefulWidget {
  static String routeName = "applewatch";
  static String routeURL = "applewatch";
  const AppleWatchScreen({super.key});

  @override
  State<AppleWatchScreen> createState() => _AppleWatchScreenState();
}

class _AppleWatchScreenState extends State<AppleWatchScreen>
    with SingleTickerProviderStateMixin {
  late final AnimationController _animationController = AnimationController(
    vsync: this,
    duration: const Duration(milliseconds: 2000),
  )..forward();

  late Animation<double> _animation =
      Tween(begin: 0.0, end: 1.5).animate(_curvedAnimation);

  late Animation<double> _animation2 =
      Tween(begin: 0.0, end: 1.5).animate(_curvedAnimation);

  late Animation<double> _animation3 =
      Tween(begin: 0.0, end: 1.5).animate(_curvedAnimation);

  late final CurvedAnimation _curvedAnimation = CurvedAnimation(
    parent: _animationController,
    curve: Curves.bounceOut,
  );

  @override
  void initState() {
    _animationController.addListener(() {});
    _animationController.addStatusListener((status) {});
    _animateValues();
    super.initState();
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }

  void _animateValues() {
    final newBegin = _animation.value;
    final random = Random();
    final newEnd = random.nextDouble() * 2.0;
    final random2 = Random();
    final newEnd2 = random2.nextDouble() * 2.0;
    final random3 = Random();
    final newEnd3 = random3.nextDouble() * 2.0;
    setState(() {
      _animation =
          Tween(begin: newBegin, end: newEnd).animate(_curvedAnimation);
      _animation2 =
          Tween(begin: newBegin, end: newEnd2).animate(_curvedAnimation);
      _animation3 =
          Tween(begin: newBegin, end: newEnd3).animate(_curvedAnimation);
    });
    _animationController.forward(from: 0);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      appBar: AppBar(
        elevation: 0,
        title: const Text("Apple Watch"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            AnimatedBuilder(
              animation: _animation,
              builder: (context, child) {
                return CustomPaint(
                  painter: AppleWatchPainter(
                    progress: _animation.value,
                    progress2: _animation2.value,
                    progress3: _animation3.value,
                  ),
                  size: const Size(200, 200),
                );
              },
            ),
            Padding(
              padding: const EdgeInsets.all(50),
              child: ElevatedButton(
                  onPressed: _animateValues, child: const Icon(Icons.refresh)),
            )
          ],
        ),
      ),
    );
  }
}

 

AppleWatchPainter.dart (CustomPainter 부분)
class AppleWatchPainter extends CustomPainter {
  final double progress;
  final double progress2;
  final double progress3;
  AppleWatchPainter(
      {required this.progress,
      required this.progress2,
      required this.progress3});

  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    const startingAngle = -0.5 * pi;

    final redArcRect = Rect.fromCircle(center: center, radius: 100);

    final redArcPaint = Paint()
      ..color = Colors.pink
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 20;

    canvas.drawArc(
      redArcRect,
      startingAngle,
      progress * pi,
      false,
      redArcPaint,
    );

    final redArcRect2 = Rect.fromCircle(center: center, radius: 78);

    final redArcPaint2 = Paint()
      ..color = Colors.limeAccent.shade700
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 20;

    canvas.drawArc(
      redArcRect2,
      startingAngle,
      progress2 * pi,
      false,
      redArcPaint2,
    );

    final redArcRect3 = Rect.fromCircle(
        center: Offset(size.width / 2, size.height / 2), radius: 56);

    final redArcPaint3 = Paint()
      ..color = Colors.lightBlue
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 20;

    canvas.drawArc(
      redArcRect3,
      startingAngle,
      progress3 * pi,
      false,
      redArcPaint3,
    );
  }

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

 

반응형
Comments