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

Flutter中Column内SingleChildScrollView滚动失效及溢出问题

问题解决:Flutter中SingleChildScrollView无法滚动及类型转换错误

问题描述

  • flex值为1的Expanded组件内的SingleChildScrollView可正常滚动,但flex值为2的Expanded组件中,由createButtons(answerList)生成按钮列表的SingleChildScrollView无法滚动,当答案条目过多时页面底部溢出203像素。
  • 同时出现类型转换错误:type 'Null' is not a subtype of type 'FutureOr<int>'

代码示例

import 'package:flutter/material.dart';

import '../../backend/QuestionaireConstants/question.dart';
import '../../backend/utils/constants.dart';
import '../../backend/utils/setupcomingRoute.dart';
import '../../backend/widgets/button_content.dart';
import '../../backend/widgets/reusable_cart.dart';

int selectedAnswer = -1;
int counter = 0;

Color determineColor(int selection) {
  return selectedAnswer == selection ? Colors.white : Colors.black;
}

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

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

class _SelectionWindowState extends State<SelectionWindow> {
  @override
  Widget build(BuildContext context) {
    final args = ModalRoute.of(context)!.settings.arguments as List<Question>;
    int count = args[0].counter;
    int arraylen = args.length;
    List answerList = args[count].possibleAnswers;

    String upcomingRoute = setUpcomingRoute(count, arraylen, args);

    List<Widget> createButtons(list) {
      List<Widget> buttons = [];

      // 创建单选按钮
      for (var i = 0; i < list.length; i++) {
        buttons.add(
          Row(
            children: [
              Expanded(
                child: GestureDetector(
                  onTap: () {
                    setState(() {
                      selectedAnswer = i;
                    });
                  },
                  child: ReusableCard(
                    colour: selectedAnswer == i
                        ? Colors.blue.shade900
                        : Colors.blue.shade200,
                    cardChild: ButtonContent(
                      label: args[count].possibleAnswers[i],
                      textColour: determineColor(i),
                    ),
                  ),
                ),
              ),
            ],
          ),
        );
      }

      return buttons;
    }

    // 显示错误弹窗
    Future<void> _showMyDialog() async {
      return showDialog<void>(
        context: context,
        barrierDismissible: false, // 用户必须点击按钮关闭
        builder: (BuildContext context) {
          return AlertDialog(
            title: const Text('提示'),
            content: SingleChildScrollView(
              child: ListBody(
                children: const <Widget>[
                  Text('请先选择一个答案再继续。'),
                ],
              ),
            ),
            actions: <Widget>[
              TextButton(
                child: const Text(
                  '确认',
                  style: TextStyle(fontSize: 22),
                ),
                onPressed: () {
                  Navigator.of(context).pop();
                },
              ),
            ],
          );
        },
      );
    }

    return Scaffold(
      appBar: AppBar(
        iconTheme: backwardsArrowBlack,
        automaticallyImplyLeading: false,
        title: appBarText,
        backgroundColor: Colors.blue.shade100,
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Expanded(
            flex: 1,
            child: Container(
              alignment: Alignment.center,
              child: SingleChildScrollView(
                scrollDirection: Axis.vertical,
                child: Text(args[count].questionText,
                    style: multipleChoiceTextStyle),
              ),
            ),
          ),
          Expanded(
            flex: 2,
            child: Column(
              children: [
                SingleChildScrollView(
                  scrollDirection: Axis.vertical,
                  child: Column(
                    children: createButtons(answerList),
                  ),
                ),
                Expanded(
                  child: Align(
                    alignment: FractionalOffset.bottomCenter,
                    child: Row(
                      crossAxisAlignment: CrossAxisAlignment.end,
                      children: [
                        Expanded(
                          child: GestureDetector(
                            onTap: () {
                              if (args[0].counter > 0) {
                                args[0].counter -= 1;
                                args[count - 1].givenAnswers.clear();
                              }
                              Navigator.pop(context);
                            },
                            child: backwardsButton,
                          ),
                        ),
                        Expanded(
                          child: GestureDetector(
                            onTap: () {
                              if (selectedAnswer == -1) {
                                _showMyDialog();
                              } else {
                                args[count].givenAnswers.add(args[count]
                                    .possibleAnswers[selectedAnswer]);
                                selectedAnswer = -1;
                                if (args[0].counter == arraylen - 1) {
                                  args[0].counter += 1;
                                  Navigator.pushNamed(context, '/endScreen',
                                          arguments: args)
                                      .then((_) => setState(() {}));
                                } else {
                                  args[0].counter += 1;
                                  Navigator.pushNamed(
                                    context,
                                    upcomingRoute,
                                    arguments: args,
                                  ).then((_) => setState(() {}));
                                }
                              }
                            },
                            child: forwardButton,
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

错误日志

D/EGL_emulation(15191): app_time_stats: avg=10.35ms min=0.79ms max=89.84ms count=52
D/EGL_emulation(15191): app_time_stats: avg=104.68ms min=0.77ms max=1043.27ms count=11
D/EGL_emulation(15191): app_time_stats: avg=69.44ms min=11.30ms max=1017.83ms count=19
I/flutter (15191): type 'Null' is not a subtype of type 'FutureOr<int>'
D/EGL_emulation(15191): app_time_stats: avg=151.10ms min=0.54ms max=925.70ms count=12

======== Exception caught by rendering library =====================================================
The following assertion was thrown during layout:
A RenderFlex overflowed by 203 pixels on the bottom.

解决方案

1. 修复SingleChildScrollView无法滚动及溢出问题

问题根源在于flex=2的Expanded内部的Column中,SingleChildScrollView没有被分配固定的可用空间,导致它会试图占据内容的全部高度,而非在剩余空间内滚动。

修改方式:给SingleChildScrollView外层包裹Expanded组件,让它占据Column中除底部按钮栏外的所有剩余空间:

Expanded(
  flex: 2,
  child: Column(
    children: [
      // 新增Expanded包裹SingleChildScrollView
      Expanded(
        child: SingleChildScrollView(
          scrollDirection: Axis.vertical,
          child: Column(
            children: createButtons(answerList),
          ),
        ),
      ),
      Expanded(
        child: Align(
          alignment: FractionalOffset.bottomCenter,
          child: Row(
            // 按钮代码保持不变
          ),
        ),
      ),
    ],
  ),
),

2. 修复类型转换错误

错误type 'Null' is not a subtype of type 'FutureOr<int>'是因为Navigator.pushNamed的返回值被隐式期望为int类型,但实际返回了null。

修改方式:明确指定返回值类型为dynamic,或者确保目标路由返回正确的int值。例如修改then回调的参数类型:

Navigator.pushNamed(context, '/endScreen', arguments: args)
    .then((dynamic _) => setState(() {}));

以及:

Navigator.pushNamed(
  context,
  upcomingRoute,
  arguments: args,
).then((dynamic _) => setState(() {}));

另外,检查_showMyDialog中的Navigator.of(context).pop();,如果该对话框被期望返回int值,需要改为Navigator.of(context).pop(0);(或其他合法int值),否则保持当前写法即可。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 23:50:28