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,

          ),
        ),
      ),

    );
  }
}

- 변수 선언

 

- body 부분의 이름 보여지는 곳

 

- Form 부분

 

- 버튼

 

 


- 컨트롤러에서 변경 함수

 

 

 

전체소스

ReactiveStateManagePageTest.dart

더보기
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:getx_test/src/controller/CountControllerGetxTest2.dart';
import 'package:getx_test/src/controller/CountControllerReactiveTest.dart';


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

  final _formKey = GlobalKey<FormState>();


  @override
  Widget build(BuildContext context) {

    String userName;

    // 컨트롤러 객체 생성
    CountControllerReactiveTest _controllerReactive = Get.put(CountControllerReactiveTest());

    return Scaffold(
      appBar: AppBar(
        title: Text('반응형 상태 관리'),
      ),

      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('GetX 반응형', style: TextStyle(fontSize: 30),),

            // Obx랑  GetX 둘 중 하나 골라서 사용하면 됨
            Obx(()=>
                Text('${_controllerReactive.count.value}', style: TextStyle(fontSize: 30, color: Colors.blue),),
            ),

            // 이름 보여지는 부분
            Obx(()=>
                Text('이름: ${_controllerReactive.user1.value.name}', style: TextStyle(fontSize: 30, color: Colors.blue),),
            ),

            GetX(
              builder: (_){
                return Text('${_controllerReactive.nums.value}', style: TextStyle(fontSize: 30, color: Colors.red),);
              },
            ),

            // 이름 입력전달 받기
            Form(
              key: _formKey,
              child: TextFormField(
                onSaved: (inText){
                  userName=inText;

                  print('dddd -> $userName');
                },
                validator: (input){
                  if (input == null || input.isEmpty) {
                    return "입력창이 비었습니다.";
                  } else {
                    return null;
                  }
                },
              ),
            ),

            RaisedButton(
                child: Text('유저네임변경', style: TextStyle(fontSize: 30),),
                onPressed: (){
                  //  ↱ Form유효성 검사하기
                  if(_formKey.currentState.validate()){
                    // ↱ Form의 값들 저장하기
                    _formKey.currentState.save();
                    // ↱ 컨트롤러 이용해 이름변경하기
                    _controllerReactive.changeUser(userName);
                  }
                }
            ),
            RaisedButton(
              child: Text('+', style: TextStyle(fontSize: 30),),
              onPressed: (){
                _controllerReactive.increase();
              }
            ),
            RaisedButton(
              child: Text('초기화 하기', style: TextStyle(fontSize: 30),),
              onPressed: (){
                _controllerReactive.resetNum(0);
              }
            ),


          ],
        ),
      ),
    );
  }
}

 


CountControllerReactiveTest.dart 

더보기
import 'package:get/get.dart';

enum NUM {Hahaha, Hohoho}

class User{
  String name;
  int age;

  User({this.name, this.age});
}


class CountControllerReactiveTest extends GetxController {

  RxInt count = 0.obs;

  Rx<NUM> nums = NUM.Hahaha.obs;
  Rx<User> user1 = User(name: '김길동', age: 11).obs;

  void increase(){
    count++;

    // ↱ enum 타입 변경하기
    nums(NUM.Hohoho);

    // ↱ 객체형 변경하기
    user1.update((val) {
      val.name = '아무개';
      val.age = 99;
    });


  }

  void resetNum(int value){
    count(value);
    user1.update((val) {
      val.name ='김길동';
      val.age = 11;
    });

    nums(NUM.Hahaha);

  }

  void changeUser(String inText){
    user1.update((val) {
      val.name=inText;
    });
    print('컨트롤러 부분-> ${user1.value.name}');
  }



  @override
  void onInit() {
    ever(count, (_)=> print('ever는 매번호출'));
    once(count, (_)=>print('once는 한 번만 호출'));

    super.onInit();
  }

}

페이지 이동 에니메이션

 


애니메이션을 세밀하게 조정하기

secondPage2.dart

더보기
import 'package:flutter/material.dart';
import 'dart:math';


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

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

class _SecondPageState extends State<SecondPage2>
  with SingleTickerProviderStateMixin {

  // 애니메이션 컨트롤러, 애니메이션 객체 만들기
  //   ↱ AnimationController -> 프레임마다 새로운 값을 생성하는 애니메이션
  //     (지정된 시간 동안 선형보간법을 이용해 시작점부터 끝점 사이의 숫자를 -> 초당 60개씩 생성)
  late AnimationController _animationController;

  late Animation _rotateAnimation;
  late Animation _scaleAnimation;
  late Animation _transAnimation;

  @override
  void initState(){
    super.initState();
    //                                           ↱ 애니메이션 재생시간          ↱애니메이션 표현할 대상
    _animationController = AnimationController(duration: Duration(seconds: 5), vsync: this);
    //                  ↱ 기본적으로 생성해주는 애니메이션 컨트롤러 숫자의 범위는 0.0 ~ 1.0 임 -> 다른 범위, 데이터 유형이 필요한 경우 사용
    _rotateAnimation = Tween<double>(begin: 0, end: pi * 10).animate(_animationController); // 화전
    _scaleAnimation = Tween<double>(begin: 1, end: 0).animate(_animationController);        // 크기
    _transAnimation = Tween<Offset>(begin: Offset(0,0), end: Offset(200,200)).animate(_animationController);  //위젯의 방향
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('애니메이션 예제2'),
      ),

      body: Container(
        child: Center(
          child: Column(
            children: [
              AnimatedBuilder(
                animation: _rotateAnimation,
                //  ↱ 애니메이션 어떻게 보여줄지 정의하기
                builder: (context, widget){
                  return Transform.translate( // 위젯의 방향 설정
                    offset: _transAnimation.value,

                    child: Transform.rotate( // 위젯의 회전
                      angle: _rotateAnimation.value,

                      child: Transform.scale(  // 위젯의 크기를 조절
                        scale: _scaleAnimation.value,
                        child: widget,
                      ),
                    ),
                  );
                },
                child: Hero(
                    tag: 'detail1',
                    child: Icon(Icons.cake, size: 300,)
                ),
              ),
              // ignore: deprecated_member_use
              RaisedButton(
                onPressed: (){
                  _animationController.forward();
                },
                child: Text('로테이션 시작하기'),
              ),
            ],
            mainAxisAlignment: MainAxisAlignment.center,
          ),
        ),
      ),
    );
  }
}

 


 

나만의 인트로 화면 만들기

 

 

인트로 화면의 애니매이션 만들기 saturnLoading.dart

더보기
import 'package:flutter/material.dart';
import 'dart:math';

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

  @override
  _SaturnLoadingState createState() => _SaturnLoadingState();


  void start(){
    _SaturnLoadingState().start();
  }

  void stop(){
    _SaturnLoadingState().stop();
  }

}


class _SaturnLoadingState extends State<SaturnLoading>
  with SingleTickerProviderStateMixin {

  late AnimationController _animationController;
  late Animation _animation;

  @override
  void initState(){
    super.initState();

    // 3초동안 동작하는 애니메이션
    _animationController = AnimationController(vsync :this, duration: Duration(seconds: 3));
    // 애니메이션 시작점과 끝점 정의하기
    _animation = Tween<double>(begin: 0, end: pi * 2).animate(_animationController);
    _animationController.repeat();
  }

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

  void start(){
    _animationController.repeat();
  }

  void stop(){
    _animationController.stop(canceled: true);
  }


  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animationController,
      builder: (context, child){
        return SizedBox(
          width: 100,
          height: 100,

          child: Stack(
            children: [
              Image.asset(
                'repo/images/circle.png',
                width: 100,
                height: 100,
              ),
              Center(
                child: Image.asset(
                  'repo/images/sunny.png',
                  width: 30,
                  height: 30,
                ),
              ),
              Padding(
                padding: EdgeInsets.all(5),
                child: Transform.rotate(
                  angle: _animation.value,
                  origin: Offset(35, 35),  // 회전의 기준점 지정
                  child: Image.asset(
                    'repo/images/saturn.png',
                    width: 20,
                    height: 20,
                  ),
                ),
              ),
            ],
          ),
        );
      });
  }
}

 

 

애니메이션 보여줄 인트로 화면 (애니메이션 보여주다가 5초 후 메인으로 이동)   animation_intro.dart

더보기
import 'package:flutter/material.dart';
import 'package:psw1/animation/saturnLoading.dart';
import 'dart:async';

import 'package:psw1/main_animation.dart';


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

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

class _AnimationIntroPageState extends State<AnimationIntroPage> {

  @override
  void initState(){
    super.initState();
    loadData();
  }


  // 5초후 메인 화면으로 이동하기
  Future<Timer> loadData() async {
    return Timer(Duration(seconds: 5), onDoneLoading);
  }

  onDoneLoading() async{
    Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context)=> AnimationApp()));
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: Center(
          child: Column(
            children: [
              Text('애니메이션 앱!~'),
              SizedBox(
                height: 20,
              ),
              SaturnLoading()  // 애니메이션 페이지 풀러오기
            ],
            mainAxisAlignment: MainAxisAlignment.center,
          ),
        ),
      ),
    );
  }
}

 

 

 

Curves 공식 문서

 

그래프 애니메이션 구현

