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;
}

Flutter Canvas 그리기 예제 - (1) 배경 색상 그리기

|

Canvas 사용하기

Flutter에서 Canvas를 사용하기 위해서는 CustomPainter라는 위젯을 상속받아 구현해야 합니다. 그리고 void paint(...) 함수와 bool shouldRepaint(...) 함수를 오버라이드해서 구현해주면 됩니다.

BackgroundPainter 클래스

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

    Rect rect = Rect.fromLTWH(0, 0, size.width, size.height);
    canvas.drawRect(rect, background);
  }

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

void paint(Canvas canvas, Size size) 메소드의 인자로 넘어오는 size는 현재 CustomPainter의 크기가 들어옵니다.

EmptyPage 클래스

그리고 위에서 구현한 BackgroundPainter를 화면에 그리기 위해서는 다음과 같이 CustomPainterpaint 속성에 위에서 만든 객체를 배치하면 됩니다.

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

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

여기서 주의할 점은 CustomPaint 속성 중 child: Container()를 지정해주어야 BackgroundPainter 클래스의 void paint(Canvas canvas, Size size) 함수 인자인 size 크기가 전달됩니다. 만약 child 속성이 없다면 size는 (0, 0)이 됩니다.

전체 코드

import 'package:flutter/material.dart';

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

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

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

    Rect rect = Rect.fromLTWH(0, 0, size.width, size.height);
    canvas.drawRect(rect, background);
  }

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

main.dart

</pre class=”prettyprint”> import ‘package:flutter/material.dart’;

import ‘canvas/01_background_color.dart’;

void main() { runApp(MyApp()); }

class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: ‘Flutter Demo’, debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: EmptyPage(), ); } } </pre>

실행 화면

image