Flutter/FlutterGallery-DIY

머테리얼 - 인풋 폼

꽃피는봄날 2021. 7. 19. 16:17

 

import 'package:flutter/gestures.dart' show DragStartBehavior;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';


class PersonData {
  String name = '';
  String phoneNumber = '';
  String email = '';
  String password = '';
}


class TextFieldDemo extends StatelessWidget {
  const TextFieldDemo({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        automaticallyImplyLeading: false,
        title: Text('테스트 인풋 폼들'),
      ),
      body: const TextFormFieldDemo(),
    );
  }
}

class TextFormFieldDemo extends StatefulWidget {
  const TextFormFieldDemo({Key key}) : super(key: key);

  @override
  TextFormFieldDemoState createState() => TextFormFieldDemoState();
}


class TextFormFieldDemoState extends State<TextFormFieldDemo> with RestorationMixin {
  PersonData person = PersonData();

  FocusNode _phoneNumber, _email, _lifeStory, _password, _retypePassword;

  @override
  void initState() {
    super.initState();
    _phoneNumber = FocusNode();
    _email = FocusNode();
    _lifeStory = FocusNode();
    _password = FocusNode();
    _retypePassword = FocusNode();
  }

  @override
  void dispose() {
    _phoneNumber.dispose();
    _email.dispose();
    _lifeStory.dispose();
    _password.dispose();
    _retypePassword.dispose();
    super.dispose();
  }

  // void showInSnackBar(String value) {
  //   ScaffoldMessenger.of(context).hideCurrentSnackBar();
  //   ScaffoldMessenger.of(context).showSnackBar(SnackBar(
  //     content: Text(value),
  //   ));
  // }

  @override
  String get restorationId => 'text_field_demo';

  @override
  void restoreState(RestorationBucket oldBucket, bool initialRestore) {
    registerForRestoration(_autoValidateModeIndex, 'autovalidate_mode');
  }

  final RestorableInt _autoValidateModeIndex =
  RestorableInt(AutovalidateMode.disabled.index);

  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  final GlobalKey<FormFieldState<String>> _passwordFieldKey =
  GlobalKey<FormFieldState<String>>();
  final _UsNumberTextInputFormatter _phoneNumberFormatter =
  _UsNumberTextInputFormatter();

  void _handleSubmitted() {
    final form = _formKey.currentState;
    if (!form.validate()) {
      _autoValidateModeIndex.value =
          AutovalidateMode.always.index; // Start validating on every change.
      // showInSnackBar(
      //   GalleryLocalizations.of(context).demoTextFieldFormErrors,
      // );
    } else {
      form.save();
      // showInSnackBar(GalleryLocalizations.of(context)
      //     .demoTextFieldNameHasPhoneNumber(person.name, person.phoneNumber));
    }
  }

  String _validateName(String value) {
    if (value.isEmpty) {
      return '이름 입력하시오';
    }
    final nameExp = RegExp(r'^[A-Za-z ]+$');
    if (!nameExp.hasMatch(value)) {
      return '알파벳만 입력가능';
    }
    return null;
  }

  String _validatePhoneNumber(String value) {
    final phoneExp = RegExp(r'^\(\d\d\d\) \d\d\d\-\d\d\d\d$');
    if (!phoneExp.hasMatch(value)) {
      return '폰넘버 똑바로 입력해';
    }
    return null;
  }

  String _validatePassword(String value) {
    final passwordField = _passwordFieldKey.currentState;
    if (passwordField.value == null || passwordField.value.isEmpty) {
      return '패스워드 입력해';
    }
    if (passwordField.value != value) {
      return '패스워드가 다르네';
    }
    return null;
  }