더보기
import 'package:flutter/material.dart';
import 'package:psw1/models/people.dart';

void main() => runApp(MyApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'main_animation',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: AnimationApp(),
    );
  }
}

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

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

class _AnimationAppState extends State<AnimationApp> {
  List<People> peoples = [];
  int current = 0;

  @override
  void initState(){
    super.initState();
    peoples.add(People('스미스', 100, 92));
    peoples.add(People('메리', 162, 55));
    peoples.add(People('존', 172, 75));
    peoples.add(People('바트', 130, 40));
    peoples.add(People('콘', 192, 140));
    peoples.add(People('디디', 100, 80));
    peoples.add(People('로니콜먼', 185, 110));
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('애니메이션 활용해보기'),
      ),

      body: Container(
        child: Center(
          child: Column(
            children: [
              SizedBox(
                child: Row(
                  children: [
                    SizedBox(
                      width: 100,
                      child: Text('이름 : ${peoples[current].name}'),
                    ),

                    AnimatedContainer(
                      duration: Duration(seconds: 2),
                      curve: Curves.bounceIn,
                      color: Colors.amber,
                      child: Text(
                        '키 ${peoples[current].height}',
                        textAlign: TextAlign.center,
                      ),
                      width: 50,
                      height: peoples[current].height,
                    ),
                    AnimatedContainer(
                      duration: Duration(seconds: 2),
                      curve: Curves.easeInCubic,
                      color: Colors.blue,
                      child: Text(
                        '몸무게 ${peoples[current].weight}',
                        textAlign: TextAlign.center,
                      ),
                      width: 50,
                      height: peoples[current].height,
                    ),
                    AnimatedContainer(
                      duration: Duration(seconds: 2),
                      curve: Curves.linear,
                      color: Colors.pinkAccent,
                      child: Text(
                        'bmi : ${peoples[current].bmi.toString().substring(0, 2)}',
                        textAlign: TextAlign.center,
                      ),
                      width: 50,
                      height: peoples[current].bmi,
                    ),
                  ],
                  mainAxisAlignment: MainAxisAlignment.spaceAround,
                  crossAxisAlignment: CrossAxisAlignment.end,
                ),
                height: 200,
              ),
              // ignore: deprecated_member_use
              RaisedButton(
                onPressed: (){
                  setState(() {
                    if (current < peoples.length -1){
                      current++;
                    }
                  });
                },
                child: Text('다음'),
              ),
              // ignore: deprecated_member_use
              RaisedButton(
                onPressed: (){
                  setState(() {
                    if(current > 0){
                      current--;
                    }
                  });
                },
                child: Text('이전'),
              ),
            ],
            mainAxisAlignment: MainAxisAlignment.center,
          ),
        ),
      ),
    );
  }
}

 

 

색감 변경 애니메이션

더보기
import 'package:flutter/material.dart';
import 'package:psw1/models/people.dart';

void main() => runApp(MyApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'main_animation',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: AnimationApp(),
    );
  }
}

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

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

class _AnimationAppState extends State<AnimationApp> {
  List<People> peoples = [];
  int current = 0;

  // 색상 변경하기
  Color weightColor = Colors.blue;

  @override
  void initState(){
    super.initState();
    peoples.add(People('스미스', 100, 92));
    peoples.add(People('메리', 162, 55));
    peoples.add(People('존', 182, 75));
    peoples.add(People('바트', 130, 40));
    peoples.add(People('콘', 192, 140));
    peoples.add(People('디디', 160, 80));
    peoples.add(People('로니콜먼', 185, 110));
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('애니메이션 활용해보기'),
      ),

      body: Container(
        child: Center(
          child: Column(
            children: [
              SizedBox(
                child: Row(
                  children: [
                    SizedBox(
                      width: 100,
                      child: Text('이름 : ${peoples[current].name}'),
                    ),

                    AnimatedContainer(
                      duration: Duration(seconds: 2),
                      curve: Curves.bounceIn,
                      color: Colors.amber,
                      child: Text(
                        '키 ${peoples[current].height}',
                        textAlign: TextAlign.center,
                      ),
                      width: 50,
                      height: peoples[current].height,
                    ),
                    AnimatedContainer(
                      duration: Duration(seconds: 1),
                      curve: Curves.easeInCubic,
                      color: weightColor,
                      child: Text(
                        '몸무게 ${peoples[current].weight}',
                        textAlign: TextAlign.center,
                      ),
                      width: 50,
                      height: peoples[current].height,
                    ),
                    AnimatedContainer(
                      duration: Duration(seconds: 2),
                      curve: Curves.linear,
                      color: Colors.pinkAccent,
                      child: Text(
                        'bmi : ${peoples[current].bmi.toString().substring(0, 2)}',
                        textAlign: TextAlign.center,
                      ),
                      width: 50,
                      height: peoples[current].bmi,
                    ),
                  ],
                  mainAxisAlignment: MainAxisAlignment.spaceAround,
                  crossAxisAlignment: CrossAxisAlignment.end,
                ),
                height: 200,
              ),
              // ignore: deprecated_member_use
              RaisedButton(
                onPressed: (){
                  setState(() {
                    if (current < peoples.length -1){
                      current++;
                    }
                    _changeWeightColor(peoples[current].weight);
                  });
                },
                child: Text('다음'),
              ),
              // ignore: deprecated_member_use
              RaisedButton(
                onPressed: (){
                  setState(() {
                    if(current > 0){
                      current--;
                    }
                    _changeWeightColor(peoples[current].weight);
                  });
                },
                child: Text('이전'),
              ),
            ],
            mainAxisAlignment: MainAxisAlignment.center,
          ),
        ),
      ),
    );
  }

  void _changeWeightColor (double weight){
    if (weight < 40){
      weightColor = Colors.blueAccent;
    }else if(weight < 60){
      weightColor = Colors.indigo;
    }else if(weight < 80) {
      weightColor = Colors.orange;
    }else {
      weightColor = Colors.red;
    }
  }
}

 

 

 

불투명도 애니메이션

더보기
import 'package:flutter/material.dart';
import 'package:psw1/models/people.dart';

void main() => runApp(MyApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'main_animation',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: AnimationApp(),
    );
  }
}

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

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

class _AnimationAppState extends State<AnimationApp> {
  List<People> peoples = [];
  int current = 0;

  // 색상 변경하기
  Color weightColor = Colors.blue;

  // 투명도 설정하기
  double _opacity = 1;

  @override
  void initState(){
    super.initState();
    peoples.add(People('스미스', 100, 92));
    peoples.add(People('메리', 162, 55));
    peoples.add(People('존', 182, 75));
    peoples.add(People('바트', 130, 40));
    peoples.add(People('콘', 192, 140));
    peoples.add(People('디디', 160, 80));
    peoples.add(People('로니콜먼', 185, 110));
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('애니메이션 활용해보기'),
      ),

      body: Container(
        child: Center(
          child: Column(
            children: [
              AnimatedOpacity(
                opacity: _opacity,
                duration: Duration(seconds: 1),
                child: SizedBox(
                  child: Row(
                    children: [
                      SizedBox(
                        width: 100,
                        child: Text('이름 : ${peoples[current].name}'),
                      ),

                      AnimatedContainer(
                        duration: Duration(seconds: 2),
                        curve: Curves.bounceIn,
                        color: Colors.amber,
                        child: Text(
                          '키 ${peoples[current].height}',
                          textAlign: TextAlign.center,
                        ),
                        width: 50,
                        height: peoples[current].height,
                      ),
                      AnimatedContainer(
                        duration: Duration(seconds: 1),
                        curve: Curves.easeInCubic,
                        color: weightColor,
                        child: Text(
                          '몸무게 ${peoples[current].weight}',
                          textAlign: TextAlign.center,
                        ),
                        width: 50,
                        height: peoples[current].height,
                      ),
                      AnimatedContainer(
                        duration: Duration(seconds: 2),
                        curve: Curves.linear,
                        color: Colors.pinkAccent,
                        child: Text(
                          'bmi : ${peoples[current].bmi.toString().substring(0, 2)}',
                          textAlign: TextAlign.center,
                        ),
                        width: 50,
                        height: peoples[current].bmi,
                      ),
                    ],
                    mainAxisAlignment: MainAxisAlignment.spaceAround,
                    crossAxisAlignment: CrossAxisAlignment.end,
                  ),
                  height: 200,
                ),
              ),
              // ignore: deprecated_member_use
              RaisedButton(
                onPressed: (){
                  setState(() {
                    if (current < peoples.length -1){
                      current++;
                    }
                    _changeWeightColor(peoples[current].weight);
                  });
                },
                child: Text('다음'),
              ),
              // ignore: deprecated_member_use
              RaisedButton(
                onPressed: (){
                  setState(() {
                    if(current > 0){
                      current--;
                    }
                    _changeWeightColor(peoples[current].weight);
                  });
                },
                child: Text('이전'),
              ),
              // ignore: deprecated_member_use
              RaisedButton(
                onPressed: (){
                  setState(() {
                    _opacity == 1 ? _opacity = 0.2 : _opacity = 1;
                  });
                },
                child: Text('사라지기'),
              ),
            ],
            mainAxisAlignment: MainAxisAlignment.center,
          ),
        ),
      ),
    );
  }

  void _changeWeightColor (double weight){
    if (weight < 40){
      weightColor = Colors.blueAccent;
    }else if(weight < 60){
      weightColor = Colors.indigo;
    }else if(weight < 80) {
      weightColor = Colors.orange;
    }else {
      weightColor = Colors.red;
    }
  }
}

 

 

 


 

