관리 메뉴

Bull

[Flutter] keyboard 입력받기 Web, Window, ... (Level 1 : 구현) 본문

Software Framework/Flutter

[Flutter] keyboard 입력받기 Web, Window, ... (Level 1 : 구현)

Bull_ 2024. 8. 6. 16:21

서론

제가 테트리스를 즐겨해서 시간을 내가지고 Flutter의 Flame을 연습할겸, 테트리스를 직접 구현하면 어떨까~~ㅎㅎ


하는 마음에 Flame으로 간단하게 틀만 만들고 있었는데요. 가장 기본이 되는 키 입력 받는 방법을 몰라서 사경을 헤맸습니다. web폴더의 index.html 파일에 리스너를 추가해야된다는 GPT의 거짓말에 속아서 좀 걸린 것도 있구요. 다행히도 플러터에는 내장 패키지인 service library에서 키보드 관련 이벤트를 구현할 수 있었습니다!

 

Level 1 : 구현 단계 이므로 철저한 조사와 자세한 설명은 빼고 코드 방식만 대략적으로 설명하겠습니다.

결과

Keyboard Event

우선 키보드의 동작을 입력받으려면 우리가 보고 있는 프로그램의 포커싱 여부가 필요합니다.

final FocusNode _focusNode = FocusNode();

FocusNode 클래스는 이후에 사용될 위젯에서 포커싱을 확인하는 용도로 프로퍼티에 적용해야 하게 때문에 따로 인스턴스를 만들어야합니다.

String? _message;

어떤 키보드를 누르고 있는 지 확인하기 위해서 사용합니다.

KeyEventResult _handleKeyEvent(FocusNode node, KeyEvent event) {
  setState(() {
    if (event is KeyDownEvent) {
      _message = 'Key pressed: ${event.logicalKey.debugName}';
    } else if (event is KeyUpEvent) {
      _message = 'Key released: ${event.logicalKey.debugName}';
    }
  });
  return KeyEventResult.handled;
}

키를 눌렀을 때, 뗐을 때 이벤트를 _message 변수에 저장합니다. 이후에 나올 프로퍼티에 등록하기 위해서 반환값은 KeyEventResult 값입니다. handled 값은 키 이벤트가 처리되었으며 키 이벤트가 다른 키 이벤트 핸들러로 전파되지 않는다는 것을 말해줍니다.

Focus(
  autofocus: true,
  focusNode: _focusNode,
  onKeyEvent: _handleKeyEvent,
  child: GestureDetector(
    onTap: () {
      FocusScope.of(context).requestFocus(_focusNode);
    },
    child: Center(
      child: Text(
        _message ?? 'Press a key',
        style: Theme.of(context).textTheme.headlineMedium,
      ),
    ),
  ),
)

Focus 위젯은 이 위젯과 자식 위젯에 키보드 포커스를 부여할 수 있도록 포커스 노드를 관리하는 위젯입니다.
autofocus 는 현재 범위에서 다른 위젯에 포커스 되어있지 않을 때 초기 포커스로 합니다.
focusNode 초반에 정의했던 FocusNode를 등록하면 됩니다.
onKeyEvent 원하는 동작을 구현한 메소드를 등록합니다. 반환값은 KeyEventResult enum 객체의 값이어야 합니다.

전체 CODE

참고로 예제는 LogicalKeyboardKey Class 공식 문서를 살짝 변형하였으니 공식 문서도 확인해보면 좋을 것 같습니다!

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(const KeyExampleApp());
}

class KeyExampleApp extends StatelessWidget {
  const KeyExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Key Handling Example')),
        body: const MyKeyExample(),
      ),
    );
  }
}

class MyKeyExample extends StatefulWidget {
  const MyKeyExample({super.key});

  @override
  State<MyKeyExample> createState() => _MyKeyExampleState();
}

class _MyKeyExampleState extends State<MyKeyExample> {
  final FocusNode _focusNode = FocusNode();
  String? _message;

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

  KeyEventResult _handleKeyEvent(FocusNode node, KeyEvent event) {
    setState(() {
      if (event is KeyDownEvent) {
        _message = 'Key pressed: ${event.logicalKey.debugName}';
      } else if (event is KeyUpEvent) {
        _message = 'Key released: ${event.logicalKey.debugName}';
      }
    });
    return KeyEventResult.handled;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Focus(
        autofocus: true,
        focusNode: _focusNode,
        onKeyEvent: _handleKeyEvent,
        child: GestureDetector(
          onTap: () {
            FocusScope.of(context).requestFocus(_focusNode);
          },
          child: Center(
            child: Text(
              _message ?? 'Press a key',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ),
        ),
      ),
    );
  }
}

참고 자료