• Flutter for App——一个简单的BMI计算APP


    效果截图

    初始化

    初始化表单控制器和焦点节点

    void initView(){
        formKey = GlobalKey();
        heightController = TextEditingController();
        weightController = TextEditingController();
        heightNode = FocusNode();
        weightNode = FocusNode();
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    为体重和升高焦点节点进行事件监听,并改变状态标志位

    void addListener(){
        heightNode.addListener(() {
          if(heightController.text.isNotEmpty){
            heightClear = true;
          }else{
            heightClear = false;
          }
        });
    
        weightNode.addListener(() {
          if(weightController.text.isNotEmpty){
            weightClear = true;
          }else{
            weightClear = false;
          }
        });
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    布局

    顶部区域

    标题

    没有使用系统标题栏,通过将Text文本放到中间,作为标题使用

     ///标题
        Widget title = Center(
            child: Text(titleText,style: getTextStyle(18.0, Colors.white, FontWeight.bold),)
        );
    
    • 1
    • 2
    • 3
    • 4
    计算结果

    身体状况和标准体重通过Expanded各占一半宽度,并各执一边

    ///结果匹配值
        Widget physical = Row(
          children: [
            Expanded(child: Text(body,style: getTextStyle(12.0, Colors.white, FontWeight.normal),textAlign: TextAlign.left,)),
            Expanded(child: Text(standardWeight,style: getTextStyle(12.0, Colors.white, FontWeight.normal),textAlign: TextAlign.right))
          ],
        );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    组合顶部区域

    然后通过Column进行垂直排列,其中使用const SizedBox(height: 30.0,)做完高度间隔分隔符

    ///BMI计算结果
        Widget bmiResult = Column(
          children: [
            const SizedBox(height: 40.0,),
            title,
            const SizedBox(height: 30.0,),
            Align( alignment:Alignment.topLeft ,child: Text('BMI',style: getTextStyle(14.0, Colors.white, FontWeight.bold))),
            const SizedBox(height: 10.0,),
            Align( alignment:Alignment.topLeft ,child: Text(bmi,style: getTextStyle(24.0, Colors.white, FontWeight.bold))),
            const SizedBox(height: 20.0,),
            physical,
            const SizedBox(height: 20.0,),
            Align( alignment:Alignment.topLeft ,child: Text(diseaseTip,style: getTextStyle(12.0, Colors.white, FontWeight.normal))),
          ],
        );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    背景

    最后将上列用Container但布局进行包裹,并设置背景颜色

    Widget topArea = Container(
          color: Colors.green,
          padding: const EdgeInsets.all(20.0),
          child: bmiResult,
        );
    
    • 1
    • 2
    • 3
    • 4
    • 5

    中间区域

    中间区域包括两个输入框和计算按钮

    输入框

    身高和体重输入行一致,此处以身高为例;身高TextFormField包括啦上述初始化的控制权和焦点节点,并添加了一个末尾Icon,当输入内容不为空时,显示清空按钮,并对点击事件做清空处理,在onSaved中对输入的内容进行保存

       /**
         * 身高输入框*/
        Widget heightArea = Container(
          padding: const EdgeInsets.all(2.0),
          decoration: const BoxDecoration(
            shape: BoxShape.rectangle,
            borderRadius: BorderRadius.all(Radius.circular(5.0)),
            color: Colors.white30
          ),
          child: TextFormField(
            maxLines: 1,
            keyboardType: TextInputType.number,
            controller: heightController,
            focusNode: heightNode,
            decoration: InputDecoration(
                hintText: '请输入身高',
                hintStyle: getTextStyle(14.0, Colors.grey, FontWeight.normal),
                border: InputBorder.none,
                suffixIcon: heightClear ?
                IconButton(
                    onPressed: (){heightController.clear();},
                    icon: const Icon(Icons.clear)) : null
            ),
            onSaved: (value){
              inputHeight = value.toString();
            },
          ),
        );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    输入行

    然后用Row布局水平排列,同样使用 const SizedBox(width: 20.0,)做水平间隔分隔符

    /**
         * 身高行*/
        Widget height = Row(
          children: [
            Text('身高',style: getTextStyle(16.0, Colors.grey, FontWeight.bold),),
            const SizedBox(width: 20.0,),
            Expanded(child: heightArea),
            const SizedBox(width: 20.0,),
            Text('CM',style: getTextStyle(16.0, Colors.grey, FontWeight.bold),),
          ],
        );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    计算按钮

    然后使用ElevatedButton,并对点击事件做计算处理

      ///计算按钮
        Widget calculateButton = SizedBox(
          width: 300.0,
          height: 50.0,
          child: ElevatedButton(
            onPressed: (){
              if(formKey.currentState!.validate()){
                formKey.currentState!.save();
                //do calculate tings
                BMICalculate(inputHeight,inputWeight);
              }
            },
            style: ButtonStyle(
              backgroundColor: MaterialStateProperty.all(Colors.green),
            ),
            child: Text('开始计算',style: getTextStyle(16.0, Colors.white, FontWeight.bold),),
          ),
        );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    分界线
     /// 分界线
        Widget splitLine = const SizedBox(
          height: 1.0,
          width: double.infinity,
          child: DecoratedBox(
            decoration: BoxDecoration(color: Colors.grey),
          ),
        );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    组合中间区域

    并对表单进行监听,并通过Column进行子组件布局

    Widget centerArea = Form(
          key: formKey,
            child:  Container(
              padding: const EdgeInsets.all(20.0),
              child: Column(
                children: [
                  height,
                  const SizedBox(height: 10.0,),
                  splitLine,
                  const SizedBox(height: 40.0,),
                  weight,
                  const SizedBox(height: 10.0,),
                  splitLine,
                  const SizedBox(height: 30.0,),
                  standardTip,
                  const SizedBox(height: 40.0,),
                  calculateButton,
                ],
              ),
            )
        );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    底部区域

    底部使用的是Table表格进行布局,TableRow为行,TableCell为列,也可不使用TableCell进行布局,可输入自己需要的内容组件

    Widget standardTable = Container(
          padding: const EdgeInsets.all(20.0),
          child: Table(
            border: TableBorder.all(
                color: Colors.black,
                width: 1.0,
                style: BorderStyle.solid,
                borderRadius: const BorderRadius.all(Radius.circular(5.0))
            ),
            children: [
              TableRow(
                  decoration: const ShapeDecoration(
                      color: Colors.green,
                      shape: RoundedRectangleBorder(
                          borderRadius: BorderRadius.only(
                            topLeft: Radius.circular(5),
                            topRight: Radius.circular(5),
                          ),
                      ),
                  ),
                  children: [
                    TableCell(child: Container(
                        padding: const EdgeInsets.all(10.0),
                        child: Center(child: Text('BMI值',style: getTextStyle(14.0, Colors.white, FontWeight.normal),),))
                    ),
                    TableCell(child: Container(
                        padding: const EdgeInsets.all(10.0),
                        child: Center(child: Text('身体状况',style: getTextStyle(14.0, Colors.white, FontWeight.normal),),))
                    )
                  ]
              ),
              TableRow(
                  children: [
                    TableCell(child: Container(
                        padding: const EdgeInsets.all(7.0),
                        child: Center(child: Text('<18.5',style: getTextStyle(12.0, Colors.grey, FontWeight.normal),),))
                    ),
                    TableCell(child: Container(
                        padding: const EdgeInsets.all(7.0),
                        child: Center(child: Text('偏瘦',style: getTextStyle(12.0, Colors.grey, FontWeight.normal),),))
                    ),
                  ]
              ),
              TableRow(
                  children: [
                    TableCell(child: Container(
                        padding: const EdgeInsets.all(7.0),
                        child: Center(child: Text('18.5~23.9',style: getTextStyle(12.0, Colors.grey, FontWeight.normal),),))
                    ),
                    TableCell(child: Container(
                        padding: const EdgeInsets.all(7.0),
                        child: Center(child: Text('正常',style: getTextStyle(12.0, Colors.grey, FontWeight.normal),),))
                    ),
                  ]
              ),
              TableRow(
                  children: [
                    TableCell(child: Container(
                        padding: const EdgeInsets.all(7.0),
                        child: Center(child: Text('24~27.9',style: getTextStyle(12.0, Colors.grey, FontWeight.normal),),))
                    ),
                    TableCell(child: Container(
                        padding: const EdgeInsets.all(7.0),
                        child: Center(child: Text('偏胖',style: getTextStyle(12.0, Colors.grey, FontWeight.normal),),))
                    ),
                  ]
              ),
              TableRow(
                  children: [
                    TableCell(child: Container(
                        padding: const EdgeInsets.all(7.0),
                        child: Center(child: Text('>=28',style: getTextStyle(12.0, Colors.grey, FontWeight.normal),),))
                    ),
                    TableCell(child: Container(
                        padding: const EdgeInsets.all(7.0),
                        child: Center(child: Text('肥胖',style: getTextStyle(12.0, Colors.grey, FontWeight.normal),),))
                    ),
                  ]
              ),
            ],
          ),
        );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

    页面组合

    然后将上面三个区域进行组合

    return Container(
          child: Column(
            children: [
              topArea,
              const SizedBox(height: 10.0,),
              centerArea,
              const SizedBox(height: 40.0,),
              standardTable,
            ],
          ),
        );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    BMI计算

    • BMI = 体重 / 身高的平方
    • 身高标准体重 = 身高 - 105
      每一个Text使用变量作为内容展示,然后修改其内容之后,使用setState进行值刷新
     ///BMI计算
      void BMICalculate(String sHeight,String sWeight){
        if(sHeight.isEmpty || sWeight.isEmpty){
          showFailedToast('身高或体重不能为空');
          return;
        }
        //bmi计算
        double dHeight = double.parse(sHeight) ;
        double dWeight = double.parse(sWeight);
        double bmiValue = dWeight / ((dHeight / 100)*(dHeight / 100));
        //身体状况计算
        String condition = "NaN";
        if(bmiValue < 18.5){
          condition = '偏瘦';
        }else if(bmiValue >= 18.5 && bmiValue < 23.9){
          condition = '正常';
        }else if(bmiValue >= 23.9 && bmiValue < 27.9){
          condition = '偏胖';
        }else{
          condition = '肥胖';
        }
        //标准体重计算 身高-105
        double standard = dHeight - 105;
        //刷新数据
        setState(() {
          ///保留一位小数
          bmi = bmiValue.toStringAsFixed(1);
          body = 'body:$condition';
          standardWeight = '身高标准体重:${standard.toStringAsFixed(1)}';
        });
        print('【计算结果】:$bmi');
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    Toast弹窗

    效果

    导入依赖

    pubspec.yaml文件中导入如下第三方包

    fluttertoast: ^8.1.1
    
    • 1

    封装

    Web和APP有一些地方不一样,例如背景颜色,web需要单独进行设立,如下所示

    ///web端的位置和背景颜色需要重新设置,如webPosition| webBgColor
     void showSuccessToast(String text) {
      Fluttertoast.showToast(
          msg: text,
          toastLength: Toast.LENGTH_SHORT,
          gravity: ToastGravity.TOP,
          backgroundColor: Colors.green,
          fontSize: 14.0,
          webPosition: 'center',
          webBgColor: 'linear-gradient(0deg,#37ecba 0%, #72afd3 100%)',
      );
    }
    
      void showFailedToast(String text) {
        Fluttertoast.showToast(
          msg: text,
          toastLength: Toast.LENGTH_SHORT,
          gravity: ToastGravity.TOP,
          backgroundColor: Colors.red,
          fontSize: 14.0,
          webPosition: 'center',
            webBgColor: 'linear-gradient(0deg,#f43b47 0%, #453a94 100%)'
        );
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
  • 相关阅读:
    智能SQL生成:后端技术与LLM的完美结合
    ssm--ActiveMQ如何解决数据丢失?消息重发机制和消息确认机制ACK
    vue3的ref,reactive的使用和原理解析
    深度学习(十一)---zed 调用yolov5 进行识别目标并实时测距
    UTONMOS带您体验数字人 感受元宇宙
    express脚手架及koa脚手架
    RPA平台比较和选择指南
    国外访问学者/博士后留学人员反诈骗指南
    开源组件与中间件的学习笔记2
    nesp实验八 路由器RIP协议路由实验
  • 原文地址:https://blog.csdn.net/News53231323/article/details/127987357