You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SelectableRegion调用setState后选中状态丢失问题排查与修复咨询

问题说明

实现了一个包含文本组件列表的SelectableRegion,每秒调用一次setState()更新列表。当TextWidget中保留SelectionContainer.disabled时,文本选中状态会在setState()后丢失;移除该组件则选中状态能正常保留。

原因分析

SelectionContainer.disabled的作用是创建一个独立的、禁用文本选择的上下文环境,它会完全阻断父级SelectableRegion的选择上下文传递。每次setState()触发Widget树重建时,带有SelectionContainer.disabled的子组件会重新生成这个独立上下文,导致原来的选中状态关联的元素上下文被重置,无法与新重建的组件匹配,最终选中状态丢失。

而移除SelectionContainer.disabled后,子文本组件共享父级SelectableRegion的选择上下文,重建时组件的选择关联能被正确保留,所以选中状态不会丢失。

修复方案

不需要用SelectionContainer.disabled来实现文本不可选,直接通过TextSpan的样式设置userSelect: TextSelection.none即可。这种方式不会阻断父级选择上下文,同时能让目标文本无法被选中,且setState()后选中状态能正常保留。

修改后的代码示例
import 'dart:async';
import 'package:flutter/material.dart';
import 'dart:math' as math;

class SelectionTest extends StatefulWidget {
  const SelectionTest({Key? key}) : super(key: key);

  @override
  State<SelectionTest> createState() => _SelectionTestState();
}

class _SelectionTestState extends State<SelectionTest> {
  final _selectableRegionFocusNode = FocusNode();
  late final Timer _timer;
  var count = 1;

  @override
  void initState() {
    super.initState();
    _timer = Timer.periodic(const Duration(milliseconds: 1500), (timer) {
      count++;
      setState(() {});
    });
  }

  @override
  dispose() {
    _selectableRegionFocusNode.dispose();
    _timer.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SelectableRegion(
      selectionControls: MaterialTextSelectionControls(), // 替换为你项目中的自定义控件即可
      focusNode: _selectableRegionFocusNode,
      child: Container(
        width: 200,
        height: 300,
        color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt())
            .withOpacity(1.0),
        child: ListView.builder(
          itemBuilder: (context, index) => TextWidget(key: ValueKey(index)),
          itemCount: count,
        ),
      ),
    );
  }
}

class TextWidget extends StatelessWidget {
  const TextWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final textSpans = <InlineSpan>[
      const TextSpan(text: 'Hey 123 456 789'),
      TextSpan(
        text: '-> No issue now <-',
        style: Theme.of(context)
            .textTheme
            .bodyMedium!
            .copyWith(
              color: Colors.transparent,
              userSelect: TextSelection.none, // 通过样式设置文本不可选
            ),
      ),
    ];

    return Text.rich(TextSpan(children: textSpans));
  }
}

内容的提问来源于stack exchange,提问作者user123

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 13:57:27