Flutter 解决ExpansionTile展开动画时标题透出子组件背景色问题
Flutter ExpansionTile嵌套展开时子组件背景溢出问题解决
问题现象
使用ExpansionPanelList嵌套ExpansionTile实现多层级展开列表时,点击ExpansionTile头部触发展开动画过程中,子组件设置的Color(0xffffeed4)背景色会向上透出,覆盖当前ExpansionTile标题栏、上方相邻列表项区域,视觉效果异常。
问题复现核心代码如下:
class Temp extends StatefulWidget { const Temp({Key? key}) : super(key: key); @override State<Temp> createState() => _TempState(); } class Item { Item(bool exp) { this.isExpanded = exp; } late bool isExpanded; } List<Item> items = [Item(false), Item(true), Item(true), Item(false)]; class _TempState extends State<Temp> { @override Widget build(BuildContext context) { return DefaultTabController( length: 4, child: Scaffold( body: SingleChildScrollView( child: Container( child: ExpansionPanelList( expandedHeaderPadding: EdgeInsets.zero, expansionCallback: (int index, bool isExpanded) { setState(() { items[index].isExpanded = !isExpanded; }); }, children: [ ExpansionPanel( headerBuilder: (BuildContext context, bool isExpanded) { return ListTile( title: Text("Test", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700)), leading: Icon(Icons.info), minLeadingWidth : 4, contentPadding: EdgeInsets.symmetric(vertical: 0.0, horizontal: 16.0), dense:true, ); }, body: Column( children: [ ListTile( title: Text("Test 1", style: TextStyle(fontSize: 16)), leading: Icon(Icons.handshake_outlined), minLeadingWidth : 4, contentPadding: EdgeInsets.symmetric(vertical: 0.0, horizontal: 16.0), dense:true, ), ExpansionTile( title: const Text('Test 2', style: TextStyle(color: Colors.black87),), leading: Icon(Icons.handshake_outlined, color: Colors.black54,), trailing: Column(), children: <Widget>[ ListTile( dense: true, tileColor: Color(0xffffeed4), title: Text('Foo', style: TextStyle (fontSize: 16),), trailing: SizedBox( child: Text("foo", style: TextStyle (fontSize: 16),) ), ), ListTile( tileColor: Color(0xffffeed4), title: Text('Bar', style: TextStyle (fontSize: 16),), dense: true, trailing: SizedBox( child: Text("bar", style: TextStyle (fontSize: 16),) ), ), Container( height: 10, color: Color(0xffffeed4), ), Container( color: Color(0xffffeed4), child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ TextButton( style: TextButton.styleFrom( backgroundColor: Colors.indigo, shape: CircleBorder(), ), child: Icon( Icons.copy, color: Colors.white, ), onPressed: () {}, ), TextButton( style: TextButton.styleFrom( backgroundColor: Colors.indigo, shape: CircleBorder(), ), child: Icon( Icons.delete_rounded, color: Colors.white, ), onPressed: () {}, ), TextButton( style: TextButton.styleFrom( backgroundColor: Colors.indigo, shape: CircleBorder(), ), child: Icon( Icons.info_outline, color: Colors.white, ), onPressed: () {}, ), ], ), ), Container( height: 10, color: Color(0xffffeed4), ) ], onExpansionChanged: (bool expanded) { //setState(() => _customTileExpanded = expanded); }, ), ], ), isExpanded: items[1].isExpanded, canTapOnHeader: true ), ], ), ), ), floatingActionButton: FloatingActionButton( onPressed: null, tooltip: 'Increment', child: const Icon(Icons.add), ), ) ); } }
问题原因
ExpansionTile默认的裁剪行为是Clip.none,展开动画执行过程中,子组件的布局区域会临时超出ExpansionTile自身的组件边界;同时上层相邻组件没有设置不透明背景,无法遮挡越界显示的子组件内容,最终出现背景色透出覆盖上层组件的问题。
解决方法
核心修复:给ExpansionTile添加矩形裁剪
直接用ClipRect组件包裹ExpansionTile,强制裁剪所有超出组件边界的内容,动画过程中子组件不会越界显示,从根源解决背景透出问题,改动量极小。
替换原有ExpansionTile代码如下:
ClipRect( child: ExpansionTile( title: const Text('Test 2', style: TextStyle(color: Colors.black87),), leading: Icon(Icons.handshake_outlined, color: Colors.black54,), trailing: Column(), children: <Widget>[ // ExpansionTile原有子组件代码保持不变即可 ListTile( dense: true, tileColor: Color(0xffffeed4), title: Text('Foo', style: TextStyle (fontSize: 16),), trailing: SizedBox( child: Text("foo", style: TextStyle (fontSize: 16),) ), ), ListTile( tileColor: Color(0xffffeed4), title: Text('Bar', style: TextStyle (fontSize: 16),), dense: true, trailing: SizedBox( child: Text("bar", style: TextStyle (fontSize: 16),) ), ), Container( height: 10, color: Color(0xffffeed4), ), Container( color: Color(0xffffeed4), child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ TextButton( style: TextButton.styleFrom( backgroundColor: Colors.indigo, shape: CircleBorder(), ), child: Icon( Icons.copy, color: Colors.white, ), onPressed: () {}, ), TextButton( style: TextButton.styleFrom( backgroundColor: Colors.indigo, shape: CircleBorder(), ), child: Icon( Icons.delete_rounded, color: Colors.white, ), onPressed: () {}, ), TextButton( style: TextButton.styleFrom( backgroundColor: Colors.indigo, shape: CircleBorder(), ), child: Icon( Icons.info_outline, color: Colors.white, ), onPressed: () {}, ), ], ), ), Container( height: 10, color: Color(0xffffeed4), ) ], onExpansionChanged: (bool expanded) {}, ), )
优化补充:给列表容器设置统一不透明背景
为了避免后续其他组件动画出现类似透出问题,可以给ExpansionPanel的body外层容器设置和页面背景一致的不透明颜色,双重保障显示效果:
body: Container( color: Theme.of(context).scaffoldBackgroundColor, // 自动适配当前主题的页面背景色 child: Column( children: [ // 原有Test1列表项、包裹了ClipRect的ExpansionTile代码保持不变 ], ), )
不建议单独使用背景遮挡方案,硬编码背景色会导致主题切换时出现颜色不匹配问题,和裁剪方案配合使用效果最优。
内容的提问来源于stack exchange,提问作者user5182503
相关产品推荐
相关产品推荐

