본문 바로가기

flutter

flutter CustomPaint

CustomPainter

화면을 직접 그릴 때 사용한다. 기존의 UI로 만들기 어려운 화면을 만들고 싶을 때 유용하다.

Canvas : 그림을 그리는 동작이 기록된다.

Paint : 그림의 특성들을 저장한다.

CustomPainter : Canvas, Paint를 담아낸다.

CustomPaint : CustomPainter의 상위 위젯이다.

 

CustomPainter 구현

class MyPainter extends CustomPainter{
  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint() // Paint 클래스는 어떤 식으로 화면을 그릴지 정할 때 사용
        ..color = Colors.deepPurpleAccent // 선의 색
        ..strokeCap = StrokeCap.round // 선의 끝 모양
        ..strokeWidth = 4.0; // 선의 굵기

    Offset p1 = const Offset(0.0, 0.0); // 선을 그리기 위한 좌표값
    Offset p2 = Offset(size.width, size.height);

    canvas.drawLine(p1, p2, paint); // 선을 그림
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }

}

CustomPainter는 paint와 shouldRepaint를 구현해야한다.

paint는 화면을 그릴 때 사용하고 shouldRepaint는 화면을 새로 그릴지 말지 정한다.

canvas 객체를 사용하여 화면을 그린다.

 

CustomPaint 위젯

class PainterPage extends StatelessWidget {
  const PainterPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('custom painter'),
      ),
      body: CustomPaint(
        size: const Size(200, 200), // 위젯의 크기를 정한다.
        painter: MyPainter(), // painter에 그리기를 담당할 클래스를 넣는다.
      ),
    );
  }
}

CustomPaint는 위젯의 크기를 정하고 어떤 painter를 사용할지 결정한다.

painter의 종류로 foregroundPainter, painter가 있다.

두 painter는 그려지는 순서의 차이가 있다.

 

painter : painter -> child 순으로 그려진다. (나중에 그려지는 위젯이 맨앞에 존재)

class PainterPage extends StatelessWidget {
  const PainterPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('custom painter'),
      ),
      body: CustomPaint(
        size: const Size(200, 200), // 위젯의 크기를 정한다.
        painter: MyPainter(), // painter에 그리기를 담당할 클래스를 넣는다.
        child: Container(
          width: 200,
          height: 200,
          color: Colors.red,
        ),
      ),
    );
  }
}

painter가 먼저 그려지므로 child 위젯에 가려져서 painter가 그리는 선이 안보인다.

 

foregroundPainter : child -> foregroundPainter 순으로 그려진다.

CustomPaint(
        size: const Size(200, 200), // 위젯의 크기를 정한다.
        foregroundPainter: MyPainter(), // painter에 그리기를 담당할 클래스를 넣는다.
        child: Container(
          width: 200,
          height: 200,
          color: Colors.red,
        ),
      ),

foregroundPainter가 늦게 그려지므로 앞에 존재한다.

 

CustomPaint 위젯의 크기는 부모 -> 자식 -> CustomPaint 속성의 size 순으로 따라간다.

 

간단한 차트 그려보기

bar_chart.dart

class BarChart extends CustomPainter{

  double percentage = 0;
  Color fillColor;
  Color remainColor;

  BarChart({required this.percentage, required this.fillColor, this.remainColor = Colors.grey});

  @override
  void paint(Canvas canvas, Size size) {

    double totalLength = size.width;
    double fillLength = size.width*(percentage/100);

    drawLines(canvas, 0, fillLength, fillColor);
    drawLines(canvas, fillLength, totalLength, remainColor);
    drawText(canvas, '$percentage', fillLength);

  }

  void drawLines(Canvas canvas, double startX, double endX, Color lineColor){
    Paint paint = Paint()
      ..color = lineColor
      ..strokeWidth = 20
      ..style = PaintingStyle.fill;

    Offset p1 = Offset(startX, 0);
    Offset p2 = Offset(endX, 0);

    canvas.drawLine(p1, p2, paint);
  }