import 'package:flutter/material.dart';
import 'dart:io';
import 'package:path_provider/path_provider.dart';

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

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

class _FileAppState extends State<FileApp> {

  int _count = 0;

  @override
  void initState(){
    super.initState();

    // 앱이 실행 될 때 파일에서 가지고온 데이터 표시하기위에 initState에서 실행함
    readCountFile();
  }

  // 문자열 형태로 파일을 만들어 저장하기
  void writeCountFile(int count) async{
    //               ↱ 내부 저장소 경로 가져오기
    var dir = await getApplicationDocumentsDirectory();
    File(dir.path + '/count.txt').writeAsStringSync(count.toString());
  }


  // 파일 읽기
  void readCountFile() async{
    try{
      var dir = await getApplicationDocumentsDirectory();
      var file = await File(dir.path + '/count.txt').readAsString();
      print('파일-> $file');

      setState(() {
        _count = int.parse(file);
      });
    }catch(e){
      print(e.toString());
    }

  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('파일 예제2'),
      ),

      body: Container(
        child: Center(
          child: Text(
            '$_count',
            style: TextStyle(fontSize: 40),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          setState(() {
            _count++;
          });
          writeCountFile(_count);
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

 

 

 

  • 공유 환경설정에 저장하는 데이터 ->   키 : 값 쌍으로 구성되어야함  (키는 중복되지 않아야 함)
  • 공유 환경설정에 저장할 수 있는 값 ->  int,  String,  bool,  double, StringList 등 
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  // 데이터 저장하는 함수
  void _setData(int value) async{
    var key = 'count';

    SharedPreferences pref = await SharedPreferences.getInstance();

    // SharedPreferencesdml 세터 함수
    pref.setInt(key, value);

  }

  //데이터를 가져오는 함수
  void _loadData() async{
    var key = 'count';
    SharedPreferences pref = await SharedPreferences.getInstance();

    setState(() {
      //                      ↱ 키 값으로 데이터 가지고 옴
      var value = pref.getInt(key);

      if (value == null){
        _counter = 0;
      }else{
        _counter = value;
      }
    });
  }


  void _incrementCounter() {
    setState(() {
      _counter++;
      // 숫자사 증가되면 그 숫자를 저장해 놓음
      _setData(_counter);
    });
  }

  @override
  void initState(){
    super.initState();
    _loadData();
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(

        title: Text(widget.title),
      ),
      body: Center(

        child: Column(

          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

 

route 페이지 이동 pushNamed, pushReplacementNamed

 

main_todoLIst.dart

import 'package:flutter/material.dart';
import 'package:psw1/todo/secondDetail.dart';
import 'package:psw1/todo/subDetail.dart';
import 'package:psw1/todo/thirdPage.dart';


void main() => runApp(MyApp());

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

  static const String _title = 'Widget Example!!';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      initialRoute: "/",
      routes: {
        '/': (context)=> SubDetail(),
        '/second':(context)=> SecondDetail(),
        '/third':(context)=> ThirdDetail(),
      },
    );
  }
}

 

 

subDetail.dart

import 'package:flutter/material.dart';

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

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

class _SubDetailState extends State<SubDetail> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Sub Detail Example!!'),
      ),

      body: Container(
        child: Center(
          // ignore: deprecated_member_use
          child: RaisedButton(
            onPressed: () {
              //                       ↱ 스택 메모리에 있는 자료를 교체함(스택에 자료를 추가하지 않고 기존 자료를 교체함)
              // Navigator.of(context).pushReplacementNamed('/second');

              //                      ↱ 스택에 새로운 자료를 추가(즉 /second -> secondDetail 등록해 놈)
              Navigator.of(context).pushNamed('/second');
            },
            child: Text('두 번째 페이지로 이동하기'),
          ),
        ),
      ),
    );
  }
}

 

 

secondDetail.dart

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('second Page!'),
      ),
      body: Container(
        child: Center(
          // ignore: deprecated_member_use
          child: RaisedButton(
            onPressed: (){
              //                     ↱ 전 페이지에서 스택에 등록한 /second (secondDetail) 를 -> /third (ThirdDetail)로 변경시킴
              Navigator.of(context).pushReplacementNamed('/third');
            },
            child: Text('세 번째 페이지로 이동하기'),

          ),
        ),
      ),
    );
  }
}

 

 

thirdPage.dart

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Tird Page'),
      ),

      body: Container(
        child: Center(
          // ignore: deprecated_member_use
          child: RaisedButton(
            onPressed: (){
              Navigator.of(context).pop();
            },
            child: Text('첫 번째 페이지로 돌아가기'),
          ),
        ),
      ),
    );
  }
}

 

 



 

subDetail.dart

더보기
import 'package:flutter/material.dart';

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

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

class _SubDetailState extends State<SubDetail> {

  List<String> todoList = [];

  @override
  void initState(){
    super.initState();

    todoList.add('낮잠 자기');
    todoList.add('게임 하기');
    todoList.add('하체 운동 하기');
    todoList.add('단백질 먹기');

  }

  @override
  Widget build(BuildContext context) {

    void _addNavigation(BuildContext context) async{
      //              ↱ secondDetail로 이동하고 결과를 가지고 올때까지 기다림
      final result = await Navigator.of(context).pushNamed('/second');

      setState(() {
        if(result != null && result.toString().length != 0){
          todoList.add(result.toString());
        }
      });
    }


    return Scaffold(
      appBar: AppBar(
        title: Text('Sub Detail Example!!'),
      ),

      body: ListView.builder(
        itemBuilder: (context, index){
          return Card(
            child: InkWell(
              child: Text(
                todoList[index],
                style: TextStyle(fontSize: 30),
              ),
              onTap: (){
                //                       ↱ 스택 메모리에 있는 자료를 교체함(스택에 자료를 추가하지 않고 기존 자료를 교체함)
                // Navigator.of(context).pushReplacementNamed('/second');

                //                      ↱ 스택에 새로운 자료를 추가(즉 /third -> thirdPage 등록해 놈)
                Navigator.of(context).pushNamed('/third', arguments: todoList[index]);
              },
            ),
          );
        }, itemCount: todoList.length,
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          _addNavigation(context);
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

 

secondDetail.dart

더보기
import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {

    TextEditingController textController = new TextEditingController();

    return Scaffold(
      appBar: AppBar(
        title: Text('second Detail!'),
      ),
      body: Container(
        child: Center(
          child: Column(
            children: [
              TextField(
                controller: textController,
                keyboardType: TextInputType.text,
              ),
              SizedBox(
                height: 20,
              ),
              // ignore: deprecated_member_use
              RaisedButton(
                onPressed: () {
                  //                         ↱ pop되면서 데이터 가지고감
                  Navigator.of(context).pop(textController.value.text);
                },
                child: Text('저장하기'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

 

thirdPage.dart

더보기
import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {

    //                  ↱ 라우터로 전달받으 ㄴ데이터 가져오기
    final Object? args = ModalRoute.of(context)!.settings.arguments;

    return Scaffold(
      appBar: AppBar(
        title: Text('Tird Page'),
      ),

      body: Container(
        child: Center(
          // ignore: deprecated_member_use
          child: Text(
            args.toString(),
            style: TextStyle(fontSize: 30),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          Navigator.of(context).pop();
        },
        child: Icon(Icons.arrow_back),
      ),
    );
  }
}

 

 

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '서브 페이지 테스트!!',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      // home: FirstPage(),
      initialRoute: '/',
      routes: {'/':(context)=> FirstPage(),
              '/second':(context)=> SecondPage()},
    );
  }
}


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

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

class _FirstPageState extends State<FirstPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('서브 페이지 메인!'),
      ),

      body: Container(
        child: Center(
          child: Text('첫 번째 페이지!'),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          Navigator.of(context).pushNamed('/second');
        },
        child: Icon(Icons.add),
      ),
    );
  }
}


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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('second Page!'),
      ),

      body: Container(
        child: Center(
          // ignore: deprecated_member_use
          child: RaisedButton(
            onPressed: (){
              Navigator.of(context).pop();
            },
            child: Text('돌아가기'),
          ),
        ),
      ),
    );
  }
}

 

 

 

 

 

 

import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
import 'dart:core';

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

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

class _LargeFileMainState extends State<LargeFileMain> {
  // 내려받을 이미지 주소
  // final imgUrl = 'https://images.pexels.com/photos/240040/pexels-photo-240040.jpeg'
  //                 '?auto=compress';
  bool downloading = false;  // 지금 내려받는 중인지 확인용 (true 면 현재 파일을 내려받고 있다는 의미)
  var progressString ='';    // 현재 얼마나 내려받았는지 표시
  var file;                  // 내려받을 파일
  TextEditingController? _editingController;

