본문 바로가기
프로그래밍/Flutter & Dart

Flutter로 비만도 계산기 만들기

by 어느덧중반 2020. 5. 19.
반응형

키, 몸무게 2개의 숫자값을 입력받아 비만도를 계산하는 어플

 

1. 입력 화면

2. 결과 화면

3. 값 검증 및 화면 전환

4. 최종 화면 및 소스


 

1. 입력 화면

키, 몸무게 입력받는 양식을 Form 위젯으로 wrap해준다. 그리고 submit을 누를 때 키, 몸무게값을 검증하기 위해 form의 상태를

얻기위한 key가 필요하다. 키는 GlobalKey<FormState>타입으로 선언하고 Form 위젯의 key 프로퍼티로 선언하면 상태를 얻을 수 있다.

키, 몸무게 입력필드는 검증 로직을 작성할 수 있는 TextFormField를 사용한다.

form key의 currentState.validate()로 Form 입력값에 대한 검증이 가능하다. (아래 소스는 검증부분 미작성)

import 'package:flutter/material.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: 'TextEditingController example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: InputForm(),
    );
  }
}

class InputForm extends StatefulWidget {
  @override
  _InputFormState createState() => _InputFormState();
}

class _InputFormState extends State<InputForm> {
  final _formKey = GlobalKey<FormState>(); // form 상태를 얻기 위한 키

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('BMI CALCULATE'),
      ),
      body: Center(
        child: Container(
          padding: const EdgeInsets.all(15.0),
          child: Form(
            key: _formKey, // form의 key 할당
            child: Column(
              children: <Widget>[
                TextFormField(
                  decoration: InputDecoration(
                    border: OutlineInputBorder(),
                    hintText: 'height',
                  ),
                  keyboardType: TextInputType.number, // 숫자 입력만
                ),
                SizedBox(
                  height: 20,
                ),
                TextFormField(
                  decoration: InputDecoration(
                    border: OutlineInputBorder(),
                    hintText: 'weight'
                  ),
                  keyboardType: TextInputType.number,
                ),
                Container(
                  margin: const EdgeInsets.all(20.0),
                  alignment: Alignment.centerRight,
                  child: RaisedButton(
                    onPressed: () {
                      // form 값 검증
                      if(_formKey.currentState.validate()) {
                        // 검증되면 처리
                      }
                    },
                    child: Text('submit'),
                  )
                )
              ],
            )
          )
        ),
      )
    );
  }
}

2. 결과화면

결과화면은 별도의 State를 갖지 않으므로 statelesswidget으로 작성하자.

우선은 예상 결과 화면만 그려보자

import 'package:flutter/material.dart';

class Result extends StatelessWidget {
  final double height;
  final double weight;

  Result(this.height, this.weight);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Result')
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              '정상',
              style: TextStyle(fontSize: 40),
            ),
            SizedBox(height: 20,),
            Icon(
              Icons.sentiment_very_satisfied,
              color: Colors.lightGreen,
              size: 90,
            ),
          ],
        )
      )
    );
  }
}

3. 값 검증 및 화면 전환

키, 몸무게값을 검증하고 결과화면으로 전달해야 한다.

- 키, 몸무게 값을 얻기 위한 TextEditingController

- 컨트롤러 TextFormField에 연결

- submit 클릭시 값 검증하고 결과화면으로 값 전달 및 화면 전환

 

 

- 키, 몸무게 값을 얻기 위한 TextEditingController

컨트롤러 객체 2개 생성해주자. (반드시 dispose() 해줘야 한다)

...

class _InputFormState extends State<InputForm> {
  final _formKey = GlobalKey<FormState>(); // form 상태를 얻기 위한 키
  final _heightController = TextEditingController();
  final _weightController = TextEditingController();
  
  @override
  void dispose() {
    // 화면 종료될 때 컨트롤러도 반드시 종료시켜주기
    _heightController.dispose();
    _weightController.dispose();
    super.dispose();
  }

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

 

- 컨트롤러 TextFormField에 연결

TextFormField는 TextField와 모양,기능은 동일하나 validator 프로퍼티를 추가로 가지고 있다.

(validator는 에러메시지를 표시할 규칙을 함수로 작성할 수 있기 때문에 회원가입 등 폼에 사용하면 간단한 입력검증이 가능)

각 controller를 TextFormField에 연결해주기

