[Flutter] Textfield 위젯

2023-03-18 hit count image

이번 블로그 포스트에서는 Flutter에서 사용자 입력을 받기 위한 Textfield 위젯을 사용하는 방법에 대해서 알아봅니다.

개요

Flutter를 사용해서 앱을 개발해 보려고 합니다. 이번 블로그 포스트에서는 Flutter에서 사용자의 입력을 받는 방법에 대해서 알아봅니다.

이 블로그 포스트에서 소개하는 소스 코드는 아래에 링크에서 확인할 수 있습니다.

Flutter 프로젝트 생성

Flutter에서 사용자의 입력을 받기 위해서는 Textfield 위젯을 사용합니다. 그럼 TextField 위젯을 사용해 보기 위해 먼저 Flutter 프로젝트를 생성합니다.

flutter create my_app
cd my_app

Textfield

프로젝트를 생성하였다면, main.dart 파일을 다음과 같이 수정하여 Textfield를 표시합니다.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      debugShowCheckedModeBanner: false,
      home: Home(),
    );
  }
}

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('TextField'),
      ),
      body: Center(
        child: Padding(
          child: TextField(
            decoration: InputDecoration(
              labelText: 'Input',
            ),
          ),
          padding: EdgeInsets.all(20.0),
        ),
      ),
    );
  }
}

위와 같이 코드를 작성하면 다음과 같은 화면을 확인할 수 있습니다.

Flutter - textfield

Textfield를 표시하는 부분만 자세히 살펴보도록 하겠습니다.

TextField(
  decoration: InputDecoration(
    labelText: 'Input',
  ),
)

위와 같이 Textfield를 표시할 수 있으며, decration 파라메터에 InputDecoration을 사용하여 여러가지 설정을 할 수 있습니다.

InputDecoration

InputDecoration을 사용하면 Textfield 위젯을 좀 더 다양하게 사용할 수 있습니다.

TextField(
  decoration: InputDecoration(
    labelText: 'Email',
    hintText: 'Enter your email',
    labelStyle: TextStyle(color: Colors.redAccent),
    focusedBorder: OutlineInputBorder(
      borderRadius: BorderRadius.all(Radius.circular(10.0)),
      borderSide: BorderSide(width: 1, color: Colors.redAccent),
    ),
    enabledBorder: OutlineInputBorder(
      borderRadius: BorderRadius.all(Radius.circular(10.0)),
      borderSide: BorderSide(width: 1, color: Colors.redAccent),
    ),
    border: OutlineInputBorder(
      borderRadius: BorderRadius.all(Radius.circular(10.0)),
    ),
  ),
  keyboardType: TextInputType.emailAddress,
)

이렇게 InputDecoration을 사용하면 아래와 같이 다양한 디자인을 할 수 있습니다.

Flutter - Textfield InputDecoration

SingleChildScrollView

Textfield를 사용만 사용하면 키보드가 활성화되었을 때, 다음과 같이 큰 문제가 없습니다.

Flutter - Textfield keyboard

하지만 보통 디자인을 위해 Column 위젯과 함께 Textfield을 사용합니다.

Flutter - Textfield column

이때, Column의 영역 위에 키보드가 표시되게 되면 다음과 같은 경고를 확인할 수 있다.

Flutter - Textfield column

이 경고를 해결하기 위해 사용할 수 있는 것이 SingleChildScrollView 위젯입니다.

SingleChildScrollView 위젯을 다음과 같이 사용하면 이 문제를 해결할 수 있습니다.

SingleChildScrollView(
  child: Column(
    children: [
      Container(
        width: 300,
        height: 300,
        margin: EdgeInsets.all(40.0),
        color: Colors.lightBlue,
      ),
      Padding(
        padding: EdgeInsets.fromLTRB(20.0, 0.0, 20.0, 10.0),
        child: TextField(
          decoration: InputDecoration(
            labelText: 'Email',
            hintText: 'Enter your email',
            labelStyle: TextStyle(color: Colors.redAccent),
            focusedBorder: OutlineInputBorder(
              borderRadius: BorderRadius.all(Radius.circular(10.0)),
              borderSide: BorderSide(width: 1, color: Colors.redAccent),
            ),
            enabledBorder: OutlineInputBorder(
              borderRadius: BorderRadius.all(Radius.circular(10.0)),
              borderSide: BorderSide(width: 1, color: Colors.redAccent),
            ),
            border: OutlineInputBorder(
              borderRadius: BorderRadius.all(Radius.circular(10.0)),
            ),
          ),
          keyboardType: TextInputType.emailAddress,
        ),
      ),
    ],
  ),
)