  @override
  void initState(){
    super.initState();
    _editingController = new TextEditingController(
      text: 'https://images.pexels.com/photos/240040/pexels-photo-240040.jpeg'
            '?auto=compress');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: TextField(
          controller: _editingController,
          style: TextStyle(color: Colors.white),
          keyboardType: TextInputType.text,
          decoration: InputDecoration(hintText: 'url을 입력하시오'),
        ),
      ),

      body: Center(
        child: downloading
          ? Container(
              height: 120.0,
              width: 200.0,
              child: Card(
                color: Colors.black,
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    CircularProgressIndicator(),
                    SizedBox(
                      height: 20.0,
                    ),
                    Text(
                      '다운로드 진행중: $progressString',
                      style: TextStyle(
                        color: Colors.white,
                      ),
                    ),
                  ],
                ),
              ),
            )

            // downloading이 false일때 실행함 (즉 다운로드를 안 했을때, 또는 다운로드가 완료되었을 때 실행됨)
            // ↱아직 데이터가 없지만 앞으로 데이터를 받아서 처리한 후에 만들겠다는 의미
          :FutureBuilder(
                               //↱ FutureBuilder.future에서 받아온 데이터를 저장한 dynamic 형태의 변수임
            builder: (context, snapshot){
              switch (snapshot.connectionState){
                case ConnectionState.none: // null 일때
                  print('none');
                  return Text('다운 받은 이미지 없음');

                case ConnectionState.waiting:  // 연결되기 전 (아직 데이터 반환 받기전)
                  print('waiting');
                  return CircularProgressIndicator();

                case ConnectionState.active:  // 하나이상의 데이터를 받았을 때
                  print('active');
                  return CircularProgressIndicator();

                case ConnectionState.done:  // 모든 데이터 받아서 연결이 끝났을 때
                  print('done');
                  if(snapshot.hasData){
                    return snapshot.data! as Widget;
                    //    ↳ downloadWidget(file) 함수가 반환하는 데이터 이다..
                  }
              }
              print('end process!!!');
              return Text('데이터 없음');
            },

            future: file == null ? null : downloadWidget(file),
          ),
      ),

      floatingActionButton: FloatingActionButton(
        onPressed: (){
          downloadFile();
        },
        child: Icon(Icons.file_download),
      ),

    );

  }

  // 파일 다운 받기 메서드
  Future<void> downloadFile() async{
    Dio dio = Dio();
    try{
      // ↱ 플러터 앱의 내부 디렉토리를 가져옴
      var dir = await getApplicationDocumentsDirectory();
      // ↱ url에 있는 파일 다운, ↱내부디렉토리에 myimage.jpg 이름으로 저장
      await dio.download(_editingController!.value.text, '${dir.path}/myimage.jpg',
          // ↱ 진행상황을 표시 ↱ rec 는 지금까지 내려받은 데이터, total은 파일의 전체 크기임
          onReceiveProgress: (rec, total){
            print('다운로드 진행: $rec , 파일크기: $total');

            // ↱내려받은 파일의 저장주소 및 파일명을 저장
            file = '${dir.path}/myimage.jpg';
            print('저장된 주소: $file');

            setState(() {
              downloading = true;  // 파일 다운받기 시작하면 true로 변경됨
              // ↱ 파일 다운 진행 상황을 100분율로 계산해서 보여주기
              progressString = ((rec / total) * 100).toStringAsFixed(0) + '%';
            });
          });
    }catch (e){
      print('에러-> $e');
    }

    // ↱ 다운로드 완료되면 실행됨
    setState(() {
      downloading = false;
      progressString = 'Completed!!';
    });
    print('Download 완료');
  }

  Future<Widget> downloadWidget(String filePath) async{
    File file = File(filePath);
    bool exist = await file.exists(); // 파일존재 여부 확인
    new FileImage(file).evict();
    //   ↳ 캐시 초기화 (만약 캐시에 같은 이름의 이미지가 있으면 -> 이미지를 바꾸지 않고 해당하는 이미지를 사용하는데
    //                  evict()을 사용하면 -> 캐시를 비우고 같은 이름이어도 이미지를 갱신함.

    if (exist){
      return Center(
        child: Column(
          children: <Widget>[
            Image.file(file)
          ],
        ),
      );

    }else {
      return Text('데이타가 없군요');
    }
  }
}

탭바 만들기

더보기
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

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

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

class _CupertinoMainState extends State<CupertinoMain> {

  late CupertinoTabBar tabBar;

  @override
  void initState(){
    super.initState();
    
    // 탭바생성
    tabBar = CupertinoTabBar(
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(icon: Icon(CupertinoIcons.home),),
          BottomNavigationBarItem(icon: Icon(CupertinoIcons.add),),
        ]);
  }

  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      home: CupertinoTabScaffold(
        tabBar: tabBar,
        //                    ↱탭 클릭시 해당하는 탭의 인덱스 들어옴
        tabBuilder: (context, value){
          if(value == 0){
            return Container(
              child: Center(
                child: Text('쿠퍼티노 탭 1'),
              ),
            );
          } else{
            return Container(
              child: Center(
                child: Text('쿠퍼티노 탭 2'),
              ),
            );
          }
        },
      ),
    );
  }
}

 

 

 



쿠퍼티노 리스트뷰 만들기

더보기
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:psw1/animalItem.dart';

class CupertinoFirstPage extends StatelessWidget {
  const CupertinoFirstPage({Key? key, required this.animalList}) : super(key: key);

  final List<Animal> animalList;

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: Text('동물리스트!!'),
      ),
      child: ListView.builder(
        itemBuilder: (context, index){
          return Container(
            padding: EdgeInsets.all(5),
            height: 100,
            child: Column(
              children: <Widget>[
                Row(
                  children: <Widget>[
                    Image.asset(
                      animalList[index].imagePath,
                      fit: BoxFit.contain,
                      width: 80,
                      height: 80,
                    ),
                    Text(animalList[index].animalName)
                  ],
                ),
                Container(
                  height: 2,
                  color: CupertinoColors.systemGrey,
                ),
              ],
            ),
          );
        },itemCount: animalList.length,
      ),
    );
  }
}

 

 

 

 


 

cupertinoMain.dart

더보기
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:psw1/animalItem.dart';
import 'package:psw1/iosSub/cupertinoFirstPage.dart';
import 'package:psw1/iosSub/cupertinoSecondPage.dart';

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

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

class _CupertinoMainState extends State<CupertinoMain> {

  late CupertinoTabBar tabBar;
  List<Animal> animalList = <Animal>[];

  @override
  void initState(){
    super.initState();

    // 탭바생성
    tabBar = CupertinoTabBar(
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(icon: Icon(CupertinoIcons.home),),
          BottomNavigationBarItem(icon: Icon(CupertinoIcons.add),),
        ]);

    // 동물 리스트 만들기
    animalList.add(Animal(imagePath: 'repo/images/bee.png', animalName: '벌', kind: '곤충'));
    animalList.add(Animal(imagePath: 'repo/images/cat.png', animalName: '고양이', kind: '포유류'));
    animalList.add(Animal(imagePath: 'repo/images/cow.png', animalName: '젖소', kind: '포유류'));
    animalList.add(Animal(imagePath: "repo/images/dog.png", animalName: "강아지", kind: "포유류"));
    animalList.add(Animal(imagePath: "repo/images/fox.png", animalName: "여우", kind: "포유류"));
    animalList.add(Animal(imagePath: "repo/images/monkey.png", animalName: "원숭이", kind: "영장류"));
    animalList.add(Animal(imagePath: "repo/images/pig.png", animalName: "돼지", kind: "포유류"));
    animalList.add(Animal(imagePath: "repo/images/wolf.png", animalName: "늑대", kind: "포유류"));

  }

  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      home: CupertinoTabScaffold(
        tabBar: tabBar,
        //                    ↱탭 클릭시 해당하는 탭의 인덱스 들어옴
        tabBuilder: (context, value){
          if(value == 0){
            return CupertinoFirstPage(
                animalList: animalList
            );
          } else{
            return CupertinoSecondPage(
              animalList: animalList,
            );
          }
        },
      ),
    );
  }
}

 

 


cupertinoFirstPage.dart

더보기
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:psw1/animalItem.dart';

class CupertinoFirstPage extends StatelessWidget {
  const CupertinoFirstPage({Key? key, required this.animalList}) : super(key: key);

  final List<Animal> animalList;

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: Text('동물리스트!!'),
      ),
      child: ListView.builder(
        itemBuilder: (context, index){
          return Container(
            padding: EdgeInsets.all(5),
            height: 100,
            child: Column(
              children: <Widget>[
                Row(
                  children: <Widget>[
                    Image.asset(
                      animalList[index].imagePath,
                      fit: BoxFit.contain,
                      width: 80,
                      height: 80,
                    ),
                    Text(animalList[index].animalName)
                  ],
                ),
                Container(
                  height: 2,
                  color: CupertinoColors.systemGrey,
                ),
              ],
            ),
          );
        },itemCount: animalList.length,
      ),
    );
  }
}

 

 

 


cupertinoSecondPage.dart

더보기
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:psw1/animalItem.dart';


class CupertinoSecondPage extends StatefulWidget {
  const CupertinoSecondPage({Key? key, required this.animalList}) : super(key: key);

  final List<Animal> animalList;

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

class _CupertinoSecondPageState extends State<CupertinoSecondPage> {

