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

Flutter中如何让BottomSheet高度自适应内部PageView的子项内容?

核心问题

你当前的实现存在两个直接导致高度无法自适应的问题:

  • 给包裹PageView的Container写死了height: 600的固定高度
  • PageView默认不支持自适应子组件高度,需要手动计算每个页面的实际渲染高度,在切换页面时动态调整
解决方案

我们通过动态计算每个页面高度、监听页面切换更新高度的方式实现需求,同时增加最大高度限制避免内容过长溢出屏幕:

  1. 把组件改为StatefulWidget,维护当前页面高度状态与PageController监听
  2. 给每个PageView的子页面根节点绑定GlobalKey,用于获取布局完成后的实际高度
  3. 页面首次渲染完成后获取初始页高度,切换页面时同步更新当前高度
  4. 移除原有的固定高度配置,替换为动态高度+最大高度限制
完整实现代码
import 'package:flutter/material.dart';

class Test extends StatefulWidget {
  @override
  State<Test> createState() => _TestState();
}

class _TestState extends State<Test> {
  final PageController controller = PageController(initialPage: 0);
  // 存储每个页面的GlobalKey
  final List<GlobalKey> pageKeys = [GlobalKey(), GlobalKey(), GlobalKey()];
  // 当前页面高度
  double? currentPageHeight;
  // 底部弹窗最大高度限制(屏幕高度的80%,可自行调整)
  late double maxSheetHeight;

  @override
  void initState() {
    super.initState();
    // 监听页面切换,更新高度
    controller.addListener(() {
      if (controller.page?.round() == controller.page) {
        _updatePageHeight(controller.page!.toInt());
      }
    });
  }

  @override
  void didChangeDependencies() {
    maxSheetHeight = MediaQuery.of(context).size.height * 0.8;
    super.didChangeDependencies();
  }

  // 更新指定页面的高度
  void _updatePageHeight(int index) {
    final renderBox = pageKeys[index].currentContext?.findRenderObject() as RenderBox?;
    if (renderBox != null) {
      setState(() {
        currentPageHeight = renderBox.size.height;
      });
    }
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        padding: const EdgeInsets.symmetric(horizontal: 20),
        child: Center(
          child: ElevatedButton(
            child: const Text('BottomSheet'),
            onPressed: () {
              showModalBottomSheet(
                isScrollControlled: true,
                context: context,
                backgroundColor: Colors.white,
                shape: const RoundedRectangleBorder(
                  borderRadius: BorderRadiusDirectional.only(
                    topEnd: Radius.circular(25),
                    topStart: Radius.circular(25),
                  ),
                ),
                builder: (context) => Container(
                  padding: const EdgeInsetsDirectional.only(
                    start: 20,
                    end: 20,
                    bottom: 30,
                    top: 8,
                  ),
                  child: Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      // 移除固定高度,改为动态高度限制
                      Container(
                        constraints: BoxConstraints(
                          maxHeight: maxSheetHeight,
                          minHeight: currentPageHeight ?? 0,
                        ),
                        child: PageView(
                          controller: controller,
                          children: <Widget>[
                            SingleChildScrollView(
                              child: Column(
                                key: pageKeys[0],
                                mainAxisAlignment: MainAxisAlignment.start,
                                crossAxisAlignment: CrossAxisAlignment.center,
                                mainAxisSize: MainAxisSize.min,
                                children: List.generate(
                                  5,
                                  (index) => Container(
                                    height: 60,
                                    margin: const EdgeInsetsDirectional.only(bottom: 10),
                                    width: double.infinity,
                                    color: Colors.yellow,
                                    child: Center(
                                      child: Text(
                                        'Widgets $index',
                                        style: const TextStyle(fontSize: 20),
                                      ),
                                    ),
                                  ),
                                ),
                              ),
                            ),
                            SingleChildScrollView(
                              child: Column(
                                key: pageKeys[1],
                                mainAxisAlignment: MainAxisAlignment.start,
                                crossAxisAlignment: CrossAxisAlignment.center,
                                mainAxisSize: MainAxisSize.min,
                                children: List.generate(
                                  10, (index) => Container(
                                  height: 60,
                                  margin:
                                  const EdgeInsetsDirectional.only(bottom: 10),
                                  width: double.infinity,
                                  color: Colors.yellow,
                                  child: Center(
                                    child: Text(
                                      'Widgets $index',
                                      style: const TextStyle(fontSize: 20),
                                    ),
                                  ),
                                ),),
                              ),
                            ),
                            SingleChildScrollView(
                              child: Column(
                                key: pageKeys[2],
                                mainAxisAlignment: MainAxisAlignment.start,
                                crossAxisAlignment: CrossAxisAlignment.center,
                                mainAxisSize: MainAxisSize.min,
                                children: List.generate(
                                    12, (index) => Container(
                                  height: 60,
                                  margin:
                                  const EdgeInsetsDirectional.only(bottom: 10),
                                  width: double.infinity,
                                  color: Colors.yellow,
                                  child: Center(
                                    child: Text(
                                      'Widgets $index',
                                      style: const TextStyle(fontSize: 20),
                                    ),
                                  ),
                                ),),
                              ),
                            ),
                          ],
                        ),
                      ),
                    ],
                  ),
                ),
              ).then((_) {
                // 关闭弹窗后重置高度状态,避免下次打开出现高度异常
                setState(() {
                  currentPageHeight = null;
                });
              });
              // 弹窗首次渲染完成后获取初始页高度
              WidgetsBinding.instance.addPostFrameCallback((_) {
                _updatePageHeight(0);
              });
            },
          ),
        ),
      ),
    );
  }
}
关键说明
  • 动态获取的高度已经包含了子组件的margin、padding等间距,不需要额外计算
  • 最大高度限制可根据需求自行调整,内容超过最大高度时SingleChildScrollView会自动开启滚动,不会溢出屏幕
  • 页面切换时高度会自动平滑适配对应页面的内容高度

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 18:18:02