TextField

샤워실의 바보·2024년 2월 9일
0
post-thumbnail
post-custom-banner
import 'package:flutter/material.dart';
import 'package:tiktok_clone/constants/gaps.dart';
import 'package:tiktok_clone/constants/sizes.dart';

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

  
  State<UsernameScreen> createState() => _UsernameScreenState();
}

class _UsernameScreenState extends State<UsernameScreen> {
  final TextEditingController _usernameController = TextEditingController();

  String _username = "";

  
  void initState() {
    super.initState();

    _usernameController.addListener(() {
      setState(() {
        _username = _usernameController.text;
      });
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(
          "Sign up",
        ),
      ),
      body: Padding(
        padding: const EdgeInsets.symmetric(
          horizontal: Sizes.size36,
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Gaps.v40,
            const Text(
              "Create username",
              style: TextStyle(
                fontSize: Sizes.size24,
                fontWeight: FontWeight.w700,
              ),
            ),
            Gaps.v8,
            const Text(
              "You can always change this later.",
              style: TextStyle(
                fontSize: Sizes.size16,
                color: Colors.black54,
              ),
            ),
            Gaps.v16,
            TextField(
              controller: _usernameController,
              decoration: InputDecoration(
                hintText: "Username",
                enabledBorder: UnderlineInputBorder(
                  borderSide: BorderSide(
                    color: Colors.grey.shade400,
                  ),
                ),
                focusedBorder: UnderlineInputBorder(
                  borderSide: BorderSide(
                    color: Colors.grey.shade400,
                  ),
                ),
              ),
              cursorColor: Theme.of(context).primaryColor,
            ),
            Gaps.v28,
            FractionallySizedBox(
              widthFactor: 1,
              child: AnimatedContainer(
                padding: const EdgeInsets.symmetric(
                  vertical: Sizes.size16,
                ),
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(Sizes.size5),
                  color: _username.isEmpty
                      ? Colors.grey.shade300
                      : Theme.of(context).primaryColor,
                ),
                duration: const Duration(milliseconds: 500),
                child: const Text(
                  'Next',
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    color: Colors.white,
                    fontWeight: FontWeight.w600,
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

위 코드는 UsernameScreen 위젯을 Flutter에서 정의하고 있습니다. 해당 화면의 주요 내용은 다음과 같습니다:

  1. 초기화:

    • TextField의 텍스트를 관리하기 위해 _usernameController라는 TextEditingController이 정의되어 있습니다.
    • 사용자명을 저장하기 위한 _username이라는 private 변수가 있습니다.
    • initState() 메서드 내에서, _usernameController에 리스너가 추가되어 있습니다. 컨트롤러의 텍스트가 변경될 때마다 _username 변수가 업데이트되고, setState가 호출되어 위젯을 다시 빌드합니다.
  2. Scaffold:

    • 주요 구조는 Scaffold 위젯으로 구성되어 있습니다.
    • "Sign up"이라는 제목이 있는 AppBar가 설정되어 있습니다.
  3. Body:

    • 본문은 자식 위젯 주위에 가로 패딩을 추가하는 Padding 위젯입니다.
    • Column 위젯 내부에서 위젯들이 수직으로 배열됩니다.
    • 여러 갭들이 위젯들 사이의 수직 간격을 제공합니다.
    • 두 개의 Text 위젯으로 사용자는 사용자명을 생성하는 과정을 소개받습니다.
    • TextField는 사용자가 사용자명을 입력할 수 있습니다.
    • AnimatedContainer는 버튼 역할을 합니다. _username이 비어있는 경우 버튼의 색상은 회색입니다. 그렇지 않으면 앱의 주요 색상을 사용합니다. 사용자명이 입력되거나 제거될 때 버튼의 색상 전환은 500 밀리초 동안 애니메이션됩니다.
  4. TextField:

    • TextField_usernameController와 연결된 컨트롤러를 가지고 있습니다. 이 컨트롤러는 변경 사항을 감지하고 _username 변수를 그에 따라 업데이트합니다.
    • TextField의 장식은 "Username"라는 힌트와 사용자 정의 밑줄 테두리를 보여줍니다. 커서 색상은 앱의 주제의 주요 색상으로 설정됩니다.
  5. AnimatedContainer (Next 버튼):

    • FractionallySizedBox는 AnimatedContainer(버튼)이 부모의 전체 너비로 늘어나도록 합니다.
    • 버튼의 색상은 _username이 비어 있는지 여부에 따라 동적으로 변경됩니다. 비어 있으면 버튼은 Colors.grey.shade300 색상이 되고, 그렇지 않으면 앱의 주제의 주요 색상을 사용합니다.
    • 버튼의 텍스트는 "Next"이며 흰색으로 중앙에 표시됩니다.

이 화면은 사용자가 사용자명을 입력할 수 있는 회원가입 과정의 일부로 보입니다. Next 버튼의 색상은 사용자가 진행할 수 있는지(버튼이 주요 색상일 때) 아니면 사용자명이 아직 입력되지 않았는지(버튼이 회색일 때)를 나타냅니다.

initState 메서드는 Flutter의 StatefulWidget 수명 주기 중에서 위젯이 State 객체에 최초로 만들어질 때 한 번만 호출됩니다. 이 메서드는 State 객체가 위젯 트리에 삽입될 때 초기 설정이 필요한 경우 사용합니다.

제공해주신 initState 내용을 살펴보면:

  1. super.initState();: 상위 클래스의 initState를 호출합니다. 이 호출은 항상 initState의 시작 부분에서 이루어져야 합니다.

  2. _usernameController.addListener(() { ... });: _usernameController에 리스너를 추가합니다. TextEditingController의 값이 변경될 때마다 이 리스너는 호출됩니다.

  3. setState(() { ... });: _usernameController의 텍스트가 변경될 때마다 setState를 사용하여 State 객체를 업데이트합니다. setState 내부에서 _username 변수의 값을 _usernameController.text로 업데이트합니다. setState를 호출하면 build 메서드가 다시 호출되어 UI가 업데이트됩니다.

이렇게 하면 사용자가 TextField에 텍스트를 입력할 때마다 _username 변수의 값이 실시간으로 업데이트되고, 필요한 경우 UI도 업데이트됩니다.

import 'package:flutter/material.dart';
import 'package:tiktok_clone/constants/gaps.dart';
import 'package:tiktok_clone/constants/sizes.dart';
import 'package:tiktok_clone/features/authentication/password_screen.dart';
import 'package:tiktok_clone/features/authentication/widgets/form_button.dart';

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

  
  State<EmailScreen> createState() => _EmailScreenState();
}

class _EmailScreenState extends State<EmailScreen> {
  final TextEditingController _emailController = TextEditingController();

  String _email = "";

  
  void initState() {
    super.initState();
    _emailController.addListener(() {
      setState(() {
        _email = _emailController.text;
      });
    });
  }

  
  void dispose() {
    _emailController.dispose();
    super.dispose();
  }

  String? _isEmailValid() {
    if (_email.isEmpty) return null;
    final regExp = RegExp(
        r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+");
    if (!regExp.hasMatch(_email)) {
      return "Email not valid";
    }
    return null;
  }

  void _onScaffoldTap() {
    FocusScope.of(context).unfocus();
  }

  void _onSubmit() {
    if (_email.isEmpty || _isEmailValid() != null) return;
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => const PasswordScreen(),
      ),
    );
  }

  
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _onScaffoldTap,
      child: Scaffold(
        appBar: AppBar(
          title: const Text(
            "Sign up",
          ),
        ),
        body: Padding(
          padding: const EdgeInsets.symmetric(
            horizontal: Sizes.size36,
          ),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Gaps.v40,
              const Text(
                "What is your email?",
                style: TextStyle(
                  fontSize: Sizes.size24,
                  fontWeight: FontWeight.w700,
                ),
              ),
              Gaps.v16,
              TextField(
                controller: _emailController,
                keyboardType: TextInputType.emailAddress,
                onEditingComplete: _onSubmit,
                decoration: InputDecoration(
                  hintText: "Email",
                  errorText: _isEmailValid(),
                  enabledBorder: UnderlineInputBorder(
                    borderSide: BorderSide(
                      color: Colors.grey.shade400,
                    ),
                  ),
                  focusedBorder: UnderlineInputBorder(
                    borderSide: BorderSide(
                      color: Colors.grey.shade400,
                    ),
                  ),
                ),
                cursorColor: Theme.of(context).primaryColor,
              ),
              Gaps.v28,
              GestureDetector(
                onTap: _onSubmit,
                child: FormButton(
                  disabled: _email.isEmpty || _isEmailValid() != null,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

위의 코드는 Flutter에서 사용되는 TextField 위젯입니다. 여기서 사용자는 이메일 주소를 입력할 수 있습니다. 해당 위젯의 각 속성에 대해 간략히 설명드리겠습니다.

  1. controller:

    • TextEditingController의 인스턴스인 _emailController를 사용합니다.
    • 컨트롤러를 통해 사용자의 입력값을 가져오거나 수정할 수 있습니다.
  2. keyboardType:

    • TextInputType.emailAddress를 사용하여 키보드 타입을 이메일 주소 입력용으로 설정합니다. 따라서 키보드에 '@' 기호 등이 우선적으로 표시됩니다.
  3. onEditingComplete:

    • 텍스트 필드에서의 편집이 완료되면 호출되는 콜백입니다. 여기서는 _onSubmit 메서드가 호출됩니다. 즉, 사용자가 키보드에서 '완료'나 '다음' 버튼을 누르면 _onSubmit이 실행됩니다.
  4. decoration:

    • TextField의 디자인 및 모양을 정의하는 InputDecoration을 사용합니다.
    • hintText를 사용하여 텍스트 필드에 힌트 텍스트 "Email"을 표시합니다.
    • errorText_isEmailValid() 함수를 호출하여 이메일 주소의 유효성을 검사하고, 유효하지 않은 경우 에러 메시지를 표시합니다. errorText 속성은 문자열 값을 필요로 합니다. 따라서 errorText: _isEmailValid와 같이 함수를 직접 참조하는 것은 올바르지 않습니다.

대신, errorText: _isEmailValid()와 같이 함수를 호출하여 그 결과값 (이 경우 문자열 또는 null)을 errorText 속성에 제공해야 합니다.

정리하면, 현재 코드에서 errorText: _isEmailValid(),는 올바르게 작성된 것입니다.

  • enabledBorderfocusedBorder를 사용하여 텍스트 필드의 아래쪽 경계선의 디자인을 정의합니다.
  1. cursorColor:
    • 텍스트 필드에서 커서의 색상을 Theme.of(context).primaryColor로 설정합니다.

TextField 위젯은 사용자에게 이메일 주소를 입력하도록 안내하고, 입력된 이메일 주소의 유효성을 실시간으로 검사하여 에러 메시지를 표시하는 역할을 합니다.

autocorrect: false,TextField 위젯의 속성 중 하나입니다. 이 속성은 자동 수정 기능을 활성화 또는 비활성화하는 데 사용됩니다.

예를 들어, 사용자가 이메일 주소나 비밀번호와 같은 특정 형식의 텍스트를 입력해야 할 때 자동 수정 기능이 활성화되어 있으면, 사용자가 원하는 텍스트가 아닌 다른 텍스트로 변경될 수 있습니다. 이러한 경우에 autocorrect: false를 설정하여 자동 수정 기능을 비활성화할 수 있습니다.

따라서, 위에서 제공한 EmailScreen 예제에서 이메일 주소를 입력하는 TextFieldautocorrect: false를 추가하는 것은 좋은 아이디어입니다. 다음과 같이 코드에 추가할 수 있습니다:

TextField(
  controller: _emailController,
  keyboardType: TextInputType.emailAddress,
  onEditingComplete: _onSubmit,
  autocorrect: false, // 자동 수정 기능 비활성화
  decoration: InputDecoration(
    hintText: "Email",
    errorText: _isEmailValid(),
    enabledBorder: UnderlineInputBorder(
      borderSide: BorderSide(
        color: Colors.grey.shade400,
      ),
    ),
    focusedBorder: UnderlineInputBorder(
      borderSide: BorderSide(
        color: Colors.grey.shade400,
      ),
    ),
  ),
  cursorColor: Theme.of(context).primaryColor,
),

이렇게 하면 사용자가 이메일 주소를 입력할 때 자동 수정 기능에 의해 텍스트가 변경되지 않습니다.

TextField(
  cursorColor: Theme.of(context).primaryColor,
  decoration: InputDecoration(
    hintText: "Add comment...",
    border: OutlineInputBorder(
      borderRadius: BorderRadius.circular(
        Sizes.size12,
      ),
      borderSide: BorderSide.none,
    ),
    filled: true,
    fillColor: Colors.grey.shade200,
    contentPadding: const EdgeInsets.symmetric(
      vertical: Sizes.size10,
      horizontal: Sizes.size12,
    ),
  ),
)
  1. cursorColor: 이 속성은 텍스트 필드가 포커스를 가질 때 깜빡이는 커서의 색상을 결정합니다. 여기서는 현재 테마의 주요 색상이 커서 색상으로 사용됩니다.

  2. decoration: InputDecoration은 입력 필드의 모양과 느낌을 정의하는 다양한 방법을 제공합니다. 그 속성들의 세부 정보는 다음과 같습니다:

    • hintText: 사용자가 어떠한 값을 입력하기 전에 입력 필드에 표시되는 힌트입니다. 이 경우, "Add comment..."라는 힌트가 표시됩니다.
    • border: 입력 필드의 테두리를 정의합니다. 여기서는 입력 필드 주위에 둥근 직사각형 테두리를 표시하는 OutlineInputBorder가 사용됩니다.
      • borderRadius: 테두리 모서리의 둥근 반지름을 정의합니다. 여기서는 Sizes.size12 상수(앱의 상수에서 가져온 값)가 사용됩니다.
      • borderSide: 테두리의 측면이 어떻게 보이는지를 지정합니다. BorderSide.none은 테두리 자체가 어떤 선도 표시하지 않을 것임을 의미합니다.
    • filled: 입력 필드에 채우기 색상이 있어야 하는지를 결정하는 불린 속성입니다. true는 채우기 색상이 있음을 의미합니다.
    • fillColor: 입력 필드에 채울 색상입니다. 여기서는 연한 회색(Colors.grey.shade200)이 사용됩니다.
    • contentPadding: 입력 필드 내의 패딩을 정의합니다. 이는 필드의 테두리와 필드 내의 텍스트/내용 사이에 얼마나 많은 공간이 있어야 하는지 결정합니다. 여기서는 앱의 상수를 사용하여 수직 및 수평으로 대칭 패딩이 제공됩니다.

요약하면, 이 TextField 위젯은 사용자가 댓글을 추가할 수 있는 입력 필드를 나타냅니다. 주요 색상의 커서, 사용자를 안내하는 힌트 텍스트, 눈에 보이지 않는 테두리와 둥근 및 채워진 디자인, 그리고 내부의 콘텐츠가 적절하게 간격을 둔 상태로 표시되도록 특정 패딩이 있습니다.

TextField 위젯의 속성들에 대해 설명드리겠습니다:

TextField(
  onTap: _onStartWriting,
  expands: true,
  minLines: null,
  maxLines: null,
  textInputAction: TextInputAction.newline,
)
  1. onTap: onTap 속성은 사용자가 해당 TextField를 탭할 때 호출되는 콜백 함수입니다. 여기서는 _onStartWriting라는 함수가 호출됩니다.

  2. expands: expands 속성이 true로 설정되면 TextField의 크기가 부모 위젯의 사용 가능한 공간을 최대한 확장하려고 합니다. 이 속성을 사용하면 minLinesmaxLines 속성은 무시됩니다.

  3. minLinesmaxLines: 이 두 속성은 TextField의 최소 및 최대 줄 수를 정의합니다. 둘 다 null로 설정되어 있으면, TextField는 부모 위젯의 사용 가능한 공간에 따라 크기가 자동으로 조절됩니다.

  4. textInputAction: 이 속성은 소프트 키보드에서의 특정 동작을 정의합니다. 여기서 TextInputAction.newline가 설정되어 있으므로, 사용자는 엔터(줄바꿈) 키를 통해 새 줄을 시작할 수 있습니다.

TextField 설정은 주로 여러 줄의 텍스트를 입력하도록 설정된 경우에 유용합니다. onTap 콜백을 통해 사용자가 입력을 시작할 때 특정 동작을 실행하도록 설정할 수 있습니다.

profile
공부하는 개발자
post-custom-banner

0개의 댓글