
各位 Compose 爱好者们,早上好,今天我们来看一个 Compose 的新特性,SelectionState API ------ 可以主动选择文本的能力。
我在之前的一篇文章破坏性更新 - 解读 Jetpack Compose 1.12中,提到过这个变更。
以往的 Compose 中,实际上已经有一个可以实现选择的控件 ------ SelectionContainer,但它更像是一个纯粹的交互容器:把文本放进去,用户就可以长按、拖动来选择内容。
如果你想在代码中主动做点事情------例如点击按钮后全选、清除当前选择,或者把已选文本同步到界面其他位置------可用的入口并不多。
Compose 1.12.0 新增的 SelectionState,就是用来解决这个问题的。
有了它,选择状态可以被提升出来,供外部观察、双向控制,而不再只是容器内部的临时交互状态。
SelectionState 是什么
SelectionState 用来保存某个 SelectionContainer 的选择状态。它既能让我们拿到当前选中的文本,也提供了一组方法,用于在代码中修改选择范围。
在 Composable 中,通常通过 rememberSelectionState() 创建:
kotlin
val selectionState = rememberSelectionState()
然后把它交给对应的 SelectionContainer:
kotlin
SelectionContainer(state = selectionState) {
Text(
"可以通过代码主动选择的文本内容。"
)
}
这里有一个需要注意的约束:
同一个
SelectionState不能同时传给多个SelectionContainer。
它描述的是某一个容器的选择状态,而不是全局的选择管理器。多个容器需要独立选择时,就应分别创建各自的 SelectionState。
另外,rememberSelectionState() 会自动处理状态保存与恢复;如果是更复杂的状态管理场景,也可以配合 SelectionState.Saver 和 rememberSaveable 使用。
主动控制选择范围
SelectionState 提供了四个直接的控制方法。
全选
selectAll() 会选择容器中全部可选择的 Text 内容。
kotlin
Button(onClick = { selectionState.selectAll() }) {
Text("全选")
}
需要留意的是,如果把 SelectionContainer 放在 LazyColumn 这类懒布局的外层,selectAll() 只能选择当前已经进入 Composition 的文本,并不会自动把未组合的列表项也选中。
清除选择
clear() 会清除当前选择,同时移除选择手柄。
kotlin
Button(onClick = { selectionState.clear() }) {
Text("清除选择")
}
指定范围选择
select(TextRange) 可以选中指定范围的文本:
kotlin
Button(onClick = { selectionState.select(TextRange(0, 4)) }) {
Text("选择指定范围")
}
这里的 TextRange 是相对于整个 SelectionContainer 内全部 Text 的全局文本位置 ,而不是某一个单独 Text 的局部下标。
这意味着,即使容器内有多个 Text,也可以用一个范围跨越它们进行选择。
按单词扩展
extendSelectionByWord() 会把当前选择扩展到下一个单词边界。
kotlin
Button(onClick = { selectionState.extendSelectionByWord() }) {
Text("按单词扩展")
}
它的行为有两个细节:
- 如果当前没有选择内容,会先选中容器中的第一个单词;
- 如果下一个单词位于另一个
Text中,选择会继续扩展到下一个Text。
这里的"单词边界"遵循 Unicode 的文本分词规则,因此空格、标点等非单词字符也会参与边界判断。若用户从右向左反向选择,扩展方向也会跟随当前选择的活动端,而不是始终向后增加内容。

读取当前选中的文本
控制选择只是第一步,更实用的是我们终于可以直接观察"用户到底选了什么"。
selectionState.selectedTexts 会返回当前被选中的文本:
kotlin
val selectedTexts: List<AnnotatedString> = selectionState.selectedTexts
返回值是 List<AnnotatedString>,并且按照界面中的视觉/布局顺序排列。容器中每个参与选择的 Text,都会作为一个 AnnotatedString 出现在这个列表里。
这个属性由 mutableStateOf 支撑,因此它本身就是可观察的 Compose State。选择范围变化后,读取它的 Composable 会自动重组:
kotlin
Text(text = "当前选中:${selectionState.selectedTexts}")
实际项目里,一般会把它转换成你真正需要的形式,例如用于展示已选字符数、复制内容预览,或者驱动一组只在存在选择时才显示的操作按钮。
完整示例
下面这个示例把前面的几个能力放在一起:全选、清除、按范围选择、按单词扩展,以及把当前选择结果直接展示在界面上。
kotlin
@Composable
fun ProgrammaticSelectionExample() {
val selectionState = rememberSelectionState()
Column {
Row {
Button(
onClick = { selectionState.selectAll() },
) {
Text("全选")
}
Button(
onClick = { selectionState.clear() },
) {
Text("清除选择")
}
}
Row {
Button(
onClick = { selectionState.select(TextRange(0, 4)) },
) {
Text("选择指定范围")
}
Button(
onClick = { selectionState.extendSelectionByWord() },
) {
Text("按单词扩展")
}
}
SelectionContainer(state = selectionState) {
Text(
"可以通过代码主动选择的文本内容。",
style = MaterialTheme.typography.headlineLarge
)
}
Text(text = "当前选中:${selectionState.selectedTexts}")
}
}
原文中的演示效果如下:


总结
SelectionState 本身不是大的 API 变更,但它让 SelectionContainer 的选择除了靠用户手势,也能由代码主动驱动。
当你的页面需要实现选中文本的计数、复制前预览、批量操作,或者给文本选择提供额外快捷操作时,就不必再绕过 Compose 的选择机制,自己维护一套选择范围了。
它的使用方式也很符合 Compose 一贯的思路:把状态创建在合适的位置,上提给需要读取和操作它的组件;容器负责渲染与手势,外层则根据状态作出响应。