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

Flutter抽屉ExpansionTile展开时底部溢出3像素问题求解

修复Flutter抽屉展开ExpansionTile时的底部溢出问题

问题原因

当前Drawer内部用Column布局,展开ExpansionTile后,总内容高度超过了Drawer的可用视口高度,而Column不支持滚动,因此触发“bottom overflowed by 3 pixels”的溢出提示。

解决方案

下面提供两种可行方案,按需选择:


方案1:保留底部固定的Logout按钮(推荐)

通过Expanded + SingleChildScrollView包裹中间的列表内容,让这部分支持滚动,同时让Logout按钮固定在抽屉底部。

修改后的完整代码:

return Scaffold(
  appBar: AppBar(
    backgroundColor: Color(0xFF781B1B),
    foregroundColor: Colors.white,
    title: Text(widget.page),
  ),
  body: PageBody(widget.page),
  drawer: SafeArea(
    child: Container(
      width: 250,
      child: Drawer(
        backgroundColor: Theme.of(context).colorScheme.onPrimary,
        child: Column(
          children: <Widget>[
            DrawerHeader(
              padding: EdgeInsets.zero,
              child: UserAccountsDrawerHeader(
                accountName: Text(
                  name,
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 18.0,
                  ),
                ),
                accountEmail: null,
                currentAccountPicture: CircleAvatar(
                  backgroundColor: Colors.white,
                  child: Icon(
                    Icons.person,
                    color: Color(0xFF781B1B),
                    size: 48.0,
                  ),
                ),
                decoration: BoxDecoration(
                  color: Color(0xFF781B1B),
                ),
              ),
            ),
            // 用Expanded让滚动区域占满头部和底部之间的空间
            Expanded(
              child: SingleChildScrollView(
                child: Column(
                  children: <Widget>[
                    ListTile(
                      leading: const Icon(Icons.account_box),
                      title: const Text('Profile'),
                      selected: _selectedIndex == 0,
                      onTap: () {
                        if (_selectedIndex == 0) return;
                        GoRouter.of(context).pop();
                        GoRouter.of(context).push("/show_profile");
                        setState(() {
                          _selectedIndex = 0;
                        });
                      },
                    ),
                    ExpansionTile(
                      leading: Icon(Icons.confirmation_num),
                      title: Text('Tickets'),
                      initiallyExpanded: _selectedIndex == 1,
                      onExpansionChanged: (bool expanded) {
                        if (expanded) {
                          setState(() {
                            _selectedIndex = 1;
                          });
                        }
                      },
                      children: <Widget>[
                        ListTile(
                          leading: Icon(Icons.confirmation_num),
                          title: Text('Choose Ticket'),
                          onTap: () {
                            GoRouter.of(context).pop();
                            GoRouter.of(context).push("/ticket_choose");
                          },
                        ),
                        ListTile(
                          leading: Icon(Icons.history),
                          title: Text('Ticket History'),
                          onTap: () {
                            GoRouter.of(context).pop();
                            GoRouter.of(context).push("/ticket_history");
                          },
                        ),
                      ],
                    ),
                    ListTile(
                      leading: const Icon(Icons.mail),
                      title: const Text('Requisition'),
                      selected: _selectedIndex == 7,
                      onTap: () {
                        if (_selectedIndex == 7) return;
                        GoRouter.of(context).pop();
                        GoRouter.of(context).push("/user_requisition");
                        setState(() {
                          _selectedIndex = 7;
                        });
                      },
                    ),
                    ListTile(
                      leading: const Icon(Icons.history),
                      title: const Text('Show Requisition'),
                      selected: _selectedIndex == 8,
                      onTap: () {
                        if (_selectedIndex == 8) return;
                        GoRouter.of(context).pop();
                        GoRouter.of(context).push("/show_requisition");
                        setState(() {
                          _selectedIndex = 8;
                        });
                      },
                    ),
                  ],
                ),
              ),
            ),
            // 直接放置Logout按钮,固定在底部
            ListTile(
              visualDensity: const VisualDensity(vertical: 4),
              leading: const Icon(Icons.logout),
              title: const Text('Log out'),
              selected: false,
              onTap: () async {
                var r1 = await Requests.post(
                    'http://18.117.93.134:6969/api/logout');
                r1.raiseForStatus();
                dynamic json1 = r1.json();
                if (json1['success']) {
                  await Requests.clearStoredCookies(
                      "18.117.93.134:6969");
                  Fluttertoast.showToast(
                      msg: 'Logged out.',
                      toastLength: Toast.LENGTH_SHORT,
                      gravity: ToastGravity.BOTTOM,
                      timeInSecForIosWeb: 1,
                      backgroundColor: Color.fromARGB(73, 77, 65, 64),
                      textColor: Colors.white,
                      fontSize: 16.0);
                  GoRouter.of(context).go("/login");
                }
              },
            ),
          ],
        ),
      ),
    ),
  ),
);

方案2:让所有内容随滚动移动

如果不需要Logout按钮固定在底部,直接把Column替换成ListView,利用ListView的滚动特性解决溢出问题:

核心修改部分:

drawer: SafeArea(
  child: Container(
    width: 250,
    child: Drawer(
      backgroundColor: Theme.of(context).colorScheme.onPrimary,
      child: ListView(
        padding: EdgeInsets.zero,
        children: <Widget>[
          DrawerHeader(...), // 原代码不变
          ListTile(...), // Profile项
          ExpansionTile(...), // Tickets项
          ListTile(...), // Requisition项
          ListTile(...), // Show Requisition项
          // 可选:添加间距让Logout靠底部
          SizedBox(height: MediaQuery.of(context).size.height - 600),
          ListTile(...), // Logout项
        ],
      ),
    ),
  ),
),

说明

  • 方案1既保留了底部固定的Logout按钮,又让中间内容支持滚动,更符合大多数场景的需求。
  • 方案2实现简单,但Logout按钮会随内容滚动,适合不需要固定底部的场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 15:34:50