  TextEditingController? _textController;  // 동물 이름
  // TextEditingController _textController = TextEditingController();
  int _kindChoice = 0;                                // 동물 종류
  bool _flyExist = false;                             // 날개 유무
  String? _imagePath;                             // 동물 이미지

  // 세그먼트 위젯에 구성할 내용 담기
  Map<int, Widget> segmentWidgets ={
    0:SizedBox(
      child: Text('양서류', textAlign: TextAlign.center,),
      width: 80,
    ),
    1:SizedBox(
      child: Text('포유류', textAlign: TextAlign.center,),
      width: 80,
    ),
    2:SizedBox(
      child: Text('파충류', textAlign: TextAlign.center,),
      width: 80,
    ),
  };

  @override
  void initState(){
    super.initState();
    _textController = TextEditingController();
  }

  @override
  Widget build(BuildContext context) {

    dynamic getKind(int radioValue){
      switch (radioValue){
        case 0:
          return '양서류';
        case 1:
          return '파충류';
        case 2:
          return '포유류';
      }
    }

    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: Text('동물 추가'),
      ),


      //     ↱머테리얼 디자인은 제일 상위에 SafeArea를 함...
      child: SafeArea(
        child: Container(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Padding(
                  padding: EdgeInsets.all(0),
                  child: CupertinoTextField(
                    controller: _textController,
                    keyboardType: TextInputType.text,
                    maxLines: 1,
                  ),
                ),

                CupertinoSegmentedControl(
                  padding: EdgeInsets.only(bottom: 20, top: 20),
                  groupValue: _kindChoice,
                  children: segmentWidgets,
                  onValueChanged: (int value){
                    setState(() {
                      _kindChoice = value;
                    });
                  }),

                Row(
                  children: <Widget>[
                    Text('날개 있음?'),
                    CupertinoSwitch(
                      value: _flyExist,
                      onChanged: (value){
                        setState(() {
                          _flyExist = value;
                        });
                      }
                    ),
                  ],
                  mainAxisAlignment: MainAxisAlignment.center,
                ),
                SizedBox(
                  height: 100,
                  child: ListView(
                    // ↱ListView를 가로로 스크롤 가능하게 해줌
                    scrollDirection: Axis.horizontal,
                    children: <Widget>[
                      GestureDetector(
                        child: Container(
                            padding: const EdgeInsets.symmetric(horizontal: 10),
                            color: _imagePath=='repo/images/cow.png'?  Colors.blue : Colors.white,
                            child: Image.asset('repo/images/cow.png', width: 80,)),
                        onTap: (){
                          setState(() {
                            _imagePath = 'repo/images/cow.png';

                          });
                        },
                      ),
                      GestureDetector(
                        child: Container(
                            padding: const EdgeInsets.symmetric(horizontal: 10),
                            color: _imagePath=='repo/images/pig.png'?  Colors.blue : Colors.white,
                            child: Image.asset('repo/images/pig.png', width: 80,)),
                        onTap: (){
                          setState(() {
                            _imagePath = 'repo/images/pig.png';
                          });
                        },
                      ),
                      GestureDetector(
                        child: Container(
                            padding: const EdgeInsets.symmetric(horizontal: 10),
                            color: _imagePath=='repo/images/bee.png'? Colors.blue : Colors.white,
                            child: Image.asset('repo/images/bee.png', width: 80,)),
                        onTap: (){
                          setState(() {
                            _imagePath = 'repo/images/bee.png';
                          });
                        },
                      ),
                      GestureDetector(
                        child: Container(
                            padding: const EdgeInsets.symmetric(horizontal: 10),
                            color: _imagePath=='repo/images/cat.png'? Colors.blue : Colors.white,
                            child: Image.asset('repo/images/cat.png', width: 80,)),
                        onTap: (){
                          setState(() {
                            _imagePath = 'repo/images/cat.png';
                          });
                        },
                      ),
                      GestureDetector(
                        child: Container(
                            padding: const EdgeInsets.symmetric(horizontal: 10),
                            color: _imagePath=='repo/images/dog.png'? Colors.blue : Colors.white,
                            child: Image.asset('repo/images/dog.png', width: 80,)),
                        onTap: (){
                          setState(() {
                            _imagePath = 'repo/images/dog.png';
                          });
                        },
                      ),
                      GestureDetector(
                        child: Container(
                            padding: const EdgeInsets.symmetric(horizontal: 10),
                            color: _imagePath =='repo/images/fox.png'? Colors.blue : Colors.white,
                            child: Image.asset('repo/images/fox.png', width: 80,)),
                        onTap: (){
                          setState(() {
                            _imagePath = 'repo/images/fox.png';
                          });
                        },
                      ),
                      GestureDetector(
                        child: Container(
                            padding: const EdgeInsets.symmetric(horizontal: 10),
                            color: _imagePath=='repo/images/monkey.png'? Colors.blue : Colors.white,
                            child: Image.asset('repo/images/monkey.png', width: 80,)),
                        onTap: (){
                          setState(() {
                            _imagePath = 'repo/images/monkey.png';
                          });
                        },
                      ),
                    ],
                  ),
                ),
                CupertinoButton(
                    child: Text('동물 추가하기'),
                    onPressed: (){
                      widget.animalList.add(Animal(
                          imagePath: _imagePath!,
                          animalName: _textController!.value.text,
                          kind: getKind(_kindChoice),
                          flyExist: _flyExist));
                    }
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

 

 

 

 

main.dart

더보기
import 'package:flutter/material.dart';
import 'package:psw1/listview_example.dart';


void main() => runApp(MyApp());

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

  static const String _title = 'Widget Example!!';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: MyHomePage2(),
    );
  }
}

 

 

 

listview_example.dart

더보기
import 'package:flutter/material.dart';
import 'package:psw1/animalItem.dart';
import 'package:psw1/sub/firstPage.dart';
import 'package:psw1/sub/secondPage.dart';


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

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


//                                                       ↱탭 컨트롤러를 이용해 애니메이션 동작 처리를 제공
class _MyHome2PageState extends State<MyHomePage2> with SingleTickerProviderStateMixin{
  // 탭바 컨트롤러 생성하기
  late TabController controller;

  List<Animal> animalList = <Animal>[];


  @override
  void initState(){
    super.initState();
    //                          ↱탭바의 갯수 ↱탭이 이동했을 때 호출되는 콜백 함수를 어디서 처리할지를 지정
    controller = TabController(length: 2, vsync: this);

      animalList.add(Animal(imagePath: 'repo/images/bee.png', animalName: '벌', kind: '곤충'));
      animalList.add(Animal(imagePath: 'repo/images/cat.png', animalName: '고양이', kind: '포유류'));
      animalList.add(Animal(imagePath: 'repo/images/cow.png', animalName: '젖소', kind: '포유류'));
      animalList.add(Animal(imagePath: "repo/images/dog.png", animalName: "강아지", kind: "포유류"));
      animalList.add(Animal(imagePath: "repo/images/fox.png", animalName: "여우", kind: "포유류"));
      animalList.add(Animal(imagePath: "repo/images/monkey.png", animalName: "원숭이", kind: "영장류"));
      animalList.add(Animal(imagePath: "repo/images/pig.png", animalName: "돼지", kind: "포유류"));
      animalList.add(Animal(imagePath: "repo/images/wolf.png", animalName: "늑대", kind: "포유류"));

    // if(animalList.length != 0){
    //
    //   print(animalList.length);
    //   print(animalList.elementAt(0).imagePath);
    //   print(animalList.elementAt(1).imagePath);
    //   print(animalList.elementAt(2).imagePath);
    // }


  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('탭바 예제'),
      ),

      body: TabBarView( // 같은 탭바에 있기에 animalList를 추가, 삭제, 수정이 가능하다.
        children: <Widget>[
          FirstApp(list1: animalList),
          SecondApp(list2: animalList,),
        ],
        controller: controller,
      ),

      bottomNavigationBar: TabBar(tabs: <Tab>[
        Tab(icon: Icon(Icons.looks_one, color: Colors.blue,),),
        Tab(icon: Icon(Icons.looks_two, color: Colors.red),),
      ],
        controller: controller,
      ),
    );
  }
}

 

 

 

firstPage.dart

더보기
import 'package:flutter/material.dart';
import 'package:psw1/animalItem.dart';

class FirstApp extends StatelessWidget {
  const FirstApp({Key? key, required this.list1}) : super(key: key);

  final List<Animal> list1;


  @override
  Widget build(BuildContext context) {

    return Scaffold(
      body: Container(
        //                                     ↱위젯의 위치 ↱아이템 순번
        child: ListView.builder(itemBuilder: (context, position){
          return GestureDetector(
            child: Card(
              child: Row(
                children: <Widget>[
                  Image.asset(
                    list1[position].imagePath,
                    height: 100,
                    width: 100,
                    fit: BoxFit.contain,
                  ),
                  Text(list1[position].animalName),
                ],
              ),
            ),

            onTap: (){
              AlertDialog dialog = AlertDialog(
                content: Text(
                  '이 동물은 ${list1[position].kind} 입니다.',
                  style: TextStyle(fontSize: 30.0),
                ),
              );
              showDialog(
                context: context,
                builder: (BuildContext context) => dialog);
            },
          );
        }, itemCount: list1.length // 아이템 갯수만큼 스크롤할 수 있게 제한
        ),

      ),
    );
  }
}

 

 

 

