Flutter Canvas 그리기 예제 - (6) 드래그로 Canvas 내 Object 이동시키기

|

드래그로 Canvas 내 Object 이동시키기

node.dart

class Node {
  String id;
  String name;
  double x;
  double y;

  Node(this.id, this.name, this.x, this.y);
}

draggable_painter.dart

import 'package:flutter/material.dart';

import 'node.dart';

class DraggablePainter extends CustomPainter {
  static const gridWidth = 50.0;
  static const gridHeight = 50.0;

  var _width = 0.0;
  var _height = 0.0;

  final double offsetX;
  final double offsetY;
  final List<Node> nodeList;

  DraggablePainter(this.nodeList, this.offsetX, this.offsetY);

  void _drawBackground(Canvas canvas) {
    var paint = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.white70
      ..isAntiAlias = true;

    Rect rect = Rect.fromLTWH(0, 0, _width, _height);
    canvas.drawRect(rect, paint);
  }

  void _drawGrid(Canvas canvas) {
    var paint = Paint()
      ..style = PaintingStyle.stroke
      ..color = Colors.grey
      ..isAntiAlias = true;

    final gridRect = Rect.fromLTWH(
        offsetX % gridWidth - gridWidth,
        offsetY % gridHeight - gridHeight,
        _width + gridWidth,
        _height + gridHeight);

    final rows = _height / gridHeight;
    final cols = _width / gridWidth;

    for (int r = -1; r <= rows; r++) {
      final y = r * gridHeight + gridRect.top;
      final p1 = Offset(gridRect.left, y);
      final p2 = Offset(gridRect.right, y);

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

    for (int c = -1; c <= cols; c++) {
      final x = c * gridWidth + gridRect.left;
      final p1 = Offset(x, gridRect.top);
      final p2 = Offset(x, gridRect.bottom);

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

  void _drawNodes(Canvas canvas) {
    var paint = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.amber
      ..isAntiAlias = true;

    const textStyle = TextStyle(
      color: Colors.black,
      fontSize: 14,
    );

    const radius = 30.0;
    for (int i = 0; i < nodeList.length; i++) {
      final c = Offset(nodeList[i].x, nodeList[i].y);
      canvas.drawCircle(c, radius, paint);
      _drawText(
          canvas, nodeList[i].x, nodeList[i].y, nodeList[i].name, textStyle);
    }
  }

  void _drawText(Canvas canvas, centerX, centerY, text, style) {
    final textSpan = TextSpan(
      text: text,
      style: style,
    );

    final textPainter = TextPainter()
      ..text = textSpan
      ..textDirection = TextDirection.ltr
      ..textAlign = TextAlign.center
      ..layout();

    final xCenter = (centerX - textPainter.width / 2);
    final yCenter = (centerY - textPainter.height / 2);
    final offset = Offset(xCenter, yCenter);

    textPainter.paint(canvas, offset);
  }

  void _drawCanvas(Canvas canvas) {
    _drawBackground(canvas);
    _drawGrid(canvas);

    canvas.save();
    canvas.translate(offsetX, offsetY);
    _drawNodes(canvas);

    canvas.restore();
  }

  @override
  void paint(Canvas canvas, Size size) {
    _width = size.width;
    _height = size.height;

    _drawCanvas(canvas);
  }

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

draggable_canvas.dart

import 'package:flutter/material.dart';
import 'node.dart';
import 'draggable_painter.dart';

class DraggableObjectPage extends StatefulWidget {
  const DraggableObjectPage({Key key}) : super(key: key);

  @override
  State createState() => DraggableObjectPageState();
}

class DraggableObjectPageState extends State<DraggableObjectPage> {
  final List<Node> nodeList = List.empty(growable: true);

  var offsetX = 0.0;
  var offsetY = 0.0;

  var preX = 0.0;
  var preY = 0.0;

  Node currentNode;

  DraggableObjectPageState() {
    initNodes();
  }

  void initNodes() {
    nodeList.clear();
    nodeList.add(Node("1", "Node\n1", 150.0, 180.0));
    nodeList.add(Node("2", "Node\n2", 220.0, 40.0));
    nodeList.add(Node("3", "Node\n3", 380.0, 240.0));
    nodeList.add(Node("4", "Node\n4", 640.0, 190.0));
    nodeList.add(Node("5", "Node\n5", 480.0, 350.0));
  }

  Node getNode(x, y) {
    const radius = 30.0;
    for (final node in nodeList) {
      final c = Offset(node.x - x + offsetX, node.y - y + offsetY);
      if (c.distance <= radius) {
        return node;
      }
    }
    return null;
  }

  void _handlePanDown(details) {
    final x = details.localPosition.dx;
    final y = details.localPosition.dy;

    final node = getNode(x, y);
    if (node != null) {
      currentNode = node;
    } else {
      currentNode = null;
    }

    preX = x;
    preY = y;
  }

  void _handlePanUpdate(details) {
    final dx = details.localPosition.dx - preX;
    final dy = details.localPosition.dy - preY;

    if (currentNode != null) {
      setState(() {
        currentNode.x = currentNode.x + dx;
        currentNode.y = currentNode.y + dy;
      });
    } else {
      setState(() {
        offsetX = offsetX + dx;
        offsetY = offsetY + dy;
      });
    }

    preX = details.localPosition.dx;
    preY = details.localPosition.dy;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        onPanDown: _handlePanDown,
        onPanUpdate: _handlePanUpdate,
        child: CustomPaint(
          child: Container(),
          painter: DraggablePainter(nodeList, offsetX, offsetY),
        ),
      ),
    );
  }
}

Flutter Canvas 그리기 예제 - (5) 드래그로 Canvas 이동시키기

|

드래그로 Canvas 이동시키기

실행 화면

화면을 드래그(Drag & Drop)해서 Canvas를 이동시키는 예제입니다.

image

Grid 그린 부분이 비어있는 것을 볼 수 있는데, 이 부분은 약간만 수정해주면 되는 부분이라 아래 예제 다음에 다루도록 하겠습니다.

예제 코드

import 'package:flutter/material.dart';
import 'package:snowdeer_canvas_example/drag_and_drop/node.dart';

class DraggableObjectPage extends StatefulWidget {
  const DraggableObjectPage({Key key}) : super(key: key);

  @override
  State createState() => DraggableObjectPageState();
}

class DraggableObjectPageState extends State<DraggableObjectPage> {
  final List<Node> nodeList = List.empty(growable: true);

  var offsetX = 0.0;
  var offsetY = 0.0;

  var preX = 0.0;
  var preY = 0.0;

  DraggableObjectPageState() {
    initNodes();
  }

  void initNodes() {
    nodeList.clear();
    nodeList.add(Node("1", "Node\n1", 150.0, 180.0));
    nodeList.add(Node("2", "Node\n2", 220.0, 40.0));
    nodeList.add(Node("3", "Node\n3", 380.0, 240.0));
    nodeList.add(Node("4", "Node\n4", 640.0, 190.0));
    nodeList.add(Node("5", "Node\n5", 480.0, 350.0));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        onPanDown: (detail) {
          preX = detail.localPosition.dx;
          preY = detail.localPosition.dy;
        },
        onPanUpdate: (detail) {
          setState(() {
            final dx = detail.localPosition.dx - preX;
            final dy = detail.localPosition.dy - preY;

            offsetX = offsetX + dx;
            offsetY = offsetY + dy;

            preX = detail.localPosition.dx;
            preY = detail.localPosition.dy;
          });
        },
        child: CustomPaint(
          child: Container(),
          painter: DraggablePainter(nodeList, offsetX, offsetY),
        ),
      ),
    );
  }
}

