公众号「稀有猿诉」
文本是所有UI系统中非常重要的一个种元素,文本的输入在UI框架中的重要性也特别的高,因为这是最重要的一种用户输入。今天专注于文本的输入处理,包括文本输入框,以及文本的选择和富式点击处理。
文本输入
Compose提供了符合Material Design的文本输入TextField,默认的实现是全填充的:
Kotlin
@Composable
fun SimpleFilledTextFieldSample() {
var text by remember { mutableStateOf("Hello") }
TextField(
value = text,
onValueChange = { text = it },
label = { Text("Label") }
)
}
还有一个边框式的OutlinedTextField:
Kotlin
@Composable
fun SimpleOutlinedTextFieldSample() {
var text by remember { mutableStateOf("") }
OutlinedTextField(
value = text,
onValueChange = { text = it },
label = { Text("Label") }
)
}
可以看到TextField函数最关键的有三个参数:文本框中的显示的文本text,文本变化回调onValueChange,提示标签label。需要注意传给text的变量要是状态(State),这样才会触发重组,否则TextField显示的文本不会发生变化。
定制TextField
可以通过其他的参数来控制输入框的行为,最为常用的就是行数限制singleLine和maxLines, 以及文本的样式控制textStyle,它可以控制文本颜色和字体:
Kotlin
@Composable
fun StyledTextField() {
var value by remember { mutableStateOf("Hello\nWorld\nInvisible") }
TextField(
value = value,
onValueChange = { value = it },
label = { Text("Enter text") },
maxLines = 2,
textStyle = TextStyle(color = Color.Blue, fontWeight = FontWeight.Bold),
modifier = Modifier.padding(20.dp)
)
}
textStyle比较丰富,除了直接指定颜色以外,还可以用Brush API,以实现一些颜色渐变,渐变效果是针对整个输入框的,换言之不同的行效果是一样的:
Kotlin
var text by remember { mutableStateOf("") }
val brush = remember {
Brush.linearGradient(
colors = listOf(Color.RED, Color.YELLOW, Color.GREEN, Color.BLUE)
)
}
TextField(
value = text, onValueChange = { text = it }, textStyle = TextStyle(brush = brush)
)
与键盘联动
TextField能够配置软件盘以实现特定输入样式,比如只输入数字,只有英文字符等等,通过TextField的keyboardOptions参数,传入一个KeyboardOptions对象。常用的配置项有:
- capitalization 对于有大小写的语言来说,输入时词首自动大写
- autoCorrect 自动更正
- keyboardType 键盘类型,如纯字符,纯数字,E-mail地址,网址等等
- imeAction 让键盘的显示输入完成后的行为相关的按扭,比如『搜索(Search)』,『发送(Send)』等
当输入完成后,用户点了imeAction指定的按扭后,可以指定回调函数以执行相关的操作,通过keyboardActions参数指定一个KeyboardActions对象,里面可以指定对应于imeAction中的各种回调,如onSearch会在imeAction指定为Search时,用户点击后触发;onSend会在imeAction是Send时,用户点击触发,等等。
特殊形式的输入
有些特殊的场景是不能够直接把用户的输入文本直接的展现在框里,比如输入密码时,再比如像输入电话号码时,可能会自动在3个数字后面加上短横线。这时就需要用到VisualTransformation来对文本进行转换处理:
Kotlin
@Composable
fun PasswordTextField() {
var password by rememberSaveable { mutableStateOf("") }
TextField(
value = password,
onValueChange = { password = it },
label = { Text("Enter password") },
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
)
}
文本状态管理
TextField的文本(text参数)是需要转换成状态的,这样才能更好的触发重组。基础的通用的TextField使用方式是,把文本转成状态,塞给TextField,然后在其onValueChange中再更新此状态:
Kotlin
// ...
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
)
// ...
但现实的代码不可能这么简单,用户的输入必然会有业务逻辑去处理,所以onValueChange肯定会调用ViewModel去处理用户输入。那么自然也要从ViewModel处获得。但由于TextField的特殊性,仍然要把使用MutableState来定义状态,而不能用响应式的Reactive stream或者StateFlow:
Kotlin
class SignUpViewModel(private val userRepository: UserRepository) : ViewModel() {
var username by mutableStateOf("")
private set
fun updateUsername(input: String) {
username = input
}
}
// SignUpScreen.kt
@Composable
fun SignUpScreen(/*...*/) {
OutlinedTextField(
value = viewModel.username,
onValueChange = { username -> viewModel.updateUsername(username) }
/*...*/
)
}
文本的选择
除了文本输入以外,文本显示的选择也视为文字编辑的一种方式,因为选择之后就可以执行复制或者搜索等全局操作。Compose提供了细粒度的可交互式文本显示控制。Text本身是不支持选择的(Not Selectable),自然也就无法复制。可以使用SelectableContainer来包裹Text以实现可选择(Selectable):
Kotlin
@Composable
fun SelectableText() {
SelectionContainer {
Text("This text is selectable")
}
}
并且,可选择区域可以跨多个Text。与之相对的,还有不可选择函数DisableSelection,比如一大片可选择文本中,想让某一小块文本不能被选择,这时DisableSelection就派上用场了:
Kotlin
@Composable
fun PartiallySelectableText() {
SelectionContainer {
Column {
Text("This text is selectable")
Text("This one too")
Text("This one as well")
DisableSelection {
Text("But not this one")
Text("Neither this one")
}
Text("But again, you can select this one")
Text("And this one too")
}
}
}
可以看出对于文本的选择控制还是相当的灵活的(flexible)。
富式文本点击
对于针对 整个文本的点击事件可以用Modifier中的clickable函数来处理,这跟常规的Composable没区别都一样的。但对于文本来说有更为细腻的点击事件处理,包括获取具体点击的光标位置,以及富式文本点击,也即针对 文本中不同部分的响应。
获取点击的光标位置
想要获取到文本中点击的光标位置,其实也就是点击的是第几个字符,可以用ClickableText,它有一个自己的onClick回调函数,里面的参数是一个offset表示被点击字符的索引:
Kotlin
@Composable
fun SimpleClickableText() {
ClickableText(text = AnnotatedString("Click Me"), onClick = { offset ->
Log.d("ClickableText", "$offset -th character is clicked.")
})
}
注意onClick的参数是文本字符串的索引,从0开始。这个索引一般用来确定点击的富文本中的某一个标记(Annotation)。
富文本的点击处理
Text是支持富文本的(基于AnnotatedString)。通过ClickableText中onClick的索引参数,就能知道点击的具体是哪个Annotation。比如一个超链接标记,具体的URL对用户是不可见的,作为额外的Tag信息在Annotation中,通过索引判断当点击到了超链接上面时,可以跳转到此URL:
Kotlin
@Composable
fun AnnotatedClickableText() {
val annotatedText = buildAnnotatedString {
append("Click ")
// We attach this *URL* annotation to the following content
// until `pop()` is called
pushStringAnnotation(
tag = "URL", annotation = "https://developer.android.com"
)
withStyle(
style = SpanStyle(
color = Color.Blue, fontWeight = FontWeight.Bold
)
) {
append("here")
}
pop()
}
ClickableText(text = annotatedText, onClick = { offset ->
// We check if there is an *URL* annotation attached to the text
// at the clicked position
annotatedText.getStringAnnotations(
tag = "URL", start = offset, end = offset
).firstOrNull()?.let { annotation ->
// If yes, we log its value
Log.d("Clicked URL", annotation.item)
}
})
}
总结
本文介绍了两种最常规的文本编辑,一是文本输入,一个是文本的选择和点击,这些都是日常项目开发中的非常常见的需求。Jetpack Compose对文本的操作提供了非常友好的支持,能够应付绝大部分的需求场景。
References
欢迎搜索并关注 公众号「稀有猿诉」 获取更多的优质文章!
保护原创,请勿转载!