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

Flutter分步表单问题:设置步骤编号后表单内容无法正常显示

Flutter分步表单内容不显示问题修复方案

问题描述

开发Flutter分步验证表单时遇到以下问题:

  • 期望步骤1展示「既往病史」单选按钮组,选择Yes/No后点击Next切换到步骤2展示「既往手术史」问题
  • 设置步骤编号判断后,表单内容无法正常显示,仅能看到操作按钮
  • 移除步骤编号判断逻辑后,两个表单内容会显示在同一页面

问题排查与修复

1. 核心问题:Next按钮未触发步骤切换

_validateFormfieldAndMoveToNext函数中缺少更新_stepno的逻辑,点击Next后不会切换到下一个步骤,导致表单内容始终不更新。同时验证逻辑的条件判断错误,应该在当前步骤完成后再切换步骤。

2. Previous按钮逻辑错误

_previous函数中,当_stepno == 2时没有执行步骤回退操作,导致无法从步骤2回到步骤1。

3. 大小写不一致导致验证失效

Radio按钮的value是小写的"yes"/"no",但验证时用了大写的"No",导致判断条件不成立。

4. 进度条显示逻辑冗余

进度条的判断条件过于复杂,可简化逻辑,让所有步骤都显示进度条。


修正后的完整代码

override
Widget build(BuildContext context) {
  return Scaffold(
    backgroundColor: Colors.white,
    body: SingleChildScrollView(
      child: Column(children: [
        _isLoading
            ? const LinearProgressIndicator(
                backgroundColor: DefaultTheme.accentColor,
                valueColor: AlwaysStoppedAnimation(DefaultTheme.primaryColor),
                minHeight: 3,
              )
            : Container(),
        Container(
          height: 52.0,
          decoration: const BoxDecoration(
            border: Border(
              bottom: BorderSide(
                color: DefaultTheme.borderColor,
                width: 1,
              ),
            ),
          ),
          child: const TopBar(pageTitle: "Verification Form"),
        ),
        Container(
          width: MediaQuery.of(context).size.width > 800
              ? 700
              : MediaQuery.of(context).size.width,
          padding: const EdgeInsets.all(20),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.start,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // 简化进度条显示逻辑:所有步骤都显示进度条
              Container(
                margin: const EdgeInsets.only(bottom: 20, top: 20),
                child: LinearPercentIndicator(
                  padding: const EdgeInsets.symmetric(horizontal: 0.0),
                  lineHeight: 20.0,
                  animationDuration: 3000,
                  percent: _stepno / 2, // 总步骤2,直接用当前步骤/总步骤计算进度
                  animateFromLastPercent: true,
                  center: Text(
                    "$_stepno/2",
                    style: const TextStyle(color: Colors.white),
                  ),
                  barRadius: const Radius.circular(5),
                  backgroundColor: Colors.grey,
                  progressColor: Colors.black,
                ),
              ),
              _stepno == 1
                  ? Column(
                      mainAxisAlignment: MainAxisAlignment.start,
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        const Text(
                          "Past medical history: ",
                          style: TextStyle(
                              fontWeight: FontWeight.bold,
                              fontSize: 14,
                              height: 1.5),
                        ),
                        RadioListTile(
                          title: const Text("Yes"),
                          value: "yes",
                          groupValue: _medicalhistory,
                          onChanged: (value) {
                            setState(() {
                              _medicalhistory = value!;
                            });
                          },
                        ),
                        if (_medicalhistory == "yes") ...[
                          const Text(
                              "If yes, please share and describe the previous medical illness: "),
                          const SizedBox(height: 5),
                          SizedBox(
                            height: 60,
                            child: TextField(
                              controller: _medicalillness,
                              maxLength: 1000,
                              decoration: const InputDecoration(
                                border: OutlineInputBorder(),
                                contentPadding: EdgeInsets.only(left: 10),
                              ),
                              maxLines: 1,
                            ),
                          ),
                          Row(
                            mainAxisAlignment: MainAxisAlignment.end,
                            crossAxisAlignment: CrossAxisAlignment.end,
                            children: [
                              TextButton(
                                onPressed: () {
                                  setState(() {
                                    // 保存输入内容,可根据需求调整逻辑
                                  });
                                },
                                child: const Text("Save"),
                              ),
                            ],
                          ),
                        ],
                        RadioListTile(
                          title: const Text("No"),
                          value: "no",
                          groupValue: _medicalhistory,
                          onChanged: (value) {
                            setState(() {
                              _medicalhistory = value!;
                            });
                          },
                        ),
                      ],
                    )
                  : const SizedBox.shrink(), // 替代固定高度的SizedBox,减少空白
              _stepno == 2
                  ? Column(
                      mainAxisAlignment: MainAxisAlignment.start,
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        const Text(
                          "Past surgical history: ",
                          style: TextStyle(
                              fontWeight: FontWeight.bold,
                              fontSize: 14,
                              height: 1.5),
                        ),
                        RadioListTile(
                          title: const Text("Yes"),
                          value: "yes",
                          groupValue: _surgicalhistory,
                          onChanged: (value) {
                            setState(() {
                              _surgicalhistory = value!;
                            });
                          },
                        ),
                        if (_surgicalhistory == "yes") ...[
                          const Text(
                              "If yes, please share and describe the previous surgical illness: "),
                          const SizedBox(height: 5),
                          SizedBox(
                            height: 60,
                            child: TextField(
                              controller: _surgicalillness,
                              maxLength: 1000,
                              decoration: const InputDecoration(
                                border: OutlineInputBorder(),
                                contentPadding: EdgeInsets.only(left: 10),
                              ),
                              maxLines: 1,
                            ),
                          ),
                          Row(
                            mainAxisAlignment: MainAxisAlignment.end,
                            crossAxisAlignment: CrossAxisAlignment.end,
                            children: [
                              TextButton(
                                onPressed: () {
                                  setState(() {
                                    // 保存输入内容,可根据需求调整逻辑
                                  });
                                },
                                child: const Text("Save"),
                              ),
                            ],
                          ),
                        ],
                        RadioListTile(
                          title: const Text("No"),
                          value: "no",
                          groupValue: _surgicalhistory,
                          onChanged: (value) {
                            setState(() {
                              _surgicalhistory = value!;
                            });
                          },
                        ),
                      ],
                    )
                  : const SizedBox.shrink(),
            ],
          ),
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.end,
          crossAxisAlignment: CrossAxisAlignment.end,
          children: [
            Expanded(
              child: _stepno > 1
                  ? Container(
                      width: double.infinity,
                      padding: const EdgeInsets.only(left: 20, right: 20),
                      height: 45,
                      decoration: const BoxDecoration(
                        color: AppConst.primaryColor,
                        borderRadius: BorderRadius.all(
                          Radius.circular(5.0),
                        ),
                      ),
                      child: Center(
                        child: InkWell(
                          onTap: () {
                            _previous();
                          },
                          child: const Text(
                            "Previous",
                            maxLines: 1,
                            overflow: TextOverflow.ellipsis,
                            style: TextStyle(
                                fontSize: 16,
                                color: Colors.white,
                                fontWeight: FontWeight.w600),
                          ),
                        ),
                      ),
                    )
                  : const SizedBox.shrink(),
            ),
            const SizedBox(width: 10),
            Expanded(
              child: Container(
                width: double.infinity,
                padding: const EdgeInsets.only(left: 20, right: 20),
                height: 45,
                decoration: const BoxDecoration(
                  color: AppConst.primaryColor,
                  borderRadius: BorderRadius.all(
                    Radius.circular(5.0),
                  ),
                ),
                child: Center(
                  child: InkWell(
                    onTap: () {
                      if (_stepno < 2) {
                        _validateFormfieldAndMoveToNext();
                      }
                    },
                    child: const Text(
                      "Next",
                      maxLines: 1,
                      overflow: TextOverflow.ellipsis,
                      style: TextStyle(
                          fontSize: 16,
                          color: Colors.white,
                          fontWeight: FontWeight.w600),
                    ),
                  ),
                ),
              ),
            )
          ],
        ),
      ]),
    ),
  );
}

