Flutter中展开ExpansionTile后TextFormField消失且无法恢复的问题
解决Flutter中ExpansionTile展开后TextFormField消失的问题
问题现象
在BottomSheet的Container内使用Stack组件,Stack包含ExpansionTile和TextFormField,展开ExpansionTile后TextFormField消失且无法恢复。
原因分析
- Positioned组件误用:ExpansionTile的children属于Column布局,而Positioned仅在Stack中生效,放在Column里会导致布局约束异常,直接破坏回复输入框的渲染逻辑。
- 状态更新不及时:
isExpand变量更新时未调用setState,UI无法响应展开/收起的状态变化,导致布局刷新不彻底。 - Stack布局特性限制:Stack是层叠布局,ExpansionTile展开后高度增加,会将TextFormField挤出可视区域,且Stack不会自动调整子组件的位置适配高度变化。
解决方案
方案1:修复布局结构与状态更新
移除无效的Positioned组件,同时确保状态更新触发UI重绘:
return Column( children: [ ExpansionTile( initiallyExpanded: isExpand, onExpansionChanged: (value) { setState(() { // 新增setState触发UI更新 isExpand = value; }); }, childrenPadding: EdgeInsets.symmetric(horizontal: width * 0.04), leading: CircleAvatar( backgroundColor: Colors.yellowAccent, radius: height * 0.02, child: ClipRRect( borderRadius: BorderRadius.circular(50), child: CachedNetworkImage( fit: BoxFit.fitWidth, imageUrl: url, placeholder: (context, url) => const BlurHash( hash: "LLGj~e}sQmn\$={aLS3tRMJS2b^X8", ), )), ), title: Text(name), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: height * 0.01), Text( doc['comment'], maxLines: 3, overflow: TextOverflow.ellipsis, ), SizedBox(height: height * 0.005), Row( children: [ Text(time), TextButton( onPressed: () { setState(() { // 新增setState更新展开状态 isExpand = true; }); }, child: const Text("reply")), ], ) ], ), trailing: const SizedBox(), children: [ FirestoreListView( shrinkWrap: true, query: FirebaseFirestore.instance .collection("posts") .doc(id) .collection("comments") .doc(doc.id) .collection("replies") .orderBy("repliedAt"), emptyBuilder: (context) { return const Center(child: Text("no replies")); }, itemBuilder: (context, snap) { return StreamBuilder( stream: FirebaseFirestore.instance .collection("users") .doc(snap['repliedBy']) .snapshots(), builder: (context, snapper) { String url1 = ''; String name1 = ''; String time1 = ''; if (snapper.hasData) { url1 = snapper.data!['photoUrl']; name1 = snapper.data!['name']; Timestamp time2 = snap['repliedAt']; DateTime date = DateTime.fromMicrosecondsSinceEpoch( time2.microsecondsSinceEpoch); time1 = timeago.format(date); } return Column( children: [ ListTile( leading: CircleAvatar( backgroundColor: Colors.yellowAccent, radius: height * 0.02, child: ClipRRect( borderRadius: BorderRadius.circular(50), child: CachedNetworkImage( fit: BoxFit.fitWidth, imageUrl: url1, placeholder: (context, url) => const BlurHash( hash: "LLGj~e}sQmn\$={aLS3tRMJS2b^X8", ), )), ), title: Text(name1), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: height * 0.01), Text( snap['reply'], maxLines: 3, overflow: TextOverflow.ellipsis, ), SizedBox(height: height * 0.005), Row( children: [Text(time1)], ) ], ), ), const Divider() ], ); }); }), // 移除无效的Positioned,直接用Padding包裹输入框 Padding( padding: EdgeInsets.symmetric(horizontal: width * 0.01), child: Form( key: _formKey, child: Row( children: [ CircleAvatar( backgroundColor: Colors.yellowAccent, radius: height * 0.015, child: ClipRRect( borderRadius: BorderRadius.circular(50), child: CachedNetworkImage( fit: BoxFit.fitWidth, imageUrl: FirebaseAuth.instance.currentUser!.photoURL!, placeholder: (context, url) => const BlurHash( hash: "LLGj~e}sQmn\$={aLS3tRMJS2b^X8", ), )), ), SizedBox(width: width * 0.02), Expanded( child: ConstrainedBox( constraints: BoxConstraints(maxHeight: height * 0.03), child: TextFormField( controller: _replyController, decoration: const InputDecoration(hintText: "reply..."), ), ), ), SizedBox(width: width * 0.02), TextButton( onPressed: () { FirebaseFirestore.instance .collection("posts") .doc(id) .collection("comments") .doc(doc.id) .collection("replies") .doc() .set({ "repliedBy": FirebaseAuth.instance.currentUser!.email, "reply": _replyController.text, "repliedAt": FieldValue.serverTimestamp(), }); _replyController.clear(); FocusManager.instance.primaryFocus?.unfocus(); }, child: Text( "REPLY", style: TextStyle(letterSpacing: width * 0.002), )) ], ), ), ), ], ), const Divider() ], );
方案2:保留Stack布局的适配
如果必须使用Stack,需将TextFormField固定在底部,同时确保Stack父容器有足够高度:
Container( height: MediaQuery.of(context).size.height * 0.8, // 给Stack设置固定高度 child: Stack( children: [ ExpansionTile(...), // 展开后占据上方空间 Positioned( bottom: 16, left: 16, right: 16, child: TextFormField(...), // 固定在底部,不受ExpansionTile高度影响 ), ], ), )
内容的提问来源于stack exchange,提问作者Mohammed Bekele
相关产品推荐
相关产品推荐