  @override
  Widget build(BuildContext context) {
    const sizedBoxSpace = SizedBox(height: 24);

    return Form(
      key: _formKey,
      autovalidateMode: AutovalidateMode.values[_autoValidateModeIndex.value],
      child: Scrollbar(
        child: SingleChildScrollView(
          restorationId: 'text_field_demo_scroll_view',
          padding: const EdgeInsets.symmetric(horizontal: 16),
          child: Column(
            children: [
              sizedBoxSpace,
              TextFormField(
                textInputAction: TextInputAction.next,
                textCapitalization: TextCapitalization.words,
                decoration: InputDecoration(
                  filled: true,
                  icon: const Icon(Icons.person),
                  hintText:'한글이름적으시오',
                  labelText:
                  '이름',
                ),
                onSaved: (value) {
                  person.name = value;
                  _phoneNumber.requestFocus();
                },
                validator: _validateName,
              ),
              sizedBoxSpace,
              TextFormField(
                textInputAction: TextInputAction.next,
                focusNode: _phoneNumber,
                decoration: InputDecoration(
                  filled: true,
                  icon: const Icon(Icons.phone),
                  hintText: '숫자만 입력가능',
                  labelText:
                  '전화번호',
                  prefixText: '+1 ',
                ),
                keyboardType: TextInputType.phone,
                onSaved: (value) {
                  person.phoneNumber = value;
                  _email.requestFocus();
                },
                maxLength: 14,
                validator: _validatePhoneNumber,
                // TextInputFormatters are applied in sequence.
                inputFormatters: <TextInputFormatter>[
                  FilteringTextInputFormatter.digitsOnly,
                  // Fit the validating format.
                  _phoneNumberFormatter,
                ],
              ),
              sizedBoxSpace,
              TextFormField(
                textInputAction: TextInputAction.next,
                focusNode: _email,
                decoration: InputDecoration(
                  filled: true,
                  icon: const Icon(Icons.email),
                  hintText: '힌트3',
                  labelText:
                  '이메일',
                ),
                keyboardType: TextInputType.emailAddress,
                onSaved: (value) {
                  person.email = value;
                  _lifeStory.requestFocus();
                },
              ),
              sizedBoxSpace,
              TextFormField(
                focusNode: _lifeStory,
                decoration: InputDecoration(
                  border: const OutlineInputBorder(),
                  hintText: '파이팅',
                  helperText:
                  '간략하게 쓰시오',
                  labelText:
                  '자기소개',
                ),
                maxLines: 3,
              ),
              sizedBoxSpace,
              TextFormField(
                textInputAction: TextInputAction.next,
                keyboardType: TextInputType.number,
                decoration: InputDecoration(
                  border: const OutlineInputBorder(),
                  labelText:
                  '급여',
                  suffixText: '원',
                ),
                maxLines: 1,
              ),
              sizedBoxSpace,
              PasswordField(
                restorationId: 'password_field',
                textInputAction: TextInputAction.next,
                focusNode: _password,
                fieldKey: _passwordFieldKey,
                helperText:
                '4자리이상',
                labelText:
                '비밀번호',
                onFieldSubmitted: (value) {
                  setState(() {
                    person.password = value;
                    _retypePassword.requestFocus();
                  });
                },
              ),
              sizedBoxSpace,
              TextFormField(
                focusNode: _retypePassword,
                decoration: InputDecoration(
                  filled: true,
                  labelText: '비밀번호 확인',
                ),
                maxLength: 8,
                obscureText: true,
                validator: _validatePassword,
                onFieldSubmitted: (value) {
                  _handleSubmitted();
                },
              ),
              sizedBoxSpace,
              Center(
                child: ElevatedButton(
                  onPressed: _handleSubmitted,
                  child: Text(
                      '전송'),
                ),
              ),
              sizedBoxSpace,
              Text(
                '빠짐 없이 잘 쓰시오',
                style: Theme.of(context).textTheme.caption,
              ),
              sizedBoxSpace,
            ],
          ),
        ),
      ),
    );
  }
}


class PasswordField extends StatefulWidget {
  const PasswordField({
    Key key,
    this.restorationId,
    this.fieldKey,
    this.hintText,
    this.labelText,
    this.helperText,
    this.onSaved,
    this.validator,
    this.onFieldSubmitted,
    this.focusNode,
    this.textInputAction,
  }) : super(key: key);

  final String restorationId;
  final Key fieldKey;
  final String hintText;
  final String labelText;
  final String helperText;
  final FormFieldSetter<String> onSaved;
  final FormFieldValidator<String> validator;
  final ValueChanged<String> onFieldSubmitted;
  final FocusNode focusNode;
  final TextInputAction textInputAction;

  @override
  _PasswordFieldState createState() => _PasswordFieldState();
}

class _PasswordFieldState extends State<PasswordField> with RestorationMixin {
  final RestorableBool _obscureText = RestorableBool(true);