  void drawText(Canvas canvas, String text, double endX) {
    TextSpan sp = TextSpan(style: TextStyle(fontSize: 15, color: Colors.white), text: text); // TextSpan은 Text위젯과 거의 동일하다.
    TextPainter tp = TextPainter(text: sp, textDirection: TextDirection.ltr);

    tp.layout(); // 필수! 텍스트 페인터에 그려질 텍스트의 크기와 방향를 정함.
    double dx = endX - tp.width;
    double dy =  - tp.height / 2;

    Offset offset = Offset(dx, dy);
    tp.paint(canvas, offset);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }

}

circle_chart.dart

class CircleChart extends CustomPainter {
  List list;
  final Paint _paint = Paint()..isAntiAlias = true;

  CircleChart({required this.list});

  @override
  void paint(Canvas canvas, Size size) {
    Offset center = Offset(size.width / 2, size.height / 2);

    double radius = min(size.width / 2, size.height / 2);

    double startRadian = -pi / 2;

    for (var i = 0; i < list.length; i++) {
      var item = list[i];

      double sweepRadian = (item['rate'] / 100) * 2 * pi;

      _paint.color = item['color'];

      canvas.drawArc(Rect.fromCircle(center: center, radius: radius),
          startRadian, sweepRadian, true, _paint);

      startRadian += sweepRadian;
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}

home_page.dart

- bar_chart 관련

  Widget _barGraphLabel(String text){
    return Text(text);
  }

  Widget _nutrientContainer(){
    List _list = [
      {"name": '탄수화물', "rate": 24.00, "color": Colors.indigo},
      {"name": '단백질', "rate": 65.00, "color": Colors.indigoAccent},
      {"name": '지방', "rate": 51.00, "color": Colors.blueAccent},
      {"name": '총 식이섬유', "rate": 24.00, "color": Colors.teal},
      {"name": '콜레스테롤', "rate": 48.00, "color": Colors.cyan},
      {"name": '총 포화 지방산', "rate": 48.00, "color": Colors.cyanAccent},
    ];

    return SizedBox(
      width: double.infinity,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('다량영양소'),
          const SizedBox(height: 24),
          ...List.generate(_list.length, (index) => _barGraph(_list[index]['name'], _list[index]['rate'], _list[index]['color'])),
        ],
      ),
    );
  }

  Widget _barGraph(String label, double percentage, Color fillColor){
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        _barGraphLabel(label),
        const SizedBox(height: 5),
        _barGraphRow(percentage: percentage, fillColor: fillColor),
        const SizedBox(height: 10),
      ],
    );
  }

  Widget _barGraphRow({required double percentage, required fillColor}){
    return Row(
      children: [
        Expanded(
            child: CustomPaint(
              painter: BarChart(percentage: percentage, fillColor: fillColor),
            )
        ),
        const SizedBox(width: 10),
        const Text('100%')
      ],
    );
  }

- circle_chart 관련

  Widget _circleChart(){

    List _list = [
      {"name": '탄수화물', "rate": 28.30, "color": Colors.orange},
      {"name": '단백질', "rate": 35.85, "color": Colors.purple},
      {"name": '지방', "rate": 35.85, "color": Colors.green},
    ];

    return CustomPaint(
      size: const Size(150, 150),
      painter: CircleChart(list: _list),
    );
  }

아직 circle_chart에 text는 그리지 않았다... 텍스트가 그려질 좌표를 어떻게 할지 생각중

'flutter' 카테고리의 다른 글

BoxConstraints forces an infinite width  (0) 2022.05.26
IntrinsicWidth, UnconstrainedBox  (0) 2022.05.23
TextStyle 정의하기  (0) 2022.05.19
flutter + firebase 연동하기  (0) 2022.05.13
flutter listview (flutter codlabs)  (0) 2022.05.10