secondPage.dart

더보기
import 'package:flutter/material.dart';
import 'package:psw1/animalItem.dart';

// ignore: must_be_immutable
class SecondApp extends StatefulWidget {
  final List<Animal> list2;

  const SecondApp({Key? key, required this.list2}) : super(key: key);

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

class _SecondAppState extends State<SecondApp> {

  // 텍스트 필드 사용할 컨트롤러 생성
  final nameController = TextEditingController();

  // 라디오 버튼을 이용하여 동물 종류 선택
  int _radioValue = 0;

  // 동물이 날수 있는지 선택
  bool flyExist = false;

  // 이미지 사진 경로
  var _imagePath;


  @override
  Widget build(BuildContext context) {

    // 라디오 버튼 클릭시 선택된 라디오버튼의 인덱스값으로 변경
    _radioChange(int? value){
      setState(() {
        _radioValue =value!;
      });
    }
    print(_radioValue);

    dynamic getKind(int radioValue){
      switch (radioValue){
        case 0:
          return '양서류';
        case 1:
          return '파충류';
        case 2:
          return '포유류';
      }
    }

    print('--> $_imagePath');

    return Scaffold(
      body: Container(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              TextField(
                controller: nameController,
                keyboardType: TextInputType.text,
                maxLines: 1,
              ),

              // 라디오 버튼 만들기
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: <Widget>[
                  //    ↱ 인덱스값,  ↱라디오 그룹의 선택값
                  Radio(value: 0, groupValue: _radioValue, onChanged: _radioChange),
                  Text('양서류'),

                  Radio(value: 1, groupValue: _radioValue, onChanged: _radioChange),
                  Text('파충류'),

                  Radio(value: 2, groupValue: _radioValue, onChanged: _radioChange),
                  Text('포유류'),
                ],
              ),

              // 체크박스 만들기
              Row(
                // 양쪽 여백사이를 균일하게 배치하기
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: <Widget>[
                  Text('날 수 있나요?'),
                  Checkbox(
                    value: flyExist,
                    onChanged: (check){
                        setState(() {
                          flyExist = check!;
                        });
                    }
                  ),
                ],
              ),

              // 동물이미지 선택하기
              Container(
                height: 100,
                child: ListView(
                  scrollDirection: Axis.horizontal,
                  children: <Widget>[

                    GestureDetector(
                      child: Container(
                          padding: const EdgeInsets.symmetric(horizontal: 10),
                          color: _imagePath=='repo/images/cow.png'?  Colors.blue : Colors.white,
                          child: Image.asset('repo/images/cow.png', width: 80,)),
                      onTap: (){
                        setState(() {
                          _imagePath = 'repo/images/cow.png';

                        });
                      },
                    ),
                    GestureDetector(
                      child: Container(
                          padding: const EdgeInsets.symmetric(horizontal: 10),
                          color: _imagePath=='repo/images/pig.png'?  Colors.blue : Colors.white,
                          child: Image.asset('repo/images/pig.png', width: 80,)),
                      onTap: (){
                        setState(() {
                          _imagePath = 'repo/images/pig.png';
                        });
                      },
                    ),
                    GestureDetector(
                      child: Container(
                          padding: const EdgeInsets.symmetric(horizontal: 10),
                          color: _imagePath=='repo/images/bee.png'? Colors.blue : Colors.white,
                          child: Image.asset('repo/images/bee.png', width: 80,)),
                      onTap: (){
                        setState(() {
                          _imagePath = 'repo/images/bee.png';
                        });
                      },
                    ),
                    GestureDetector(
                      child: Container(
                          padding: const EdgeInsets.symmetric(horizontal: 10),
                          color: _imagePath=='repo/images/cat.png'? Colors.blue : Colors.white,
                          child: Image.asset('repo/images/cat.png', width: 80,)),
                      onTap: (){
                        setState(() {
                          _imagePath = 'repo/images/cat.png';
                        });
                      },
                    ),
                    GestureDetector(
                      child: Container(
                          padding: const EdgeInsets.symmetric(horizontal: 10),
                          color: _imagePath=='repo/images/dog.png'? Colors.blue : Colors.white,
                          child: Image.asset('repo/images/dog.png', width: 80,)),
                      onTap: (){
                        setState(() {
                          _imagePath = 'repo/images/dog.png';
                        });
                      },
                    ),
                    GestureDetector(
                      child: Container(
                          padding: const EdgeInsets.symmetric(horizontal: 10),
                          color: _imagePath =='repo/images/fox.png'? Colors.blue : Colors.white,
                          child: Image.asset('repo/images/fox.png', width: 80,)),
                      onTap: (){
                        setState(() {
                          _imagePath = 'repo/images/fox.png';
                        });
                      },
                    ),
                    GestureDetector(
                      child: Container(
                          padding: const EdgeInsets.symmetric(horizontal: 10),
                          color: _imagePath=='repo/images/monkey.png'? Colors.blue : Colors.white,
                          child: Image.asset('repo/images/monkey.png', width: 80,)),
                      onTap: (){
                        setState(() {
                          _imagePath = 'repo/images/monkey.png';
                        });
                      },
                    ),
                  ],
                ),
              ),


              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  // ignore: deprecated_member_use
                  RaisedButton(
                    child: Text('동물 추가하기'),
                    onPressed: (){
                      var animal = Animal(
                          imagePath: _imagePath,
                          animalName: nameController.value.text,
                          kind: getKind(_radioValue),
                          flyExist: flyExist);
                      AlertDialog dialog = AlertDialog(
                        title: Text('동물 추가하기'),
                        content: Text(
                          '이 동물은 ${animal.animalName} 입니다.'
                          '또 동물의 종류는 ${animal.kind} 입니다. '
                          '\n이미지 주소는 ${animal.imagePath}입니다. \n이 동물을 추가하시겠습니까?',
                        style: TextStyle(fontSize: 15.0),
                        ),
                        actions: [
                          // ignore: deprecated_member_use
                          RaisedButton(
                            child: Text('예'),
                            onPressed: (){

                              widget.list2.add(animal);
                              Navigator.of(context).pop();
                            }
                          ),
                          // ignore: deprecated_member_use
                          RaisedButton(
                            child: Text('아니요'),
                            onPressed: (){
                              Navigator.of(context).pop();
                            }
                          ),
                        ],
                      );
                      showDialog(
                        context: context,
                        builder: (BuildContext context)=> dialog);
                    }
                  ),
                  // ignore: deprecated_member_use
                  RaisedButton(
                    child: Text('동물 삭제하기'),
                    onPressed: (){
                      widget.list2.removeAt(widget.list2.length-1);

                    })
                ],
              ),
            ],
          ),
        ),
      ),
    );


  }
}

 

 

animalItem.dart

더보기
import 'package:flutter/material.dart';

class Animal{
  late String imagePath;
  late String animalName;
  late String kind;
  bool? flyExist = false;

  // 중괄호 후 this.변수명 하면 -> key값으로 파라미터를 넘길 수 있다.
  Animal({required this.imagePath, required this.animalName, required this.kind, this.flyExist});

// Animal(
//       {@required imagePath, @required animalName, @required kind}
//       );

}
import 'package:flutter/material.dart';
import 'package:psw1/sub/firstPage.dart';
import 'package:psw1/sub/secondPage.dart';

void main() => runApp(MyApp());

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

  static const String _title = 'Widget Example!!';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: MyHomePage(),
    );
  }
}


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

  @override
  _MyHomePageState createState() => _MyHomePageState();
}
//                                                       ↱탭 컨트롤러를 이용해 애니메이션 동작 처리를 제공
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin{
  // 탭바 컨트롤러 생성하기
  late TabController controller;

  @override
  void initState(){
    super.initState();
    //                          ↱탭바의 갯수 ↱탭이 이동했을 때 호출되는 콜백 함수를 어디서 처리할지를 지정
    controller = TabController(length: 2, vsync: this);

    // 탭컨트롤러 활용하기 addListener()함수는 탭이 이동할 때 호출 됨
    controller.addListener(() {
      //               ↱ 탭이 현재 이동중인지 나타냄 
      if(!controller.indexIsChanging){
        print('이전 index-> ${controller.previousIndex}');
        print('현재 index-> ${controller.index}');
      }
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('탭바 예제'),
      ),

      body: TabBarView(
        children: <Widget>[
          FirstApp(),
          SecondApp(),
        ],
        controller: controller,
      ),

      bottomNavigationBar: TabBar(tabs: <Tab>[
        Tab(icon: Icon(Icons.looks_one, color: Colors.blue,),),
        Tab(icon: Icon(Icons.looks_two, color: Colors.red),),
      ],
        controller: controller,
      ),
    );
  }
}

 

 

 

플러터가 제공하는 키보드 유형

키보드 유형 설명
text 기본 텍스트
mutiline 멀티라인 텍스트, 메모 같이 여러줄을 입력할 때 사용
number 숫자 키보드 표시
phone 전화번호 전용
datetime 날짜 입력
emailAddress @표시 등 이메일 입력
url 주소입력창

 

 


