You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter开发:使用setState()时BMI计算器出现布局溢出问题

BMI计算器布局溢出问题修复

我正在开发一款BMI计算器应用,原本功能运行正常,但给自定义RoundIconButton组件添加setState()逻辑后,界面出现布局溢出问题,怀疑根源在这个自定义按钮组件上。

问题代码

RoundIconButton组件代码

import 'package:flutter/material.dart';

class RoundIconButton extends StatelessWidget {
  RoundIconButton({required this.icon, required this.tap});

  final IconData icon;
  final Function tap;

  @override
  Widget build(BuildContext context) {
    return RawMaterialButton(
      onPressed: tap(),
      child: Icon(
        icon,
        color: Color(0xffFF8B00),
      ),
      elevation: 6.0,
      constraints: BoxConstraints.tightFor(
        width: 40.0,
        height: 47.0,
      ),
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(10),
      ),
      fillColor: Colors.black,
    );
  }
}

主页面InputPage代码

import 'package:bmi_calculator/round_icon_button.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'reusable_card.dart';
import 'iconText.dart';
import 'constants.dart';

enum Gender {
  male,
  female,
}

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

  @override
  State<InputPage> createState() => _InputPageState();
}

class _InputPageState extends State<InputPage> {
  Gender? selectedGender;
  int height = 180;
  int weight = 50;
  int age = 18;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        backgroundColor: Colors.black,
        title: Text('BMI Calculator'),
      ),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Expanded(
            child: Row(
              children: [
                Expanded(
                  child: GestureDetector(
                    onTap: () {
                      setState(() {
                        selectedGender = Gender.male;
                        print('Male tapped');
                      });
                    },
                    child: ReuseableCard(
                      colour: selectedGender == Gender.male
                          ? kactiveCardColor
                          : kinactiveCardColor,
                      cardChild: iconText(
                        icon: FontAwesomeIcons.mars,
                        lable: 'Male',
                      ),
                    ),
                  ),
                ),
                Expanded(
                  child: GestureDetector(
                    onTap: () {
                      setState(() {
                        selectedGender = Gender.female;
                        print('Female Tapped');
                      });
                    },
                    child: ReuseableCard(
                      colour: selectedGender == Gender.female
                          ? kactiveCardColor
                          : kinactiveCardColor,
                      cardChild: iconText(
                        icon: FontAwesomeIcons.venus,
                        lable: 'FeMale',
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
          Expanded(
            child: ReuseableCard(
              colour: Color(0xffFF8B00),
              cardChild: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(
                    'Height'.toUpperCase(),
                    style: klableTextStylet,
                  ),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.baseline,
                    textBaseline: TextBaseline.alphabetic,
                    children: [
                      Text(
                        height.toString(),
                        style: kNumberTextStyle,
                      ),
                      Text('cm'),
                    ],
                  ),
                  SliderTheme(
                    data: SliderTheme.of(context).copyWith(
                      overlayColor: Colors.red,
                      thumbShape: RoundSliderThumbShape(enabledThumbRadius: 15),
                      overlayShape: RoundSliderOverlayShape(overlayRadius: 30),
                    ),
                    child: Slider(
                      value: height.toDouble(),
                      max: 220.0,
                      min: 50.0,
                      inactiveColor: Colors.yellow,
                      activeColor: Colors.black,
                      thumbColor: Colors.red,
                      onChanged: (double newValue) {
                        setState(() {
                          height = newValue.round();
                          print(newValue);
                        });
                      },
                    ),
                  ),
                ],
              ),
            ),
          ),
          Expanded(
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                Expanded(
                  child: ReuseableCard(
                    colour: Color(0xffFF8B00),
                    cardChild: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text(
                          'Weight'.toUpperCase(),
                          style: klableTextStylet,
                        ),
                        Text(
                          weight.toString(),
                          style: kNumberTextStyle,
                        ),
                        Row(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: [
                            RoundIconButton(
                              tap: (){
                                setState(() {
                                  weight++;
                                });
                              },
                              icon: FontAwesomeIcons.plus,
                            ),
                            SizedBox(
                              width: 10,
                            ),
                            RoundIconButton(
                              tap: (){
                                setState(() {
                                  weight--;
                                });
                              },
                              icon: FontAwesomeIcons.minus,
                            ),
                          ],
                        ),
                      ],
                    ),
                  ),
                ),
                Expanded(
                  child: ReuseableCard(
                    colour: Color(0xffFF8B00),
                    cardChild: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text(
                          'Age'.toUpperCase(),
                          style: klableTextStylet,
                        ),
                        Text(
                          age.toString(),
                          style: kNumberTextStyle,
                        ),
                        Row(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: [
                            RoundIconButton(
                              tap: (){
                                setState(() {
                                  age++;
                                });
                              },
                              icon: FontAwesomeIcons.plus,
                            ),
                            SizedBox(
                              width: 10,
                            ),
                            RoundIconButton(
                              tap: (){
                                setState(() {
                                  age--;
                                });
                              },
                              icon: FontAwesomeIcons.minus,
                            ),
                          ],
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          ),
          Container(
            child: Center(
                child: Text(
              'Your BMI Calculator',
              style: TextStyle(
                color: Color(0xffFF8B00),
              ),
            )),
            decoration: BoxDecoration(
              color: Colors.black,
              borderRadius: BorderRadius.only(
                  topLeft: Radius.circular(15), topRight: Radius.circular(15)),
            ),
            width: double.infinity,
            height: kbottomContainerHeight,
            margin: EdgeInsets.only(top: 10),
          ),
        ],
      ),
    );
  }
}

效果对比

  • 预期效果:界面布局正常,体重、年龄区域的加减按钮显示正常,无任何溢出情况
  • 实际效果:界面底部出现布局溢出提示,体重/年龄区域的内容超出容器范围

问题根源与修复

核心错误

RoundIconButton中RawMaterialButton的onPressed属性传值错误:

onPressed: tap(), // 错误:立即执行tap函数,触发无限重绘

这里的tap()会在组件build时立即执行,而非作为点击回调。每次执行都会触发setState(),导致页面无限重建,Flutter布局系统在频繁重建中无法正确计算组件尺寸,最终引发布局溢出。

修复方案

  1. 将onPressed: tap()改为onPressed: tap,传递函数引用而非立即执行
  2. 优化参数类型:把tap的类型从Function改为VoidCallback,类型更严谨

修复后的RoundIconButton代码:

import 'package:flutter/material.dart';

class RoundIconButton extends StatelessWidget {
  RoundIconButton({required this.icon, required this.tap});

  final IconData icon;
  final VoidCallback tap; // 使用VoidCallback替代Function

  @override
  Widget build(BuildContext context) {
    return RawMaterialButton(
      onPressed: tap, // 传递函数引用,仅在点击时执行
      child: Icon(
        icon,
        color: Color(0xffFF8B00),
      ),
      elevation: 6.0,
      constraints: BoxConstraints.tightFor(
        width: 40.0,
        height: 47.0,
      ),
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(10),
      ),
      fillColor: Colors.black,
    );
  }
}

修复原理

修改后,只有当按钮被点击时才会执行tap函数并触发setState(),避免了无限重绘的问题,Flutter布局系统能正常计算所有组件的尺寸,布局溢出问题也就解决了。

内容的提问来源于stack exchange,提问作者Sumon Hasan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 23:20:37