bounty将在3天后过期。回答此问题可获得+50声望奖励。user123希望引起更多人关注此问题。
我有一个'SelectableRegion'小部件 Package 了一堆聊天消息小部件,这些小部件使用'Text.rich'渲染文本。问题是,每次页面重建时,由于'setState',突出显示的文本选择(图片中的橙子突出显示,上面的工具栏)立即丢失。我无法将'SelectableRegion'从效果'setState'中分离出来,我找不到任何解决方案。有任何想法如何解决这个问题?
这里的代码是为了说明将文本选择与setstate隔离是很困难的,所以我正在寻找一种方法来使文本选择状态持久化,就像有状态的小部件一样。下面是从最外面到最里面的简化代码:
环聊屏幕:
class HangoutScreen extends StatefulWidget {
const HangoutScreen({
Key? key,
required this.hangoutId,
}) : super(key: key);
}
class _HangoutScreenState extends State<HangoutScreen> {
StreamSubscription<DocumentSnapshot<Map<String, dynamic>>>?
_hangoutSubscription;
Hangout? _hangout;
@override
void initState() {
super.initState();
_hangout = Provider.of<JoinedOpenHangoutsRepository>(
context,
listen: false,
).hangouts.firstWhereOrNull((element) => element.id == widget.hangoutId);
if (widget.hangoutId.isNotEmpty) {
_hangoutSubscription = FirebaseFirestore.instance
.doc(FirePath.hangout(widget.hangoutId))
.snapshots()
.listen((snapshot) {
_hangout = snapshot.exists ? Hangout.fromMap(snapshot.data()!) : null;
if (mounted) {
setState(() {});
}
});
}
}
@override
Widget build(BuildContext context) {
final hangout = _hangout;
if (hangout == null || hangout.state == HangoutState.deleted) {
return TextScaffold(AppLocalizations.of(context)!.hangout_not_found);
}
Widget content;
if (hangout.state == HangoutState.pre_open) {
//content = Center(...
} else {
content = Messenger(
containerType: MessagesContainerType.hangout,
containerId: hangout.id,
displaySenderAvatar: true,
userPreviews: hangout.userPreviews,
canDirectMessageUser: (userId) => ...
);
}
return Scaffold(
appBar: AppBar(...,
body: Column(
children: [
if (showJoinRequests) JoinRequestPanel(hangout),
Expanded(child: PortraitBody(isSafeArea: false, child: content)),
],
),
);
}
}
内在信使:
class Messenger extends StatefulWidget {
Messenger({
Key? key,
required this.containerType,
required this.containerId,
required this.displaySenderAvatar,
required this.userPreviews,
required this.messagePathGetter,
required this.isInteractable,
List<MessageComposerAction>? composerActions,
}) : composerActions = composerActions ?? [],
super(key: key) {
}
}
class _MessengerState extends State<Messenger> {
@override
void initState() {
super.initState();
_messageRepository = MessageRepository(
messagesRootPath: widget.messagesPath,
);
_messageRepositorySubscription = _messageRepository.stream.listen((state) {
setState(() {});
});
}
@override
Widget build(BuildContext context) {
if (_messageRepository.state == MessageRepositoryState.error) {
return Center(//stuff),
);
}
return Column(
children: [
Expanded(
child: _messageRepository.state ==
MessageRepositoryState.initializing
? const LoadingPanel(delay: loadingPanelVisibilityDelay)
: BlocProvider.value(
value: _messageRepository,
child: MessagePanel(
textFocusNode: _composerFocusNode,
scrollController: _scrollController,
containerId: widget.containerId,
displaySenderAvatar: widget.displaySenderAvatar,
replyHandler: widget.isInteractable ? _setReply : null,
selectHandler: _selectHandler,
toggleLikeHandler: ...,
canDirectMessageUser: widget.canDirectMessageUser,
),
),
),
if (widget.isInteractable)
Padding(
padding: messageComposerEdgeInsets,
child: MessageComposer(),
),
],
);
}
}
内部消息面板,此处SelectableRegion在构建版本中找到:
class MessagePanel extends StatefulWidget {
const MessagePanel({
Key? key,
required this.containerId,
required this.displaySenderAvatar,
required this.textFocusNode,
required this.scrollController,
this.replyHandler,
this.selectHandler,
this.toggleLikeHandler,
this.canDirectMessageUser,
}) : super(key: key);
}
class _MessagePanelState extends State<MessagePanel> {
var _topDescendingEntries = <Object>[];
var _bottomAscendingEntries = <Object>[];
final _centerKey = const ValueKey('bottom_list');
late final MessageRepository _messageRepository;
late final StreamSubscription<List<Message>> _messagesUpdateSubscription;
double? _unfocusScrollPos;
var _noMessagesFound = false;
final _messageTextSelectCubit = MessageTextSelectCubit();
final _selectableRegionFocusNode = FocusNode();
@override
void initState() {
super.initState();
_messageRepository = Provider.of<MessageRepository>(context, listen: false);
_messagesUpdateSubscription =
_messageRepository.updateStream.listen((event) => _refresh());
WidgetsBinding.instance.addPostFrameCallback((timeStamp) => _refresh());
}
@override
Widget build(BuildContext context) {
if (_noMessagesFound) {
return Center(
child: Text(
AppLocalizations.of(context)!.messages_not_found,
),
);
}
return Stack(
children: [
BlocProvider.value(
value: _messageTextSelectCubit,
child: SelectableRegion(
selectionControls: MapTextSelectionControls(),
focusNode: _selectableRegionFocusNode,
child: CustomScrollView(
center: _centerKey,
controller: widget.scrollController,
physics: ChatScrollPhysics(scrollDownThreshold: scrollDownAppear),
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => _createListEntry(
entry: _topDescendingEntries[index],
previousEntry: index == _topDescendingEntries.length - 1
? null
: _topDescendingEntries[index + 1],
nextEntry: index == 0
? (_bottomAscendingEntries.isNotEmpty
? _bottomAscendingEntries[0]
: null)
: _topDescendingEntries[index - 1],
),
childCount: _topDescendingEntries.length,
),
),
SliverList(
key: _centerKey,
delegate: SliverChildBuilderDelegate(
(context, index) => _createListEntry(
entry: _bottomAscendingEntries[index],
previousEntry: index == 0
? (_topDescendingEntries.isNotEmpty
? _topDescendingEntries[0]
: null)
: _bottomAscendingEntries[index - 1],
nextEntry: index == _bottomAscendingEntries.length - 1
? null
: _bottomAscendingEntries[index + 1],
),
childCount: _bottomAscendingEntries.length,
),
),
],
),
),
),
],
);
}
void _refresh() {
final topMessages = _messageRepository.topMessages.toList();
final bottomMessages = _messageRepository.bottomMessages.toList();
if (topMessages.isNotEmpty || bottomMessages.isNotEmpty) {
_topDescendingEntries = topMessages.reversed.cast<Object>().toList();
_bottomAscendingEntries = bottomMessages.cast<Object>().toList();
setState(() {});
}
Widget _createListEntry({
required Object entry,
required dynamic previousEntry,
required dynamic nextEntry,
}) {
return UserMessageWidget()...;
}
}
最后是最内层的消息小部件:
class UserMessageWidget extends StatefulWidget {
const UserMessageWidget({
Key? key,
required this.message,
required this.isAvatarVisible,
required this.isFirstMessage,
required this.isLastMessage,
this.replyHandler,
this.selectHandler,
this.toggleLikeHandler,
this.canDirectMessageUser = true,
}) : super(key: key);
}
class _UserMessageWidgetState extends State<UserMessageWidget>
with TickerProviderStateMixin {
//variables here
@override
Widget build(BuildContext context) {
Widget bubble = _createBubble();
if (!widget.message.isDeleted && widget.message.reactions.isNotEmpty) {
//modify bubble
}
return GestureDetector(
behavior: HitTestBehavior.translucent,
child: bubble,
);
}
Widget _createBubble() {
final textSpans = <InlineSpan>[];
textSpans.add(TextSpan(text: widget.message.text));
Widget textChild = Text.rich(TextSpan(children: textSpans));
if (widget.message.isDeleted) {
textChild = SelectionContainer.disabled(child: textChild);
}
return textChild;
}
}
2条答案
按热度按时间ckocjqey1#
有一个解决方案,那就是你将聊天气泡放在
stateless
或statefull
类中。然后在当前类中调用它,并在它之前添加const
。wfsdck302#
如果没有更多的代码真的很难知道,但假设你可以尝试用StatefulBuilder Package 抛出setState并将其隔离的小部件。