SingleChildScrollView를 사용하면, Textfield에 의해 키보드가 활성화되었을 때, 화면이 스크롤 가능한 상태가 되며 앞에서 발생하는 문제를 해결할 수 있습니다.

Flutter - Textfield SingleChildScrollView

GestureDetector와 FocusScope

현재는 키보드가 활성화되면, 키보드의 done 버튼을 눌러야 키보드가 사라진다. 다른 말로 하면, Textfield가 Focus 상태가 되면, 키보드가 활성화가 되고, done 키를 눌러 Textfield가 UnFocus 상태가 되면 키보드가 사라진다.

보통 앱의 UX는 키보드가 활성화되면, 키보드 이외의 영역을 선택하였을 시, 키보드가 사라지게 된다. 이와 같이 키보드 이외의 영역을 선택하였을 때, 키보드를 사라지게 하기 위해서 GestureDetector 위젯과 FocusScope 위젯을 사용해야 한다.

그럼 키보드 이외에 영역을 선택하였을 때, 키보드를 사라지게 하기 위해서, main.dart 파일을 다음과 같이 수정한다.

GestureDetector(
  onTap: () => FocusScope.of(context).unfocus(),
  child: SingleChildScrollView(...),
),

SingleChildScrollView 위젯안은 앞에서 살펴본 코드이므로 생략하였다. 우선 사용자의 이벤트를 감지하기 위해, GestureDetector를 사용하였다. 이때, 사용자가 화면을 터치하였을 때, 키보드로부터 Focus를 제거하기 위해, FocusScope 위젯의 unfocus 함수를 사용하였다.

이렇게 GestureDetectorFocusScope를 사용하면, 키보드를 감추는 기능을 만들 수 있다.

Textfield 값 사용하기

Textfield를 사용하는 이유는 사용자로부터 값을 입력받고, 입력받은 값을 사용하기 위해서이다. 그럼 Textfield 값을 사용하는 방법에 대해서 알아보도록 하자.

onChanged

사용자가 Textfield에 값을 입력하면 Textfield 위젯의 onChanged 함수가 호출된다. 이 함수가 호출될 때, 파라메터로 전달되는 text 값을 setState를 사용하여 저장하면 된다.

