Flutter聊天气泡Read more/Read less功能无法动态调整大小问题
解决聊天气泡Read More/Read Less后无法动态调整大小的问题
问题核心原因
showFullText变量定义在_buildChatBubble方法内部,每次Widget重建都会被重置为false,setState无法持久化状态- Text组件未根据
showFullText状态切换显示内容,始终固定显示截断文本 - 状态变化后未触发聊天气泡容器的自适应重绘
解决方案
将聊天气泡封装为独立的StatefulWidget,让每个气泡拥有自己的展开状态,同时修改文本显示逻辑以响应状态变化。
修改后的完整代码
独立聊天气泡组件
import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; class ChatBubble extends StatefulWidget { final String text; final bool isQuestion; final ThemeProvider themeProvider; final Function(String) onCopy; final Function(String) onSpeak; const ChatBubble({ super.key, required this.text, required this.isQuestion, required this.themeProvider, required this.onCopy, required this.onSpeak, }); @override State<ChatBubble> createState() => _ChatBubbleState(); } class _ChatBubbleState extends State<ChatBubble> { final double borderRadiusValue = 20.0; final int maxTextLengthToShow = 200; late bool isTextTooLong; bool showFullText = false; @override void initState() { super.initState(); isTextTooLong = widget.text.length > maxTextLengthToShow; } @override Widget build(BuildContext context) { final String avatarImage = widget.themeProvider.isIconSelected ? 'images/chatbot_icon4.jpg' : 'images/chatbot_icon4.jpg'; if (!widget.isQuestion) { return Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [ SizedBox(height: MediaQuery.of(context).size.height * 0.01), Container( margin: const EdgeInsets.symmetric(vertical: 5.0), alignment: Alignment.centerLeft, child: Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [ Padding( padding: const EdgeInsets.only(left: 12.0, right: 5.0), child: CircleAvatar( radius: 20, backgroundImage: AssetImage(avatarImage), backgroundColor: Colors.transparent, ), ), SizedBox( width: MediaQuery.of(context).size.width * 0.7, child: Container( constraints: const BoxConstraints( maxWidth: 230.0, ), padding: const EdgeInsets.all(12.0), decoration: BoxDecoration( color: widget.themeProvider.themeData.colorScheme.onError, borderRadius: BorderRadius.only( topLeft: Radius.circular(borderRadiusValue), bottomRight: Radius.circular(borderRadiusValue), topRight: Radius.circular(borderRadiusValue), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( showFullText || !isTextTooLong ? widget.text : '${widget.text.substring(0, maxTextLengthToShow)}...', style: const TextStyle( color: CustomColor.whiteColor, fontSize: 16.0, ), ), if (isTextTooLong) Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () { setState(() { showFullText = !showFullText; }); }, child: Text( showFullText ? 'Read Less' : 'Read More', style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, ), ), ), IconButton( onPressed: () { widget.onCopy(widget.text); }, icon: const Icon( size: 20.0, Icons.content_copy_rounded, color: Colors.white, ), ), IconButton( onPressed: () { widget.onSpeak(widget.text); }, icon: const Icon( size: 20.0, Icons.volume_up_sharp, color: Colors.white, ), ), ], ), ], ), ), ), ], ), ), Row( children: [ SizedBox(width: MediaQuery.of(context).size.width * 0.12), Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0), child: Text( DateFormat.yMd().add_jm().format(DateTime.now()), style: const TextStyle( fontWeight: FontWeight.normal, color: Colors.black, fontSize: 12.0, ), ), ), ], ), SizedBox(height: MediaQuery.of(context).size.height * 0.02), ], ); } else { return Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ SizedBox(height: MediaQuery.of(context).size.height * 0.01), Container( margin: const EdgeInsets.symmetric(vertical: 5.0), alignment: Alignment.centerRight, child: Row( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 10.0), child: SizedBox( width: MediaQuery.of(context).size.width * 0.75, child: Container( constraints: const BoxConstraints( maxWidth: 230.0, ), padding: const EdgeInsets.all(12.0), decoration: BoxDecoration( color: widget.themeProvider.themeData.colorScheme.onBackground, borderRadius: BorderRadius.only( topRight: Radius.circular(borderRadiusValue), bottomRight: Radius.circular(borderRadiusValue * 0.1), bottomLeft: Radius.circular(borderRadiusValue), topLeft: Radius.circular(borderRadiusValue), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.text, style: TextStyle( color: widget.themeProvider.themeData.colorScheme.onPrimary, fontSize: 16.0, ), ), ], ), ), ), ), ], ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0), child: Text( DateFormat.yMd().add_jm().format(DateTime.now()), style: const TextStyle( fontWeight: FontWeight.normal, color: Colors.black, fontSize: 12.0, ), ), ), ], ); } } }
调用聊天气泡组件
// 在原页面的build方法中替换原_buildChatBubble调用 Consumer<ThemeProvider>( builder: (context, themeProvider, _) { return ChatBubble( text: yourText, // 传入聊天文本 isQuestion: isQuestion, // 标记是否为用户提问 themeProvider: themeProvider, onCopy: _copyToClipboard, // 原页面的复制方法 onSpeak: _speak, // 原页面的朗读方法 ); }, )
关键修改说明
- 将聊天气泡拆分为独立
StatefulWidget,每个气泡维护自己的showFullText状态,避免状态重置 - 在
initState中初始化isTextTooLong,减少重复计算 - Text组件根据
showFullText状态动态切换显示完整/截断文本 - 通过回调函数传递复制、朗读方法,保持组件逻辑解耦
内容的提问来源于stack exchange,提问作者Alex Aaron
相关产品推荐
相关产品推荐

