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

Python Blocking Socket 및 Non-Blocking Socket 예제

|

Python Blocking Socket 및 Non-Blocking Socket 예제

Blocking Socket 예제

다음 코드는 httpbin.org/delay/3 서브도메인에 접속해서 3초 딜레이 이후 응답을 받는 코드입니다. s.recv 코드에서 3초동안 Blocking 되서 동작합니다.

import socket

s = socket.create_connection(('httpbin.org', 80))
s.sendall(b'GET /delay/3 HTTP/1.1\r\nHost: httpbin.org\r\n\r\n')

buf = s.recv(1024)
print(buf)

Non-Blocking Socket 예제

다음은 Non-Blocking으로 동작하기 때문에 중간에 while 문으로 응답이 완료될 때까지 대기합니다.

import select
import socket

s = socket.create_connection(('httpbin.org', 80))
s.setblocking(False)

s.sendall(b'GET /delay/3 HTTP/1.1\r\nHost: httpbin.org\r\n\r\n')

while True:
    ready_to_read, ready_to_write, in_error = select.select([s], [], [])
    if s in ready_to_read:
        break

buf = s.recv(1024)
print(buf)

select

여기에서 사용한 select는 여러 개의 이벤트 소스를 결합하여 쉽게 모니터링할 수 있는 기술이며, 오래된 기술입니다. 그래서 최상의 성능을 보여주지는 못하며 리눅스의 epoll이나 FreeBSD의 kqueue와 같이 운영체제마다 다른 대안과 최적화를 구현해서 사용하고 있습니다.

파이썬에서는 다음 포스팅에서 다룰 asyncio라는 추상화 계층을 이용해서 사용하는 것이 더 좋습니다.

Python Socket으로 HTTP Request 보내는 방법

|

Socket으로 HTTP Request 보내는 예제 코드

HTTP 1.1 부터는 Header 정보에 Host 정보를 넣어줘야 하며, 마지막 부분에 2개의 \r\n이 필요합니다.

다음 2가지 예제는 모두 같은 코드입니다.

import socket

s = socket.create_connection(('httpbin.org', 80))
s.sendall(b'GET / HTTP/1.1\r\nHost: httpbin.org\r\n\r\n')

buf = s.recv(1024)
print(buf)
import socket

s = socket.create_connection(('httpbin.org', 80))
s.sendall(b'''GET / HTTP/1.1
Host: httpbin.org

''')

buf = s.recv(1024)
print(buf)