  @override
  String get restorationId => widget.restorationId;

  @override
  void restoreState(RestorationBucket oldBucket, bool initialRestore) {
    registerForRestoration(_obscureText, 'obscure_text');
  }

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      key: widget.fieldKey,
      // restorationId: 'password_text_field',
      obscureText: _obscureText.value,
      maxLength: 8,
      onSaved: widget.onSaved,
      validator: widget.validator,
      onFieldSubmitted: widget.onFieldSubmitted,
      decoration: InputDecoration(
        filled: true,
        hintText: widget.hintText,
        labelText: widget.labelText,
        helperText: widget.helperText,
        suffixIcon: GestureDetector(
          dragStartBehavior: DragStartBehavior.down,
          onTap: () {
            setState(() {
              _obscureText.value = !_obscureText.value;
            });
          },
          child: Icon(
            _obscureText.value ? Icons.visibility : Icons.visibility_off,
            semanticLabel: _obscureText.value
                ? 'hahaha'
                : 'kkkkk',
          ),
        ),
      ),
    );
  }
}

/// Format incoming numeric text to fit the format of (###) ###-#### ##
class _UsNumberTextInputFormatter extends TextInputFormatter {
  @override
  TextEditingValue formatEditUpdate(
      TextEditingValue oldValue,
      TextEditingValue newValue,
      ) {
    final newTextLength = newValue.text.length;
    final newText = StringBuffer();
    var selectionIndex = newValue.selection.end;
    var usedSubstringIndex = 0;
    if (newTextLength >= 1) {
      newText.write('(');
      if (newValue.selection.end >= 1) selectionIndex++;
    }
    if (newTextLength >= 4) {
      newText.write(newValue.text.substring(0, usedSubstringIndex = 3) + ') ');
      if (newValue.selection.end >= 3) selectionIndex += 2;
    }
    if (newTextLength >= 7) {
      newText.write(newValue.text.substring(3, usedSubstringIndex = 6) + '-');
      if (newValue.selection.end >= 6) selectionIndex++;
    }
    if (newTextLength >= 11) {
      newText.write(newValue.text.substring(6, usedSubstringIndex = 10) + ' ');
      if (newValue.selection.end >= 10) selectionIndex++;
    }
    // Dump the rest.
    if (newTextLength >= usedSubstringIndex) {
      newText.write(newValue.text.substring(usedSubstringIndex));
    }
    return TextEditingValue(
      text: newText.toString(),
      selection: TextSelection.collapsed(offset: selectionIndex),
    );
  }
}

 

 


약간 수정 버전

더보기
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:fortigold/src/controllers/RegistryController.dart';
import 'package:get/get.dart';
import 'package:line_icons/line_icons.dart';


class RegistryPage extends StatelessWidget {
  const RegistryPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
          title: Text("회원가입 테스트"),
          backgroundColor: Colors.red.withOpacity(0.8),
        ),

        body: CustomTextFormTest(),
      ),
    );
  }
}

class CustomTextFormTest extends GetView<RegistryController> {
  CustomTextFormTest({Key key}) : super(key: key);

  final FocusNode _phoneNumber =FocusNode();
  final FocusNode _name =FocusNode();
  final FocusNode _email =FocusNode();
  final FocusNode _lifeStory =FocusNode();
  final FocusNode _password =FocusNode();
  final FocusNode _retypePassword =FocusNode();

  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  final GlobalKey<FormFieldState<String>> _passwordFieldKey = GlobalKey<FormFieldState<String>>();

  // 비빌번호 입력 보여지기 or 암호화 하기 위한 변수
  final RxBool _obscureText = true.obs;


  // ####### 폼 전송하기 #########
  Future _handleSubmitted() async{

    final form = _formKey.currentState;

    if (!form.validate()) {
      print('일로오면 안된다고111');
      form.save();
      // await controller.resistry();
      // showInSnackBar(
      //   GalleryLocalizations.of(context).demoTextFieldFormErrors,
      // );
    } else {
      form.save();
      print("유효성 검사 성공");
      await controller.resistry();
      // showInSnackBar(GalleryLocalizations.of(context)
      //     .demoTextFieldNameHasPhoneNumber(person.name, person.phoneNumber));
    }
  }