class DraggablePainter extends CustomPainter {
  static const gridWidth = 50.0;
  static const gridHeight = 50.0;

  var _width = 0.0;
  var _height = 0.0;

  final offsetX;
  final offsetY;
  final List<Node> nodeList;

  DraggablePainter(this.nodeList, this.offsetX, this.offsetY);

  void _drawBackground(Canvas canvas) {
    var paint = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.white70
      ..isAntiAlias = true;

    Rect rect = Rect.fromLTWH(0, 0, _width, _height);
    canvas.drawRect(rect, paint);
  }

  void _drawGrid(Canvas canvas) {
    var paint = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.grey
      ..isAntiAlias = true;

    final rows = _height / gridHeight;
    final cols = _width / gridWidth;

    for (int r = 0; r < rows; r++) {
      final y = r * gridHeight;
      final p1 = Offset(0, y);
      final p2 = Offset(_width, y);

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

    for (int c = 0; c < cols; c++) {
      final x = c * gridWidth;
      final p1 = Offset(x, 0);
      final p2 = Offset(x, _height);

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

  void _drawNodes(Canvas canvas) {
    var paint = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.amber
      ..isAntiAlias = true;

    const textStyle = TextStyle(
      color: Colors.black,
      fontSize: 18,
    );

    const radius = 30.0;
    for (int i = 0; i < nodeList.length; i++) {
      final c = Offset(nodeList[i].x, nodeList[i].y);
      canvas.drawCircle(c, radius, paint);
      _drawText(
          canvas, nodeList[i].x, nodeList[i].y, nodeList[i].name, textStyle);
    }
  }

  void _drawText(Canvas canvas, centerX, centerY, text, style) {
    final textSpan = TextSpan(
      text: text,
      style: style,
    );

    final textPainter = TextPainter()
      ..text = textSpan
      ..textDirection = TextDirection.ltr
      ..textAlign = TextAlign.center
      ..layout();

    final xCenter = (centerX - textPainter.width / 2);
    final yCenter = (centerY - textPainter.height / 2);
    final offset = Offset(xCenter, yCenter);

    textPainter.paint(canvas, offset);
  }

  void _drawCanvas(Canvas canvas) {
    canvas.save();
    canvas.translate(offsetX, offsetY);

    _drawBackground(canvas);
    _drawGrid(canvas);
    _drawNodes(canvas);

    canvas.restore();
  }

  @override
  void paint(Canvas canvas, Size size) {
    _width = size.width;
    _height = size.height;

    _drawCanvas(canvas);
  }

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

배경은 유지하고 오브젝트만 이동시키기

아래 예제와 같이 배경 그리는 부분과 Grid 그리는 부분을 canvas.save() 바깥 쪽으로 빼주면 됩니다.

void _drawCanvas(Canvas canvas) {
    _drawBackground(canvas);
    _drawGrid(canvas);
    
    canvas.save();
    canvas.translate(offsetX, offsetY);

    _drawNodes(canvas);

    canvas.restore();
  }

다만 이럴 경우 오브젝트는 이동되지만, Grid의 눈금은 스크롤 되지 않는 문제가 있습니다. 따라서 Grid는 화면의 offset을 이용해서 계산해서 그려주는 편이 낫습니다.

수정된 Grid 그리는 함수

void _drawGrid(Canvas canvas) {
    var paint = Paint()
      ..style = PaintingStyle.stroke
      ..color = Colors.grey
      ..isAntiAlias = true;

    final gridRect = Rect.fromLTWH(
        offsetX % gridWidth - gridWidth,
        offsetY % gridHeight - gridHeight,
        _width + gridWidth,
        _height + gridHeight);

    final rows = _height / gridHeight;
    final cols = _width / gridWidth;

    for (int r = -1; r <= rows; r++) {
      final y = r * gridHeight + gridRect.top;
      final p1 = Offset(gridRect.left, y);
      final p2 = Offset(gridRect.right, y);

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

    for (int c = -1; c <= cols; c++) {
      final x = c * gridWidth + gridRect.left;
      final p1 = Offset(x, gridRect.top);
      final p2 = Offset(x, gridRect.bottom);

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

수정된 결과 화면

image

Flutter Canvas 그리기 예제 - (4) Canvas에 Text 그리기

|

Canvas에 Text 그리기

실행 화면

화면 특정 좌표에 원과 텍스트를 그리는 예제입니다. 원, 사각형, 라인 등은 좌표만 입력하면 쉽게 그릴 수 있기 때문에 사용법이 조금 다른 텍스트 그리기 위주로 포스팅합니다.

image

Text 그리는 함수 예제

void _drawText(Canvas canvas, centerX, centerY, text, style) {
    final textSpan = TextSpan(
      text: text,
      style: style,
    );

    final textPainter = TextPainter()
      ..text = textSpan
      ..textDirection = TextDirection.ltr
      ..textAlign = TextAlign.center
      ..layout();

    final xCenter = (centerX - textPainter.width / 2);
    final yCenter = (centerY - textPainter.height / 2);
    final offset = Offset(xCenter, yCenter);

    textPainter.paint(canvas, offset);
  }

전체 코드

import 'package:flutter/material.dart';
import 'package:snowdeer_canvas_example/drag_and_drop/node.dart';

class DraggableObjectPage extends StatefulWidget {
  const DraggableObjectPage({Key key}) : super(key: key);

  @override
  State createState() => DraggableObjectPageState();
}

class DraggableObjectPageState extends State<DraggableObjectPage> {
  final List<Node> nodeList = List.empty(growable: true);

  DraggableObjectPageState() {
    initNodes();
  }

  void initNodes() {
    nodeList.clear();
    nodeList.add(Node("1", "Node\n1", 150.0, 180.0));
    nodeList.add(Node("2", "Node\n2", 220.0, 40.0));
    nodeList.add(Node("3", "Node\n3", 380.0, 240.0));
    nodeList.add(Node("4", "Node\n4", 640.0, 190.0));
    nodeList.add(Node("5", "Node\n5", 480.0, 350.0));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        onTapDown: (detail) {
          print("onTapDown:(${detail.localPosition})");
        },
        onTapUp: (detail) {
          print("onTapUp(${detail.localPosition})");
        },
        onHorizontalDragUpdate: (detail) {
          print("onHorizontalDragUpdate:(${detail.localPosition})");
        },
        child: CustomPaint(
          child: Container(),
          painter: DraggablePainter(nodeList),
        ),
      ),
    );
  }
}

class DraggablePainter extends CustomPainter {
  static const gridWidth = 50.0;
  static const gridHeight = 50.0;

  var _width = 0.0;
  var _height = 0.0;

  final List<Node> nodeList;

  DraggablePainter(this.nodeList);

  void _drawBackground(Canvas canvas) {
    var paint = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.white70
      ..isAntiAlias = true;

    Rect rect = Rect.fromLTWH(0, 0, _width, _height);
    canvas.drawRect(rect, paint);
  }

  void _drawGrid(Canvas canvas) {
    var paint = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.grey
      ..isAntiAlias = true;

    final rows = _height / gridHeight;
    final cols = _width / gridWidth;

    for (int r = 0; r < rows; r++) {
      final y = r * gridHeight;
      final p1 = Offset(0, y);
      final p2 = Offset(_width, y);

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

    for (int c = 0; c < cols; c++) {
      final x = c * gridWidth;
      final p1 = Offset(x, 0);
      final p2 = Offset(x, _height);

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

  void _drawNodes(Canvas canvas) {
    var paint = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.amber
      ..isAntiAlias = true;

    const textStyle = TextStyle(
      color: Colors.black,
      fontSize: 18,
    );

    const radius = 30.0;
    for (int i = 0; i < nodeList.length; i++) {
      final c = Offset(nodeList[i].x, nodeList[i].y);
      canvas.drawCircle(c, radius, paint);
      _drawText(
          canvas, nodeList[i].x, nodeList[i].y, nodeList[i].name, textStyle);
    }
  }

  void _drawText(Canvas canvas, centerX, centerY, text, style) {
    final textSpan = TextSpan(
      text: text,
      style: style,
    );

    final textPainter = TextPainter()
      ..text = textSpan
      ..textDirection = TextDirection.ltr
      ..textAlign = TextAlign.center
      ..layout();

    final xCenter = (centerX - textPainter.width / 2);
    final yCenter = (centerY - textPainter.height / 2);
    final offset = Offset(xCenter, yCenter);

    textPainter.paint(canvas, offset);
  }

  @override
  void paint(Canvas canvas, Size size) {
    _width = size.width;
    _height = size.height;

    _drawBackground(canvas);
    _drawGrid(canvas);
    _drawNodes(canvas);
  }

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

전체 코드

import 'package:flutter/material.dart';
import 'package:snowdeer_canvas_example/drag_and_drop/node.dart';

class DraggableObjectPage extends StatefulWidget {
  const DraggableObjectPage({Key key}) : super(key: key);

  @override
  State createState() => DraggableObjectPageState();
}

class DraggableObjectPageState extends State {
  final List nodeList = List.empty(growable: true);

  DraggableObjectPageState() {
    initNodes();
  }

  void initNodes() {
    nodeList.clear();
    nodeList.add(Node("1", "Node\n1", 150.0, 180.0));
    nodeList.add(Node("2", "Node\n2", 220.0, 40.0));
    nodeList.add(Node("3", "Node\n3", 380.0, 240.0));
    nodeList.add(Node("4", "Node\n4", 640.0, 190.0));
    nodeList.add(Node("5", "Node\n5", 480.0, 350.0));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        onTapDown: (detail) {
          print("onTapDown:(${detail.localPosition})");
        },
        onTapUp: (detail) {
          print("onTapUp(${detail.localPosition})");
        },
        onHorizontalDragUpdate: (detail) {
          print("onHorizontalDragUpdate:(${detail.localPosition})");
        },
        child: CustomPaint(
          child: Container(),
          painter: DraggablePainter(nodeList),
        ),
      ),
    );
  }
}

class DraggablePainter extends CustomPainter {
  static const gridWidth = 50.0;
  static const gridHeight = 50.0;

  var _width = 0.0;
  var _height = 0.0;

  final List nodeList;

  DraggablePainter(this.nodeList);

  void _drawBackground(Canvas canvas) {
    var paint = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.white70
      ..isAntiAlias = true;

    Rect rect = Rect.fromLTWH(0, 0, _width, _height);
    canvas.drawRect(rect, paint);
  }

  void _drawGrid(Canvas canvas) {
    var paint = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.grey
      ..isAntiAlias = true;

    final rows = _height / gridHeight;
    final cols = _width / gridWidth;

    for (int r = 0; r < rows; r++) {
      final y = r * gridHeight;
      final p1 = Offset(0, y);
      final p2 = Offset(_width, y);

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

    for (int c = 0; c < cols; c++) {
      final x = c * gridWidth;
      final p1 = Offset(x, 0);
      final p2 = Offset(x, _height);

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

  void _drawNodes(Canvas canvas) {
    var paint = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.amber
      ..isAntiAlias = true;

    const textStyle = TextStyle(
      color: Colors.black,
      fontSize: 18,
    );

    const radius = 30.0;
    for (int i = 0; i < nodeList.length; i++) {
      final c = Offset(nodeList[i].x, nodeList[i].y);
      canvas.drawCircle(c, radius, paint);
      _drawText(
          canvas, nodeList[i].x, nodeList[i].y, nodeList[i].name, textStyle);
    }
  }

  void _drawText(Canvas canvas, centerX, centerY, text, style) {
    final textSpan = TextSpan(
      text: text,
      style: style,
    );

    final textPainter = TextPainter()
      ..text = textSpan
      ..textDirection = TextDirection.ltr
      ..textAlign = TextAlign.center
      ..layout();

    final xCenter = (centerX - textPainter.width / 2);
    final yCenter = (centerY - textPainter.height / 2);
    final offset = Offset(xCenter, yCenter);

    textPainter.paint(canvas, offset);
  }

  @override
  void paint(Canvas canvas, Size size) {
    _width = size.width;
    _height = size.height;

    _drawBackground(canvas);
    _drawGrid(canvas);
    _drawNodes(canvas);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}
</pre>

Flutter Canvas 그리기 예제 - (3) Canvas내의 터치(마우스) 좌표 획득하는 방법

|

Canvas내의 터치(마우스) 좌표 획득하는 방법

GestureDetector

GestureDetector를 이용해서 터치 좌표를 획득할 수 있습니다.

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        onTapDown: (detail) {
          print("onTapDown:(${detail.localPosition})");
        },
        onTapUp: (detail) {
          print("onTapUp(${detail.localPosition})");
        },
        onHorizontalDragUpdate: (detail) {
          print("onHorizontalDragUpdate:(${detail.localPosition})");
        },
        child: CustomPaint(
          child: Container(),
          painter: GridPainter(),
        ),
      ),
    );
  }
}

전체 코드

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        onTapDown: (detail) {
          print("onTapDown:(${detail.localPosition})");
        },
        onTapUp: (detail) {
          print("onTapUp(${detail.localPosition})");
        },
        onHorizontalDragUpdate: (detail) {
          print("onHorizontalDragUpdate:(${detail.localPosition})");
        },
        child: CustomPaint(
          child: Container(),
          painter: GridPainter(),
        ),
      ),
    );
  }
}

class GridPainter extends CustomPainter {
  static const gridWidth = 50.0;
  static const gridHeight = 50.0;

  var _width = 0.0;
  var _height = 0.0;

  void _drawBackground(Canvas canvas) {
    var paint = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.lime
      ..isAntiAlias = true;

    Rect rect = Rect.fromLTWH(0, 0, _width, _height);
    canvas.drawRect(rect, paint);
  }

  void _drawGrid(Canvas canvas) {
    var paint = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.black38
      ..isAntiAlias = true;

    final rows = _height / gridHeight;
    final cols = _width / gridWidth;

    for (int r = 0; r < rows; r++) {
      final y = r * gridHeight;
      final p1 = Offset(0, y);
      final p2 = Offset(_width, y);

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

    for (int c = 0; c < cols; c++) {
      final x = c * gridWidth;
      final p1 = Offset(x, 0);
      final p2 = Offset(x, _height);

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

  @override
  void paint(Canvas canvas, Size size) {
    _width = size.width;
    _height = size.height;

    _drawBackground(canvas);
    _drawGrid(canvas);
  }

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

Flutter Canvas 그리기 예제 - (2) Grid 격자 그리기

|

10x10 Grid 격자 그리기

10x10의 격자를 그리는 샘플 코드입니다.

image

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomPaint(
        child: Container(),
        painter: GridPainter(),
      ),
    );
  }
}

class GridPainter extends CustomPainter {
  static const rows = 10;
  static const cols = 10;

  @override
  void paint(Canvas canvas, Size size) {
    var backgroundPaint = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.lime
      ..isAntiAlias = true;

    final screenWidth = size.width;
    final screenHeight = size.height;

    Rect rect = Rect.fromLTWH(0, 0, screenWidth, screenHeight);
    canvas.drawRect(rect, backgroundPaint);

    var linePaint = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.black38
      ..isAntiAlias = true;

    final gridWidth = size.width / cols;
    final gridHeight = size.height / rows;

    for (int row = 0; row < rows; row++) {
      final y = row * gridHeight;
      final p1 = Offset(0, y);
      final p2 = Offset(screenWidth, y);

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

    for (int col = 0; col < cols; col++) {
      final x = col * gridWidth;
      final p1 = Offset(x, 0);
      final p2 = Offset(x, screenHeight);

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

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

고정 간격의 Grid 그리기

화면의 크기가 변경되더라도 Grid 격자의 크기는 고정인 Canvas 예제입니다.

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomPaint(
        child: Container(),
        painter: GridPainter(),
      ),
    );
  }
}

class GridPainter extends CustomPainter {
  static const gridWidth = 50.0;
  static const gridHeight = 50.0;

  var _width = 0.0;
  var _height = 0.0;

  void _drawBackground(Canvas canvas) {
    var paint = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.lime
      ..isAntiAlias = true;

    Rect rect = Rect.fromLTWH(0, 0, _width, _height);
    canvas.drawRect(rect, paint);
  }

  void _drawGrid(Canvas canvas) {
    var paint = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.black38
      ..isAntiAlias = true;

    final rows = _height / gridHeight;
    final cols = _width / gridWidth;

    for (int r = 0; r < rows; r++) {
      final y = r * gridHeight;
      final p1 = Offset(0, y);
      final p2 = Offset(_width, y);

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

    for (int c = 0; c < cols; c++) {
      final x = c * gridWidth;
      final p1 = Offset(x, 0);
      final p2 = Offset(x, _height);

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

  @override
  void paint(Canvas canvas, Size size) {
    _width = size.width;
    _height = size.height;

    _drawBackground(canvas);
    _drawGrid(canvas);
  }

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