덧셈 계산기 만들기

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  
  static const String _title = 'Widget Example!!';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: WidgetsApp(),
    );
  }
}

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

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

class _WidgetsAppState extends State<WidgetsApp> {

  String sum ='';

  // TextField를 다루기 위해 사용
  TextEditingController value1 = TextEditingController();
  TextEditingController value2 = TextEditingController();


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          'Widget Example!!'
        ),
      ),

      body: Container(
        child: Center(
          child: Column(
            children: <Widget>[
              Padding(
                padding: EdgeInsets.all(15),
                child: Text('flutter!!'),
              ),

              Padding(
                padding: EdgeInsets.all(15),
                child: Text(
                  '결과 : $sum',
                  style: TextStyle(fontSize: 20),
                ),
              ),

              Padding(
                padding: EdgeInsets.only(left:20, right:20),
                //                                ↱숫자 입력용 키보드   ↱ 컨트롤러 연결(입력값을 value1에 넣음)
                child: TextField(keyboardType: TextInputType.number, controller: value1,), // 사용자로부터 입력받을 거임
              ),

              Padding(
                padding: EdgeInsets.only(left: 20, right: 20),
                child: TextField(keyboardType: TextInputType.number, controller: value2,),
              ),
              // ignore: deprecated_member_use
              Padding(
                padding: const EdgeInsets.all(15),
                // ignore: deprecated_member_use
                child: RaisedButton(
                  child: Row(
                    children: <Widget>[
                      Icon(Icons.add),
                      Text('더하기'),
                    ],
                  ),
                  color: Colors.amber,
                  onPressed: (){
                    setState(() {
                      int result = int.parse(value1.value.text) + int.parse(value2.value.text);
                      sum = '$result';
                    });
                }),
              )
            ],
          ),
        ),
      ),
    );
  }
}

 


뺄셈, 곱셈, 나눗셈기능 추가

DropDown버튼 만들기

더하기, 빼기, 곱하기, 나눗셈 코드추가

 

전체코드⬇︎ 

더보기
import 'package:flutter/material.dart';
import 'dart:core';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  
  static const String _title = 'Widget Example!!';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: WidgetsApp(),
    );
  }
}

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

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

class _WidgetsAppState extends State<WidgetsApp> {

  String sum ='';

  List _buttonList = ['더하기', '빼기', '곱하기', '나누기'];
  List<DropdownMenuItem<String>> _dropDownMenuItems = [];
  String? _buttonText;

  // TextField를 다루기 위해 사용
  TextEditingController value1 = TextEditingController();
  TextEditingController value2 = TextEditingController();

  @override
  void initState(){
    super.initState();

    for(var item in _buttonList){
      _dropDownMenuItems.add(DropdownMenuItem(value:item, child:Text(item)));
    }
    _buttonText = _dropDownMenuItems[0].value;
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          'Widget Example!!'
        ),
      ),

      body: Container(
        child: Center(
          child: Column(
            children: <Widget>[
              Padding(
                padding: EdgeInsets.all(15),
                child: Text('flutter!!'),
              ),

              Padding(
                padding: EdgeInsets.all(15),
                child: Text(
                  '결과 : $sum',
                  style: TextStyle(fontSize: 20),
                ),
              ),

              Padding(
                padding: EdgeInsets.only(left:20, right:20),
                //                                ↱숫자 입력용 키보드   ↱ 컨트롤러 연결(입력값을 value1에 넣음)
                child: TextField(keyboardType: TextInputType.number, controller: value1,), // 사용자로부터 입력받을 거임
              ),

              Padding(
                padding: EdgeInsets.only(left: 20, right: 20),
                child: TextField(keyboardType: TextInputType.number, controller: value2,),
              ),
              // ignore: deprecated_member_use
              Padding(
                padding: const EdgeInsets.all(15),
                // ignore: deprecated_member_use
                child: RaisedButton(
                  child: Row(
                    children: <Widget>[
                      Icon(Icons.add),
                      Text(_buttonText!),
                    ],
                  ),
                  color: Colors.amber,
                  onPressed: (){
                    setState(() {
                      var value1Int = double.parse(value1.value.text);
                      var value2Int = double.parse(value2.value.text);
                      var result;

                      if (_buttonText=='더하기'){
                        result = value1Int + value2Int;
                      }else if(_buttonText=='빼기'){
                        result = value1Int - value2Int;
                      }else if (_buttonText == '곱하기'){
                        result = value1Int * value2Int;
                      } else{
                        result = value1Int / value2Int;
                      }

                      sum = '$result';
                    });
                }),
              ),

              Padding(
                padding: EdgeInsets.all(15),
                child: DropdownButton(
                  // ↱드랍버튼 클릭시 보여질 내용들
                  items: _dropDownMenuItems,
                  onChanged: (value){
                    setState(() {
                      _buttonText = value as String?;
                    });
                  },
                  // 드랍다운 버튼에서 첫메뉴 or 선택된 메뉴를 보여줌
                  value: _buttonText,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

 

 

 

UI 작성

시작 / 일시정지 버튼 영역 UI 작성하기

  • bottomNavigationBar : 어떤 위젯도 배치할 수 있음

타이머 영역 UI 작성

  • Positioned

 

 


타이머 구현하기

  • Duration 클래스 주기

 

 

 

타이머 클래스와 필요한 변수들

 

시작 / 일시정지 /초기화 기능

 

시간표시하기

  •  ~/ 몫을 구하는 연산자,   초 부분 구하기
  • 1/100초 구하기 (00~99 표현하기)

 


 

랩타임 기록하기

 

랩타임 표시하기

 

 

전체코드⬇︎

더보기

 

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


void main() => runApp(MyApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'StopWatch',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),

      home: StopWatchPage(),
    );
  }
}


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

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

class _StopWatchPageState extends State<StopWatchPage> {

  Timer _timer;  // 타이머 선언

  var _time = 0;    // 0.01초마다 1씩 증가시킬 정수형 변수
  var _isRunning = false;   // 현재 시각 상태를 나타낼 변수 (타이머 시작하거나 멈추기 위함)

  List<String> _lapTimes = [];  // 랩타임에 표시할 시간을 저장할 리스트

  @override
  void dispose(){
    _timer?.cancel();  // 앱화면이 종료되면 타이머가 취소됨
    // ?. 는 타이머를 한 번도 동작시키지 않았을 때도(null인 상태) 안전하게 동작을 취소하기 위해 사용

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('스탑워치!~'),
      ),

      body: _buildBody(),

      bottomNavigationBar: BottomAppBar(
        child: Container(
          height: 50.0,
        ),
      ),

      // ↱ _clickButton() 메서드가 실행됨
      floatingActionButton: FloatingActionButton(
        onPressed: () => setState((){
          _clickButton();
        }),

        // ↱_isRunning이  true인 경우면(스탑워치 진행중) pause아이콘 보여지고,  false면 시작 아이콘 보여짐
        child: _isRunning ? Icon(Icons.pause) : Icon(Icons.play_arrow),
      ),

      // ↱floatingActionButton 위젯의 위치를 지정
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
    );
  }

  // 내용 부분
  Widget _buildBody(){

    // 초 부분과, 1/100초 부분을 분리하여 계산하기
    // ↱ _time 변수는 _start() 메서드로 인해 시간을 1/100초 단위로 저장함->
    //   이 것을 100을 나눌경우 몫이 초가 됨, 그리고 나머지가  1/100초 단위가 됨
    var sec = _time ~/ 100;  // " ~/ " 는 몫을 구하는 연산자이다.

    // 1/100 표현하기   ↱ 나머지 구하기  ↱ 문자열을 2자리로, 왼쪽의 빈곳을 0으로 채워 넣음
   var hundredth = '${_time % 100}'.padLeft(2, '0');

    return Center(
      child: Padding(
        padding: const EdgeInsets.only(top: 30),

        child: Stack(
          children: <Widget>[
            Column(
              children: <Widget>[
                // 시간 표시하는 영역
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.end,
                  children: <Widget>[
                    Text( // 초
                      '$sec',
                      style: TextStyle(fontSize: 50.0),
                    ),
                    Text('$hundredth'),  //  1/100초
                  ],
                ),

                // 랩타임을 표시하는 영역
                Container(
                  width: 100,
                  height: 200,
                  child: ListView(
                    //        _lapTimes리스트를 -> map을 이용해 각각 Text 형태로 변환 후 -> 다시 리스트 형태로 반환
                    children: _lapTimes.map((t) => Text(t)).toList(),
                  ),
                ),
              ],
            ),

            // 초기화 버튼 (왼쪽 아래에 위치시키기 위해 Positioned 위젯으로 감쌈)
            Positioned(
              left: 10,
              bottom: 10,
              child: FloatingActionButton(
                backgroundColor: Colors.deepOrange,
                onPressed: _reset,  // 리셋 메서드 실행
                child: Icon(Icons.rotate_left),
              ),
            ),

            // 랩타임 버튼
            Positioned(
              right: 10,
              bottom: 10,
              child: ElevatedButton(
                onPressed: (){
                  setState(() {
                    //              ↱ x.xx 형태로 연결하여 전달
                    _recordLapTime('$sec.$hundredth');
                  });
                },
                child: Text('랩타임'),
              ),
            ),
          ],
        ),
      ),
    );
  }

  // 시작 또는 일시정지 버튼 클릭
  void _clickButton(){
    _isRunning = !_isRunning;  // 클릭할 때마다 boolean 값 변경

    if(_isRunning){
      _start();
    }else{
      _pause();
    }
  }


  // Timer 객체를 초기화하고 0.01초에 한 번씩 반복하게 하기
  void _start(){
    // 첫 번째 인수 Duration 인수를 설정하면 -> 두 번째 인수로 받은 함수에서 실행됨
    //                                   ↱0.01초
    _timer = Timer.periodic(Duration(milliseconds: 10), (timer) {
      setState(() {
        _time++; // 0.01초마다 _time변수의 값을 1씩 증가 시킴
      });
    });
  }

  // 타이머 일시정지하기
  void _pause(){
    _timer?.cancel();
  }

  // 초기화
  void _reset(){
    setState(() {
      _isRunning = false;
      _timer?.cancel();
      _lapTimes.clear();
      _time = 0;
    });
  }


  // 랩타임 기록하기
  void _recordLapTime(String time){
    //               ↱_lapTimes 리스트의 맨 앞에 추가하기
    _lapTimes.insert(0, '스탑워치 ${_lapTimes.length +1} 번째 실행 $time');
  }

}

