Dice login app

23 Jun 2024  Khlee  5 mins read.

다음 플러터 강좌들을 보고 정리한 내용입니다.

로그인 페이지 구현하기

아래와 같은 화면을 구현할 것이다. 전체 코드

Login complete

Form 위젯

Form(
  child: Theme(
    data: ThemeData(
      primaryColor: Colors.teal,
      inputDecorationTheme: InputDecorationTheme(
        labelStyle: TextStyle(
          color: Colors.teal,
          fontSize: 15.0,
        ),
      ),
    ),
    child: // ...
  ),
),

TextField 위젯으로 정보를 입력 받을 때 일반적으로 Form 위젯을 사용한다. 여기서는 ThemeData를 사용해서 입력 필드가 선택되었을 때의 강조 색상을 설정하고 레이블의 색상과 크기도 설정하였다.

TextField 위젯

TextField(
  decoration: InputDecoration(
    labelText: 'Enter "dice"',
  ),
  keyboardType: TextInputType.emailAddress,
  // obscureText: true,
),

InputDecorationlabelText 속성을 통해 레이블 텍스트를 설정할 수 있고, keyboardType 속성을 통해 키보드 타입(키보드 자판의 구성)을 지정할 수 있다. 비밀번호와 같이 입력된 내용을 안 보이게 하려는 경우 obscureText 속성을 true로 설정하면 된다.

SingleChildScrollView 위젯

키보드가 화면에 올라와서 위젯의 일부가 가려진 경우 디버그 모드에서 아래와 같은 경고 문구가 출력된다.

View overflowed

여기서는 SingleChildScrollView 위젯을 사용해서 스크롤될 수 있게 하는 방법으로 해결하였다. 앱바를 제외한 화면 전체에 적용되어야 하므로 ScaffoldbodySingleChildScrollView을 사용하고 모든 컨텐츠를 그 child로 넣었다.

return Scaffold(
  appBar: // ...
  body: SingleChildScrollView(
    child: Column(
      children: [
        // ...
        Form(
          // ...
        ),
      ],
    ),
  ),
);

TextField 값 가져오기

TextField의 값은 TextEditingController를 통해 가져올 수 있다. 먼저 아래와 같이 TextEditingController 변수를 선언하고

class _LogInState extends State<LogIn> {
  TextEditingController controller = TextEditingController();
  TextEditingController controller2 = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // ...
    );
  }
}

TextFieldcontroller 속성에 해당 변수를 대입하면 된다.

TextField(
  // ...
  controller: controller,
),
TextField(
  // ...
  controller: controller2,
),

값을 가져올 때에는 다음과 같이 하면 된다.

bool isDice = controller.text == 'dice';
bool isPw = controller2.text == '1234';

TextField 외부를 터치했을 때 키보드 닫기

키보드를 닫으려면 현재 선택되어 있는 TextField의 focus를 해제하면 된다. 우선 터치 이벤트를 받기 위해 SingleChildScrollViewGestureDetector로 감싼다. 그 후 onTap 속성에 다음과 같이 focus를 해제하는 함수를 정의한다.

body: GestureDetector(
  onTap: () {
    FocusScope.of(context).unfocus();
  },
  child: SingleChildScrollView(
    // ...
  ),
),

참고로 페이지가 켜졌을 때 특정 TextField를 focus된 상태로 두고 싶다면 autofocus 속성을 true로 지정하면 된다.

TextField(
  // ...
  autofocus: true,
),

주사위 페이지 구현하기

아래와 같은 화면을 구현할 것이다. 전체 코드

Dice complete

화면에 균등하게 이미지 배치하기

Expanded 위젯을 사용하면 해당 위젯이 차지할 수 있는 최대한의 공간으로 위젯의 크기를 확장한다. 만약 아래와 같이 Row 위젯 안에 Expanded 위젯이 여러 개가 있는 경우 기본적으로 동일한 크기로 확장한다.

Row(
  children: [
    Expanded(
      child: Image.asset('image/dice$leftDice.png'),
    ),
    SizedBox(
      width: 20.0,
    ),
    Expanded(
      child: Image.asset('image/dice$rightDice.png'),
    ),
  ],
),

flex 속성을 사용해서 여러 위젯 간의 크기 비율을 지정할 수 있다.

Row(
  children: [
    Expanded(
      flex: 2,
      child: Image.asset('image/dice$leftDice.png'),
    ),
    SizedBox(
      width: 20.0,
    ),
    Expanded(
      flex: 1,
      child: Image.asset('image/dice$rightDice.png'),
    ),
  ],
),

랜덤으로 이미지 바꾸기

랜덤 기능을 사용하기 위해 dart:math import 한다.

import 'dart:math';

버튼의 onPressed를 다음과 같이 구현한다.

onPressed: () {
  setState(() {
    leftDice = Random().nextInt(6) + 1;
    rightDice = Random().nextInt(6) + 1;
  });

  showToast('Left dice: {$leftDice}, Right dice: {$rightDice}');
},

참고: showToast

void showToast(String message) {
  Fluttertoast.showToast(
    msg: message,
    backgroundColor: Colors.white,
    textColor: Colors.black,
    toastLength: Toast.LENGTH_SHORT,
    gravity: ToastGravity.BOTTOM,
  );
}

khlee
khlee