_previous() {
  if (_stepno > 1) {
    setState(() {
      _stepno--;
      // 更新进度百分比,总步骤2,进度为当前步骤/2
      _completionPercentage = _stepno / 2;
    });
  }
}

_validateFormfieldAndMoveToNext() async {
  // 步骤1验证:必须选择Yes或No
  if (_stepno == 1) {
    if (_medicalhistory == null || _medicalhistory.isEmpty) {
      // 可添加提示,比如Toast
      return;
    }
    // 验证通过,切换到步骤2
    setState(() {
      _stepno = 2;
      _completionPercentage = _stepno / 2;
    });
  }
  // 步骤2可添加提交逻辑
}

关键修改点说明

  1. 步骤切换逻辑:在_validateFormfieldAndMoveToNext中添加setState更新_stepno,验证通过后切换到下一个步骤。
  2. Previous按钮修复:简化回退逻辑,只要当前步骤大于1就执行回退,并同步更新进度。
  3. 验证逻辑修正:针对当前步骤做验证,步骤1检查_medicalhistory是否已选择,避免大小写判断错误。
  4. 布局优化:用SizedBox.shrink()替代固定高度的SizedBox,消除不必要的空白区域。
  5. 进度条简化:直接用_stepno / 2计算进度百分比,逻辑更直观。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 15:24:57