flutter SelectableRegion setState选择丢失

kd3sttzy  于 2023-03-31  发布在  Flutter
关注(0)|答案(2)|浏览(178)

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;
  }
}
ckocjqey

ckocjqey1#

有一个解决方案,那就是你将聊天气泡放在statelessstatefull中。然后在当前类中调用它,并在它之前添加const

wfsdck30

wfsdck302#

如果没有更多的代码真的很难知道,但假设你可以尝试用StatefulBuilder Package 抛出setState并将其隔离的小部件。

相关问题