class Home extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  String inputText = '';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('TextField'),
      ),
      body: Center(
        child: GestureDetector(
          onTap: () => FocusScope.of(context).unfocus(),
          child: SingleChildScrollView(
            child: Column(
              children: [
                Text('$inputText'),
                Padding(
                  padding: EdgeInsets.fromLTRB(20.0, 0.0, 20.0, 10.0),
                  child: TextField(
                    onChanged: (text) {
                      setState(() {
                        inputText = text;
                      });
                    },
                    decoration: InputDecoration(
                      labelText: 'Email',
                      hintText: 'Enter your email',
                      labelStyle: TextStyle(color: Colors.redAccent),
                      focusedBorder: OutlineInputBorder(
                        borderRadius: BorderRadius.all(Radius.circular(10.0)),
                        borderSide:
                            BorderSide(width: 1, color: Colors.redAccent),
                      ),
                      enabledBorder: OutlineInputBorder(
                        borderRadius: BorderRadius.all(Radius.circular(10.0)),
                        borderSide:
                            BorderSide(width: 1, color: Colors.redAccent),
                      ),
                      border: OutlineInputBorder(
                        borderRadius: BorderRadius.all(Radius.circular(10.0)),
                      ),
                    ),
                    keyboardType: TextInputType.emailAddress,
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

값이 변경되는 부분만 자세히 살펴보자.

...
String inputText = '';
...
Text('$inputText')
...
TextField(
  onChanged: (text) {
    setState(() {
      inputText = text;
    });
  },
  ...,
)
...

변경되는 값을 저장하기 위해, StatefulWidget을 생성하였다. 그리고 사용자의 입력값을 저장할 String 변수를 생성하였다. 이렇게 생성한 String 변수를 Text 위젯을 사용하여 화면에 표시하였다.

그리고 Textfield 위젯의 onChanged 함수를 사용하여 사용자가 입력한 값을 setState를 사용하여 앞에서 선언한 변수를 변경해 주었다.

이제 Textfield에 값을 입력하면 다음과 같이 Textfeild 위에 입력한 내용이 출력되는 것을 확인할 수 있다.

Flutter - Textfield SingleChildScrollView

TextEditingController

위와 같이 실시간으로 데이터를 갱신할 수도 있지만, 특정 이벤트가 발생하였을 때, 현재 입력된 값에 접근하고 싶을 때도 있다. 이때 사용하는 것이 TextEditingController이다.

TextEditingController는 다음과 같이 사용할 수 있다.

class _HomeState extends State<Home> {
  TextEditingController inputController = TextEditingController();
  String inputText = '';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('TextField'),
      ),
      body: Center(
        child: GestureDetector(
          onTap: () => FocusScope.of(context).unfocus(),
          child: SingleChildScrollView(
            child: Column(
              children: [
                Text('$inputText'),
                Padding(
                  padding: EdgeInsets.fromLTRB(20.0, 0.0, 20.0, 10.0),
                  child: TextField(
                    controller: inputController,
                    decoration: InputDecoration(
                      labelText: 'Email',
                      hintText: 'Enter your email',
                      labelStyle: TextStyle(color: Colors.redAccent),
                      focusedBorder: OutlineInputBorder(
                        borderRadius: BorderRadius.all(Radius.circular(10.0)),
                        borderSide:
                            BorderSide(width: 1, color: Colors.redAccent),
                      ),
                      enabledBorder: OutlineInputBorder(
                        borderRadius: BorderRadius.all(Radius.circular(10.0)),
                        borderSide:
                            BorderSide(width: 1, color: Colors.redAccent),
                      ),
                      border: OutlineInputBorder(
                        borderRadius: BorderRadius.all(Radius.circular(10.0)),
                      ),
                    ),
                    keyboardType: TextInputType.emailAddress,
                  ),
                ),
                ElevatedButton(
                  onPressed: () {
                    setState(() {
                      inputText = inputController.text;
                    });
                  },
                  child: Text('Update'),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

그럼 TextEditingController을 사용하여 입력값을 화면에 표시하는 부분만을 살펴보자.

TextEditingController inputController = TextEditingController();
String inputText = '';
...
Text('$inputText'),
...
TextField(
  controller: inputController,
  ...,
),
...
ElevatedButton(
  onPressed: () {
    setState(() {
      inputText = inputController.text;
    });
  },
  child: Text('Update'),
),
...

TextEditingController를 먼저 선언한 후, TextField 위젯의 controller 파라메터에 전달해 준다. 그리고 ElevatedButton 버튼이 눌러졌을 때, setState를 사용하여 변수를 업데이트해 준다. 이때, inputController.text와 같이 Textfield 위젯의 입력값에 접근할 수 있다.

이런 방식은 주로 서버에 데이터를 보낼 때 사용된다.

Scaffold의 resizeToAvoidBottomInset

Textfield 위젯을 사용하여 개발하다보면, 다음과 같이 키보드와 컨텐츠 사이에 공간이 표시되는 경우가 발생합니다.

Flutter - Textfield resizeToAvoidBottomInset error

이때는 ScaffoldresizeToAvoidBottomInset 옵션을 사용하면 문제를 해결할 수 있습니다.

Scaffold(
  resizeToAvoidBottomInset: false,
  appBar: ...,
  body: GestureDetector(
    onTap: () => Get.focusScope!.unfocus(),
    child: SingleChildScrollView(
      child: Column(
        children: [
          ...,
        ],
      ),
    ),
  ),
);

이렇게 ScaffoldresizeToAvoidBottomInset 옵션을 false로 설정하면 다음과 같이 문제가 해결되는 것을 확인할 수 있습니다.

Flutter - Textfield resizeToAvoidBottomInset

완료

이것으로 Textfield 위젯을 사용하여 사용자가 입력한 값에 접근하고 사용하는 방법에 대해서 알아보았다.

제 블로그가 도움이 되셨나요? 하단의 댓글을 달아주시면 저에게 큰 힘이 됩니다!

앱 홍보

책 홍보

블로그를 운영하면서 좋은 기회가 생겨 책을 출판하게 되었습니다.

아래 링크를 통해 제가 쓴 책을 구매하실 수 있습니다.
많은 분들에게 도움이 되면 좋겠네요.

스무디 한 잔 마시며 끝내는 React Native, 비제이퍼블릭
스무디 한 잔 마시며 끝내는 리액트 + TDD, 비제이퍼블릭
[심통]현장에서 바로 써먹는 리액트 with 타입스크립트 : 리액트와 스토리북으로 배우는 컴포넌트 주도 개발, 심통
Posts