如何在Flutter中实现Figma中的展开式连接器设计?
自定义ExpansionTile连接器样式,还原Figma设计
核心思路
ExpansionTile默认的展开/收起箭头没法直接实现你要的两种连接器样式,核心是自定义trailing组件,根据展开状态切换绘制逻辑,用CustomPaint能精准复刻设计细节。
实现代码示例
1. 自定义连接器组件
写一个可根据展开状态切换样式的组件,用CustomPaint绘制两种状态的线条和箭头:
class CustomConnector extends StatelessWidget { final bool isExpanded; const CustomConnector({super.key, required this.isExpanded}); @override Widget build(BuildContext context) { return SizedBox( width: 24, height: 24, child: CustomPaint( painter: _ConnectorPainter(isExpanded: isExpanded), ), ); } } class _ConnectorPainter extends CustomPainter { final bool isExpanded; final Paint _paint = Paint() ..color = Colors.grey.shade600 ..strokeWidth = 2 ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round; _ConnectorPainter({required this.isExpanded}); @override void paint(Canvas canvas, Size size) { final centerX = size.width / 2; final centerY = size.height / 2; if (!isExpanded) { // 未点击状态:横线+向右箭头 canvas.drawLine(Offset(0, centerY), Offset(centerX - 4, centerY), _paint); canvas.drawLine(Offset(centerX - 4, centerY), Offset(centerX, centerY - 4), _paint); canvas.drawLine(Offset(centerX - 4, centerY), Offset(centerX, centerY + 4), _paint); } else { // 已点击状态:竖线+向下箭头 canvas.drawLine(Offset(centerX, 0), Offset(centerX, centerY - 4), _paint); canvas.drawLine(Offset(centerX, centerY - 4), Offset(centerX - 4, centerY), _paint); canvas.drawLine(Offset(centerX, centerY - 4), Offset(centerX + 4, centerY), _paint); } } @override bool shouldRepaint(covariant _ConnectorPainter oldDelegate) { return oldDelegate.isExpanded != isExpanded; } }
2. 替换ExpansionTile的trailing
把自定义组件放到ExpansionTile里,绑定展开状态:
// 用StatefulWidget管理展开状态 class CustomExpansionTile extends StatefulWidget { const CustomExpansionTile({super.key}); @override State<CustomExpansionTile> createState() => _CustomExpansionTileState(); } class _CustomExpansionTileState extends State<CustomExpansionTile> { bool _isExpanded = false; @override Widget build(BuildContext context) { return ExpansionTile( title: const Text("示例标题"), trailing: CustomConnector(isExpanded: _isExpanded), onExpansionChanged: (expanded) { setState(() => _isExpanded = expanded); }, children: const [ Padding( padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Text("展开后的内容区域"), ), ], ); } }
3. 细节调整
- 要完全匹配Figma设计,直接修改
_paint里的color、strokeWidth参数即可 - 要是需要点击反馈,可给CustomConnector套一层
GestureDetector(不过ExpansionTile本身已经有点击事件) - 若设计有渐变、圆角等特殊效果,直接在CustomPaint的paint方法里补充绘制逻辑
简易替代方案
如果不想用CustomPaint,也可以用现成Icon组合(灵活性稍差):
- 未展开状态:
Row(children: [Icon(Icons.minor_crash, size: 16), Icon(Icons.arrow_right, size: 16)]) - 展开状态:
Column(children: [Icon(Icons.minor_crash, size: 16), Icon(Icons.arrow_drop_down, size: 16)])
内容的提问来源于stack exchange,提问作者Alvaro Dosio
相关产品推荐
相关产品推荐