  // ############ 유효성 검사 모음 #############
  String _validateId(String value){
    if(value.isEmpty){
      return "ID를 입력해 주세요";
    }
    final nameExp = RegExp(r'^[A-Za-z ]+$');
    if (!nameExp.hasMatch(value)){
      return "알파벳만 입력 가능합니다.";
    }
    return null;
  }
  String _validateName(String value){
    if (value.isEmpty) {
      return '이름을 입력해 주세요';
    }
    final nameExp = RegExp(r'^[가-힣]+$');
    if (!nameExp.hasMatch(value)) {
      return '한글만 입력가능 합니다.';
    }
    return null;
  }

  String _validatePhoneNumber(String value){
    final phoneExp = RegExp(r'^\d\d\d\d\d\d\d\d\d\d$');
    final phoneExp2 = RegExp(r'^\d\d\d\d\d\d\d\d\d\d\d$');
    if (!phoneExp.hasMatch(value) && !phoneExp2.hasMatch(value)) {
      return '- 를 제외한 핸드폰 번호를 입력해 주세요';
    }

    return null;
  }


  String _validateEmail(String value){
    if (value.isEmpty || value.length == 0 || value ==""){
      return "이메일을 입력해주세요!";
    }else if(!value.contains("@") || !value.contains(".")){
      return "이메일을 올바르게 입력해주세요!";
    }
    return null;
  }

  String _validatePasswordSize(String value){
    if(value.length < 4){
      return "4자리 이상 입력해주세요!";
    }else{
      return null;
    }
  }

  String _validatePassword(String value){
    //                       ↱ 첫번째 비밀번호 입력창의 값을 갖고옴
    final passwordField = _passwordFieldKey.currentState;
    if(passwordField.value == null || passwordField.value.isEmpty){
      return "패스워드를 입력해 주세요!";
    }if (passwordField.value != value){
      return "패스워드가 다릅니다!";
    }
    return null;
  }

  // ##########################################

