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
相关产品推荐
相关产品推荐