                ...
                TextFormField(
                  decoration: InputDecoration(
                    border: OutlineInputBorder(),
                    hintText: 'height',
                  ),
                  keyboardType: TextInputType.number, // 숫자 입력만
                  controller: _heightController,
                  validator: (value) {
                    if(value.trim().isEmpty) {
                      return 'Input your height';
                    }
                    return null;
                  },
                ),
                ...

submit 눌렀을 때 값 검증 가능

- submit 클릭시 값 검증하고 결과화면으로 값 전달 및 화면 전환

                Container(
                  margin: const EdgeInsets.all(20.0),
                  alignment: Alignment.centerRight,
                  child: RaisedButton(
                    onPressed: () {
                      // form 값 검증
                      if(_formKey.currentState.validate()) {
                        // 검증되면 처리
                        Navigator.push( // 내비게이션
                          context,
                          MaterialPageRoute(
                            builder: (context) => Result( // 결과화면에 입력값 넘겨주기
                              double.parse(_heightController.text.trim()),
                              double.parse(_weightController.text.trim())
                            ),
                          ),
                        );
                      }

 

4. 최종 소스 및 화면

- main.dart

import 'package:flutter/material.dart';
import 'bmi_result.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: 'TextEditingController example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: InputForm(),
    );
  }
}

class InputForm extends StatefulWidget {
  @override
  _InputFormState createState() => _InputFormState();
}

class _InputFormState extends State<InputForm> {
  final _formKey = GlobalKey<FormState>(); // form 상태를 얻기 위한 키
  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('BMI CALCULATE'),
      ),
      body: Center(
        child: Container(
          padding: const EdgeInsets.all(15.0),
          child: Form(
            key: _formKey, // form의 key 할당
            child: Column(
              children: <Widget>[
                TextFormField(
                  decoration: InputDecoration(
                    border: OutlineInputBorder(),
                    hintText: 'height',
                  ),
                  keyboardType: TextInputType.number, // 숫자 입력만
                  controller: _heightController,
                  validator: (value) {
                    if(value.trim().isEmpty) {
                      return 'Input your height';
                    }
                    return null;
                  },
                ),
                SizedBox(
                  height: 20,
                ),
                TextFormField(
                  decoration: InputDecoration(
                    border: OutlineInputBorder(),
                    hintText: 'weight'
                  ),
                  keyboardType: TextInputType.number,
                  controller: _weightController,
                  validator: (value) {
                    if(value.trim().isEmpty) {
                      return 'Input your weight';
                    }
                    return null;
                  },
                ),
                Container(
                  margin: const EdgeInsets.all(20.0),
                  alignment: Alignment.centerRight,
                  child: RaisedButton(
                    onPressed: () {
                      // form 값 검증
                      if(_formKey.currentState.validate()) {
                        // 검증되면 처리
                        Navigator.push(
                          context,
                          MaterialPageRoute(
                            builder: (context) => Result(
                              double.parse(_heightController.text.trim()),
                              double.parse(_weightController.text.trim())
                            ),
                          ),
                        );
                      }
                    },
                    child: Text('submit'),
                  )
                )
              ],
            )
          )
        ),
      )
    );
  }
}

- bmi_result.dart

import 'package:flutter/material.dart';

class Result extends StatelessWidget {
  final double height;
  final double weight;

  Result(this.height, this.weight);

  @override
  Widget build(BuildContext context) {
    final bmi = weight / ((height / 100) * (height / 100));

    return Scaffold(
      appBar: AppBar(
        title: Text('Result')
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              _calculate(bmi),
              style: TextStyle(fontSize: 40),
            ),
            SizedBox(height: 20,),
            _sketchIcon(bmi),
          ],
        )
      )
    );
  }

  Widget _sketchIcon(bmi) {

    if(bmi >= 23) {
      return Icon(
        Icons.sentiment_very_dissatisfied,
        size: 100,
        color: Colors.red,
      );
    } else if(bmi >= 18.5) {
      return Icon(
        Icons.sentiment_neutral,
        size: 100,
        color: Colors.lightGreen,
      );
    } else {
      return Icon(
        Icons.sentiment_very_dissatisfied,
        size: 100,
        color: Colors.amber,
      );
    }
  }

  String _calculate(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 = '정상';
    } else {
      result = '저체중';
    }
    return result;
  }
}

반응형

댓글