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

Flutter仿Notion折叠列表问题求助:按钮隐藏与子项联动

Flutter 可折叠多级列表问题解决

问题说明

  • 关闭Header 1后,Subheader 1不可见但仍可点击,且Header 1与Header 2间距过大;仅当Subheader 1不是TextButton时正常。
  • 关闭Header 1时,其下所有子Header未同步关闭,需实现父Header折叠时自动收起所有子项。

修复后的完整代码

import 'package:flutter/material.dart';

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

  @override
  State<JWMGvereinfacht> createState() => _JWMGvereinfachtState();
}

class _JWMGvereinfachtState extends State<JWMGvereinfacht> {
  bool _isAnwendungsbereichVisible = false;
  bool _is1AnwendungsbereichVisible = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[900],
      body: Scrollbar(
        child: ListView(
          children: [
            // 顶部导航栏
            Container(
              color: Colors.grey[850],
              width: double.infinity,
              height: 76,
              child: Padding(
                padding: const EdgeInsets.fromLTRB(8, 0, 0, 0),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Row(
                      mainAxisAlignment: MainAxisAlignment.start,
                      children: [
                        IconButton(
                          icon: const Icon(
                            Icons.arrow_back_ios_new,
                            color: Colors.white,
                            size: 25,
                          ),
                          onPressed: () => Navigator.pop(context),
                        ),
                        const Expanded(
                          child: Padding(
                            padding: EdgeInsets.all(8.0),
                            child: Text(
                              "Titel",
                              style: TextStyle(
                                  color: Colors.white,
                                  fontSize: 25,
                                  fontWeight: FontWeight.bold),
                              textAlign: TextAlign.end,
                            ),
                          ),
                        ),
                      ],
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 5),
            // Header 1
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 8.0),
              child: TextButton(
                style: TextButton.styleFrom(
                  padding: EdgeInsets.zero,
                  minimumSize: Size.zero,
                  tapTargetSize: MaterialTapTargetSize.shrinkWrap,
                ),
                onPressed: () {
                  setState(() {
                    _isAnwendungsbereichVisible = !_isAnwendungsbereichVisible;
                    // 折叠父Header时同步关闭子Header
                    if (!_isAnwendungsbereichVisible) {
                      _is1AnwendungsbereichVisible = false;
                    }
                  });
                },
                child: Container(
                  color: Colors.grey[850],
                  width: double.infinity,
                  height: 35,
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.start,
                    children: [
                      Icon(
                        _isAnwendungsbereichVisible
                            ? Icons.arrow_drop_down_outlined
                            : Icons.arrow_right,
                        color: Colors.white,
                      ),
                      const Expanded(
                        child: Padding(
                          padding: EdgeInsets.all(8.0),
                          child: Text(
                            "Header 1",
                            style: TextStyle(
                              color: Colors.white,
                              fontSize: 17,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
            // 仅当父Header展开时显示子项区域
            if (_isAnwendungsbereichVisible)
              Column(
                children: [
                  // Subheader 1
                  Padding(
                    padding: const EdgeInsets.fromLTRB(25.5, 8, 8, 0),
                    child: TextButton(
                      style: TextButton.styleFrom(
                        padding: EdgeInsets.zero,
                        minimumSize: Size.zero,
                        tapTargetSize: MaterialTapTargetSize.shrinkWrap,
                      ),
                      onPressed: () {
                        setState(() {
                          _is1AnwendungsbereichVisible = !_is1AnwendungsbereichVisible;
                        });
                      },
                      child: Container(
                        color: Colors.blue,
                        width: double.infinity,
                        height: 35,
                        child: const Padding(
                          padding: EdgeInsets.all(8.0),
                          child: Text(
                            "Subheader 1",
                            style: TextStyle(
                              color: Colors.white,
                              fontSize: 15.5,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                        ),
                      ),
                    ),
                  ),
                  // Subheader 1的内容
                  if (_is1AnwendungsbereichVisible)
                    Padding(
                      padding: const EdgeInsets.fromLTRB(43, 8, 8, 8),
                      child: TextButton(
                        style: TextButton.styleFrom(
                          padding: EdgeInsets.zero,
                          minimumSize: Size.zero,
                          tapTargetSize: MaterialTapTargetSize.shrinkWrap,
                        ),
                        onPressed: () {},
                        child: Container(
                          color: Colors.blue,
                          width: double.infinity,
                          height: 35,
                          child: const Padding(
                            padding: EdgeInsets.all(8.0),
                            child: Text(
                              "Inhalt",
                              style: TextStyle(
                                color: Colors.white,
                                fontSize: 15.5,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                          ),
                        ),
                      ),
                    ),
                ],
              ),
            // 其他Header
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Container(
                color: Colors.grey[850],
                width: double.infinity,
                height: 35,
                child: const Padding(
                  padding: EdgeInsets.all(8.0),
                  child: Text("Header 2",
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 17,
                        fontWeight: FontWeight.bold,
                      )),
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Container(
                color: Colors.grey[850],
                width: double.infinity,
                height: 35,
                child: const Padding(
                  padding: EdgeInsets.all(8.0),
                  child: Text("Beteiligung Dritter an der Jagd (§17-§25)",
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 17,
                        fontWeight: FontWeight.bold,
                      )),
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Container(
                color: Colors.grey[850],
                width: double.infinity,
                height: 35,
                child: const Padding(
                  padding: EdgeInsets.all(7.0),
                  child: Text("Jagdschein (§26-§28)",
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 17,
                        fontWeight: FontWeight.bold,
                      )),
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Container(
                color: Colors.grey[850],
                width: double.infinity,
                height: 57,
                child: const Padding(
                  padding: EdgeInsets.all(8.0),
                  child: Text(
                      "Besondere Rechte und Pflichten bei der Jagdausübung (§29-§40)",
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 17,
                        fontWeight: FontWeight.bold,
                      )),
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Container(
                color: Colors.grey[850],
                width: double.infinity,
                height: 35,
                child: const Padding(
                  padding: EdgeInsets.all(8.0),
                  child: Text(
                      "Sicherung der Nachhaltigkeit, Wildtierschutz (§41-§51)",
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 17,
                        fontWeight: FontWeight.bold,
                      )),
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Container(
                color: Colors.grey[850],
                width: double.infinity,
                height: 35,
                child: const Padding(
                  padding: EdgeInsets.all(8.0),
                  child: Text("Wild- und Jagdschaden (§51a-§57)",
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 17,
                        fontWeight: FontWeight.bold,
                      )),
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Container(
                color: Colors.grey[850],
                width: double.infinity,
                height: 35,
                child: const Padding(
                  padding: EdgeInsets.all(8.0),
                  child: Text("Verwaltungsbehörden, Beiräte (§58-§65)",
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 17,
                        fontWeight: FontWeight.bold,
                      )),
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Container(
                color: Colors.grey[850],
                width: double.infinity,
                height: 35,
                child: const Padding(
                  padding: EdgeInsets.all(8.0),
                  child: Text("Straf- und Bußgeldvorschriften (§66-§69)",
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 17,
                        fontWeight: FontWeight.bold,
                      )),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

修改要点说明

1. 解决Subheader隐藏后仍可点击、间距过大问题

  • 替换Visibility组件为条件渲染(if语句),直接从列表中移除子项,彻底消除TextButton的点击区域和空白间距。
  • 给所有TextButton添加样式配置,清除默认内边距和最小尺寸,避免额外空白:
    style: TextButton.styleFrom(
      padding: EdgeInsets.zero,
      minimumSize: Size.zero,
      tapTargetSize: MaterialTapTargetSize.shrinkWrap,
    )
    
  • 调整子项Padding值,保持缩进的同时统一所有Header间距为8.0。

2. 实现父Header折叠时同步关闭子Header

在Header 1的点击事件中,折叠父项时强制将子项的展开状态设为false:

onPressed: () {
  setState(() {
    _isAnwendungsbereichVisible = !_isAnwendungsbereichVisible;
    if (!_isAnwendungsbereichVisible) {
      _is1AnwendungsbereichVisible = false;
    }
  });
},

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 04:32:33