TextFormField에 사용되는 속성

  • decoration : InputDecoration 클래스를 설정하여 외곽선, 힌트 등을 설정
  • controller : TextEditingController 인스턴스를 설정, 텍스트 필드를 조작할 때 사용
  • keyboardType : 입력 타입을 제한할 수 있음
  • validator : 입력값을 검증하고, 에러 메시지를 반환

  • 키와몸구게를 입력하는 화면 작성

더보기

 

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,
      ),
      home: BmiMain(),
    );
  }
}


// 첫 번째 페이지 (사용자의 입력을 받고 검증하기 위한 페이지)
class BmiMain extends StatefulWidget {

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

class _BmiMainState extends State<BmiMain> {

  // 폼의 상태를 얻기 위한 키
  final _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('비만도 계산!'),
      ),

      body: Container(
        padding: const EdgeInsets.all(16.0),
        // 폼 (TextFormField를 사용하기 위해 Form으로 감싼다.)
        child: Form(
          key: _formKey,  // 키 할당
          child: Column(
            children: <Widget>[
              TextFormField(
                decoration: InputDecoration(
                  // ↱ 외곽선이 있고 힌트로 '키를 입력하시오'를 표시함
                  border: OutlineInputBorder(),
                  hintText: '키를 입력하시오',
                ),
                keyboardType: TextInputType.number,
              ),

              SizedBox(
                height: 16.0,
              ),

              TextFormField(
                decoration: InputDecoration(
                  border: OutlineInputBorder(),
                  hintText: '몸무게'
                ),
                keyboardType: TextInputType.number,
              ),
              Container(
                margin: const EdgeInsets.only(top: 16.0),
                alignment: Alignment.centerRight,
                child: ElevatedButton(
                  onPressed: (){
                    // 폼에 입력된 값 검증하기
                    if(_formKey.currentState.validate()){
                      // 검증시 처리하기
                    }
                  },
                  child: Text('결과'),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}



// 두 번째 페이지 (결과를 보여주는 페이지)
class BmiResult extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

 

StatelessWidget은 -> 변화가 없는 위젯 이므로 클래스 내부에는 상수(final)만 존재가능
                                     상수는 값을 반드시 초기화 해야하므로 생성자를 통해 초기화를 해야함

 

 

결과화면 페이지 만들기

 

값 검증 및 화면전환 하기

키와 몸무게 값을 얻는 컨트롤로 준비하기

- TextFormField 위젯에 입력된 값을 가져오기 위해 -> TextEditingController 클래스 사용 (컨트롤러)

 

 

 

TextFormField 위젯과 컨트롤러 연결하기

 

결과 버튼 클릭 시 -> 폼을 검증하고 다음 화면으로 값 전달하기

 

 


결과 표시하기

BMI 계산하기

 

BMI 값에 따라 결과 표시하기

 

BMI 값에 따라 아이콘 표시하기

 

전체코드⬇︎

더보기
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,
      ),
      home: BmiMain(),
    );
  }
}


// 첫 번째 페이지 (사용자의 입력을 받고 검증하기 위한 페이지)
class BmiMain extends StatefulWidget {

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


class _BmiMainState extends State<BmiMain> {

  // 폼의 상태를 얻기 위한 키
  final _formKey = GlobalKey<FormState>();

  // 입력된 값 가져오는 컨트롤러 준비하기
  final _heightController = TextEditingController();
  final _weightController = TextEditingController();

  // ↱ 화면이 종료될 때는 반드시 사용한 컨트롤러 종료시켜야함!!
  @override
  void dispose(){
    _heightController.dispose();
    _weightController.dispose();
    super.dispose();
  }



  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('비만도 계산!'),
      ),

      body: Container(
        padding: const EdgeInsets.all(16.0),
        // 폼 (TextFormField를 사용하기 위해 Form으로 감싼다.)
        child: Form(
          key: _formKey,  // 키 할당
          child: Column(
            children: <Widget>[

              // 키 입력 필드
              TextFormField(
                decoration: InputDecoration(
                  // ↱ 외곽선이 있고 힌트로 '키를 입력하시오'를 표시함
                  border: OutlineInputBorder(),
                  hintText: '키를 입력하시오',
                ),

                // 컨트롤러 프로퍼티에 -> 컨트롤러를 지정하여 입력한 값을 컨트롤를 통해 얻음
                controller: _heightController,

                keyboardType: TextInputType.number,

                // 유효성 검사하기
                validator: (value){
                  if(value.trim().isEmpty){ // 입력한 값의 공백을 제거 후 비어 있는지 확인하기
                    return '키를 입력하세요';
                  }
                  return null;  // null을 반환하면 에러가 없는 것임
               },
              ),


              SizedBox(
                height: 16.0,
              ),

              // 몸무게 입력 필드
              TextFormField(
                decoration: InputDecoration(
                  border: OutlineInputBorder(),
                  hintText: '몸무게'
                ),

                keyboardType: TextInputType.number,

                // 컨트롤러 연결하기
                controller: _weightController,

                validator: (value){
                  if(value.trim().isEmpty){
                    return '몸무게를 입력하세요';
                  }
                  return null;
                },

              ),


              Container(
                margin: const EdgeInsets.only(top: 16.0),
                alignment: Alignment.centerRight,
                child: ElevatedButton(
                  onPressed: (){
                    // 폼에 입력된 값 검증하기
                    if(_formKey.currentState.validate()){
                      // 검증시 처리하기
                      Navigator.push(
                        context,
                        MaterialPageRoute(
                          //                    ↱BmiResult 클래스의 생성자를 통해서 키와 몸무게를 double 타입으로 전달
                          builder: (context) => BmiResult(
                            // ↱ 입력받은 문자열을 -> double타입으로 변환하여 전달
                           double.parse(_heightController.text.trim()),
                           double.parse(_weightController.text.trim())),
                        ),
                      );
                    }
                  },
                  child: Text('결과'),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}



// 두 번째 페이지 (결과를 보여주는 페이지)
class BmiResult extends StatelessWidget {
  // StatelessWidget은 -> 변화가 없는 위젯 이므로 클래스 내부에는 상수(final)만 존재가능
  //                      상수는 값을 반드시 초기화 해야하므로 생성자를 통해 초기화를 해야함

  final double height;  // 키
  final double weight;  // 몸무게
  BmiResult(this.height, this.weight);  // 키와 몸무게를 받는 생성자

  @override
  Widget build(BuildContext context) {

    // BMI 값 계산하기
    final bmi = weight /((height / 100) * (height / 100));
    print('bmi-> $bmi');

    // BMI 값에 따라 결과 표시할 메서드
    String _calcBmi(double bmi){

      var result = '저체중';

      if(bmi >= 35){
        result = '고도 비만';
      }else if(bmi >= 30){
        result ='2단계 비만';
      }else if(bmi >= 25){
        result = '1단계 비만';
      }else if(bmi >= 23){
        result ='과체중';
      }else if(bmi >= 18.5){
        result ='정상';
      }
      return result;
    }

    // BMI 값에 따라 아이콘 변경하
    Widget _buildIcon(double bmi){
      if (bmi >= 23){
        return Icon(
          Icons.sentiment_very_dissatisfied,
          color: Colors.red,
          size: 100,
        );
      } else if(bmi >= 18.5){
        return Icon(
          Icons.sentiment_satisfied,
          color: Colors.green,
          size: 100,
        );
      } else{
        return Icon(
          Icons.sentiment_dissatisfied,
          color: Colors.orange,
          size: 100,
        );
      }
    }


    return Scaffold(
      appBar: AppBar(
        title: Text('비만도 계산기'),
      ),

      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              _calcBmi(bmi), // BMI 계산 결과를 문자열로 보여줌
              style: TextStyle(fontSize: 36),
            ),

            SizedBox(
              height: 16,
            ),

            // 계산 결과에 따른 아이콘 변경
            _buildIcon(bmi),
          ]
        ),
      ),
    );
  }
}

 

 

 

 

+ Recent posts