  @override
  Widget build(BuildContext context) {
    const sizedBoxSpace = SizedBox(height: 24);

    return Form(
        key: _formKey,
        child: Scrollbar(
          child: SingleChildScrollView(
            padding: const EdgeInsets.symmetric(horizontal: 16),
            child: Column(
              children: [
                Container(
                  padding: EdgeInsets.symmetric(horizontal: 10, vertical: 15),
                  height: Get.mediaQuery.size.height / 3,
                  width: Get.mediaQuery.size.height / 3,
                  child: Image.asset("assets/icon/logo.png", fit: BoxFit.fitHeight,),
                ),
                sizedBoxSpace,

                TextFormField(
                  autovalidateMode: AutovalidateMode.always,
                  textInputAction: TextInputAction.next,
                  textCapitalization: TextCapitalization.words,
                  decoration: InputDecoration(
                    filled: true,
                    icon: const Icon(LineIcons.userPlus),
                    hintText: "알파벳만 입력 가능합니다.",
                    labelText: "ID",
                  ),
                  onSaved: (value){
                    controller.user.mId = value;
                    _phoneNumber.requestFocus();
                  },
                  validator: _validateId,
                ),
                sizedBoxSpace,

                TextFormField(
                  autovalidateMode: AutovalidateMode.always,
                  textInputAction: TextInputAction.next,
                  textCapitalization: TextCapitalization.words,
                  focusNode: _name,
                  decoration: InputDecoration(
                    filled: true,
                    icon: const Icon(Icons.person),
                    hintText:'한글만 입력 가능합니다.',
                    labelText:
                    '이름',
                  ),
                  onSaved: (value) {
                    // person.name = value;
                    controller.user.mName = value;
                    _phoneNumber.requestFocus();
                  },
                  validator: _validateName,
                ),
                sizedBoxSpace,

                TextFormField(
                  autovalidateMode: AutovalidateMode.always,
                  textInputAction: TextInputAction.next,
                  focusNode: _phoneNumber,
                  decoration: InputDecoration(
                    filled: true,
                    icon: const Icon(LineIcons.phone),
                    hintText: '- 제외한 숫자만 입력가능 합니다.',
                    labelText:
                    '휴대폰 번호',
                  ),
                  keyboardType: TextInputType.phone,
                  onSaved: (value){
                    controller.user.cellPhone = value;
                    _email.requestFocus();
                  },
                  maxLength: 11,
                  validator: _validatePhoneNumber,
                ),
                sizedBoxSpace,

                TextFormField(
                  textInputAction: TextInputAction.next,
                  focusNode: _email,
                  decoration: InputDecoration(
                    filled: true,
                    icon: const Icon(Icons.email),
                    hintText: '이메일을 정확하게 입력해 주세요.',
                    labelText:
                    '이메일',
                  ),
                  keyboardType: TextInputType.emailAddress,
                  onSaved: (value) {
                    // person.email = value;
                    controller.user.email = value;
                    _password.requestFocus();
                  },
                  autovalidateMode: AutovalidateMode.always,
                  validator: _validateEmail,
                ),

                sizedBoxSpace,
                PasswordField(
                  textInputAction: TextInputAction.next,
                  focusNode: _password,
                  fieldKey: _passwordFieldKey,
                  hintText:'4자리이상 입력해 주세요',
                  labelText:'비밀번호',
                  onSaved: (value){
                    controller.user.mPw = value;
                    _retypePassword.requestFocus();
                  },
                  validator: _validatePasswordSize,
                ),

                sizedBoxSpace,
                TextFormField(
                  focusNode: _retypePassword,
                  decoration: InputDecoration(
                    helperText: "입력한 패스워드가 같습니다",
                    filled: true,
                    labelText: '비밀번호 확인',
                  ),
                  obscureText: true,
                  autovalidateMode: AutovalidateMode.always,
                  validator: _validatePassword,
                ),


                sizedBoxSpace,
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    ElevatedButton(
                        style: ButtonStyle(backgroundColor: MaterialStateProperty.all<Color>(Colors.red.withOpacity(0.5))),
                        onPressed: () async{
                          FocusScope.of(context).unfocus();
                          Get.back();
                        },
                        child: Text('취소')
                    ),
                    SizedBox(width: 15,),
                    ElevatedButton(
                        style: ButtonStyle(backgroundColor: MaterialStateProperty.all<Color>(Colors.brown.withOpacity(0.6))),
                        onPressed: () async{
                          _handleSubmitted();

                          FocusScope.of(context).unfocus();

                        },
                        child: Text('회원가입')
                    ),
                  ],
                ),
                sizedBoxSpace,
              ],
            ),
          ),
        )
    );
  }
}




class PasswordField extends StatefulWidget {
  const PasswordField({
    Key key,
    this.restorationId,
    this.fieldKey,
    this.hintText,
    this.labelText,
    this.helperText,
    this.onSaved,
    this.validator,
    this.onFieldSubmitted,
    this.focusNode,
    this.textInputAction,
  }) : super(key: key);

  final String restorationId;
  final Key fieldKey;
  final String hintText;
  final String labelText;
  final String helperText;
  final FormFieldSetter<String> onSaved;
  final FormFieldValidator<String> validator;
  final ValueChanged<String> onFieldSubmitted;
  final FocusNode focusNode;
  final TextInputAction textInputAction;

  @override
  _PasswordFieldState createState() => _PasswordFieldState();
}

class _PasswordFieldState extends State<PasswordField> with RestorationMixin {
  final RestorableBool _obscureText = RestorableBool(true);

  @override
  String get restorationId => widget.restorationId;

  @override
  void restoreState(RestorationBucket oldBucket, bool initialRestore) {
    registerForRestoration(_obscureText, 'obscure_text');
  }

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      key: widget.fieldKey,
      obscureText: _obscureText.value,
      onSaved: widget.onSaved,
      autovalidateMode: AutovalidateMode.always,
      validator: widget.validator,
      onFieldSubmitted: widget.onFieldSubmitted,
      decoration: InputDecoration(
        filled: true,
        hintText: widget.hintText,
        labelText: widget.labelText,
        helperText: widget.helperText,
        suffixIcon: GestureDetector(
          dragStartBehavior: DragStartBehavior.down,
          onTap: () {
            setState(() {
              _obscureText.value = !_obscureText.value;
            });
          },
          child: Icon(
            _obscureText.value ? Icons.visibility : Icons.visibility_off,

          ),
        ),
      ),

    );
  }
}