一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 6 天,点击查看活动详情。
一、文字的显示
参数总览
Jetpack Compose 中的Text
,类似于Android中的TextView
,其作用是用来显示一段文字内容。它有两个构造函数:
fun Text(
text: String,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current
)
fun Text(
text: AnnotatedString,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
inlineContent: Map<String, InlineTextContent> = mapOf(),
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current
)
这两个构造函数的区别在text
参数,一个是纯粹的String
类型,另一个是AnnotatedString
类型,通过源码可以得知AnnotatedString
的本质是CharSequence
,另外,第二个构造函数中多了一个inlineContent
参数。
下面来看看部分参数的解释:
参数 | 类型 | 描述 | 可选值 |
---|---|---|---|
text | String | 要显示的文本 | |
text | AnnotatedString | 要显示的文本 | |
modifier | Modifier | 修饰符 | |
color | Color | 文本颜色 | |
fontSize | TextUnit | 在绘制文本时使用的字形大小 | 单位:sp |
fontStyle | FontStyle | 在绘制字母时使用的字体变体 | FontStyle(val value: Int) FontStyle.Normal:默认字形 FontStyle.Italic:斜体 |
fontWeight | FontWeight | 在绘制文本时使用的字体厚度 | FontWeight(val weight: Int) Thin:100 ExtraLight:200 Light:300 Normal:400 Medium:500 SemiBold:600 Bold:700 ExtraBold:800 Black:900 |
fontFamily | FontFamily | 在渲染文本时要使用的字体系列 | Default:默认字体 SansSerif:无衬线字体 Serif:有衬线字体 Monospace:等宽字体 Cursive:草书 |
letterSpacing | TextUnit | 每个字母之间添加的空间量 | 单位:sp |
textDecoration | TextDecoration | 在文本上绘制的装饰 | None:不绘制横线 Underline:下划线 LineThrough:删除线 |
textAlign | TextAlign | 段落行中文本的对齐方式 | Left:左对齐 Right:右对齐 Center:居中对齐(注意只会横向居中) Justify:感觉是两端对齐。官方解释是拉伸线条以软线断裂结束以填充容器的宽度,以硬线断裂结束的线路朝向启动边缘对齐。但我不知道为什么就是不能实现这个效果。 Start:按文字方向的起始位置对齐 End:按文字方向的结束位置对齐 |
lineHeight | TextUnit | 段落的线路高度 | 单位:sp |
overflow | TextOverflow | 文字溢出时的视觉方案 | Clip:当绘制区域不够时,直接剪切掉超出的部分 Ellipsis:当绘制区域不够时,用省略号表示 Visible:当绘制区域不够时,允许靠近边界的文字以超出边界的方式完成绘制 |
softWrap | Boolean | 文本是否应在软线断裂中断 | Boolean |
maxLines | Int | 文本的可选最大行数 | |
onTextLayout | (TextLayoutResult) -> Unit | 在计算新文本布局时执行的回调 |
参数图解
fontStyle:在绘制字母时使用的字体变体
可选值为 Normal、Italic,其他值均为无效值。
fontWeight:在绘制文本时使用的字体厚度
默认提供了100~900的9个厚度值,效果分别如下:
fontFamily:在渲染文本时要使用的字体系列
letterSpacing:每个字母之间添加的空间量
textDecoration:在文本上绘制横线
textAlign:段落行中文本的对齐方式
lineHeight:段落的线路高度(行高)
overflow:文字溢出时的视觉方案
softWrap:文本是否应在软线断裂中断
举例
显示普通的文字
除了最普通的Text(text = "Hello World")
之外,还可以显示资源中的文字Text(text = stringResource(R.string.hello_world))
。
显示一段包含多种样式的文字
先看一个列子:
Text(text = buildAnnotatedString {
append("通过AnnotatedString,可以实现给文字指定")
withStyle(style = SpanStyle(color = Color.Red)) {
append(text = "各")
}
withStyle(style = SpanStyle(color = Color.Green)) {
append(text = "种")
}
withStyle(style = SpanStyle(color = Color.Cyan)) {
append(text = "颜")
}
withStyle(style = SpanStyle(color = Color.Blue)) {
append(text = "色")
}
append("、")
withStyle(style = SpanStyle(background = Color.Yellow)) {
append(text = "背景色")
}
append("、")
withStyle(style = SpanStyle(textDecoration = TextDecoration.Underline)) {
append(text = "下划线")
}
append("、")
withStyle(style = SpanStyle(textDecoration = TextDecoration.LineThrough)) {
append(text = "删除线")
}
append("、")
withStyle(style = SpanStyle(fontSize = 18.sp)) {
append(text = "变大")
}
append("、")
withStyle(style = SpanStyle(fontSize = 10.sp)) {
append(text = "变小")
}
append("、")
withStyle(style = SpanStyle(fontWeight = FontWeight.Black)) {
append(text = "黑体")
}
append("、")
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append(text = "粗体")
}
append("、")
withStyle(style = SpanStyle(fontStyle = FontStyle.Italic)) {
append(text = "斜体")
}
append("、")
withStyle(style = SpanStyle(baselineShift = BaselineShift.Superscript)) {
append(text = "上标")
}
append("、")
withStyle(style = SpanStyle(baselineShift = BaselineShift.Subscript)) {
append(text = "下标")
}
append("、")
withStyle(style = SpanStyle(textGeometricTransform = TextGeometricTransform(scaleX = 0.5f))) {
append(text = "缩")
}
withStyle(style = SpanStyle(textGeometricTransform = TextGeometricTransform(scaleX = 2f))) {
append(text = "放")
}
append("、")
withStyle(style = SpanStyle(shadow = Shadow(color = Color.Gray, offset = Offset(x = 10f, y = 10f), blurRadius = 0.5f))) {
append(text = "阴影")
}
append("等")
})
在给一段文字中的每部分设置不同的样式时,需要用到AnnotatedString
类。
@Immutable
class AnnotatedString internal constructor(
val text: String,
val spanStyles: List<Range<SpanStyle>> = emptyList(),
val paragraphStyles: List<Range<ParagraphStyle>> = emptyList(),
internal val annotations: List<Range<out Any>> = emptyList()
) : CharSequence
从源码可以看到,AnnotatedString
它继承于CharSequence
,是一个数据类,其中包含:
- text:一个Text值。
- spanStyles:用于指定位置范围的文字样式,可应用于一组指定的字符。
- paragraphStyles:用于指定文字对齐、文字方向、行高和文字缩进样式,可应用于整个段落。
二、用户互动
选择文字
要启用文字选择功能,需要使用SelectionContainer
可组合项封装文字元素:
SelectionContainer {
Text(text = "这是一段长按后可选择的文字")
}
设置部分文字可点击
我们可以通过pushStringAnnotation
来实现此功能:
- 调用
pushStringAnnotation
,设置一个标记tag
和一个注解annotation
。 - 指定一段文字,并通过调用
pop
方法来标明前一个注解的结束位置。 - 通过
AnnotatedString
的getStringAnnotations
方法,根据tag
取出annotaion
。 - 根据
annotion
的内容执行对应的操作。
val tag = "注解的tag"
val tagText = buildAnnotatedString {
append(text = "还可以给指定的文字")
pushStringAnnotation(tag = tag, annotation = "点击事件Action")
withStyle(style = SpanStyle(color = Color.Blue)) {
append(text = "设置点击事件")
}
pop()
}
// 构造可点击文本
ClickableText(text = tagText, onClick = { index ->
// 根据tag取出annotation并打印
tagText.getStringAnnotations(tag = tag, start = index, end = index).firstOrNull()?.let { annotation ->
Toast.makeText(context, "点击了:【${annotation.item}】", Toast.LENGTH_SHORT).show()
}
})
三、文字的输入
Jetpack Compose 中主要的文字输入组件有两个,TextField
和BasicTextField
,类似于Android中的EditText
。
1、TextField
先看一个最简单的使用
val valueState = remember { mutableStateOf("这是初始化内容") }
TextField(value = valueState.value,
onValueChange = { valueState.value = it })
因为Compose遵循的是单向数据流的原则,因此要做到内容的输入,需要使用到State,仅仅是value = "这是初始化内容"
这样是没法达到输入效果的。
TextField 部分参数
参数 | 类型 | 描述 | 可选值 |
---|---|---|---|
value | String | 显示在输入框中的内容 | |
onValueChange | (String) -> Unit | 更新文本时触发的回调,回调的参数就是更新的文本 | |
modifier | Modifier | 修饰符 | |
enabled | Boolean | 启用状态 | 如果为false,文本字段既不是可编辑的,也不是可聚焦的,输入的内容将不可选择,并且视觉文本将出现在禁用的UI状态中 |
readOnly | Boolean | 可编辑状态 | 如果为true,则无法修改文本内容,但是,用户可以获取焦点并复制文本,通常用于显示用户无法编辑的预先填充的表单 |
textStyle | TextStyle | 文本样式 | 默认值为 LocalTextStyle.current |
label | @Composable(() -> Unit)? | 标签内容 | |
placeholder | @Composable (() -> Unit)? | 处于焦点并输入文本为空要显示的可选占位符 | |
leadingIcon | @Composable (() -> Unit)? | 前导图标 | 显示在组件的开头 |
trailingIcon | @Composable (() -> Unit)? | 尾随图标 | 显示在组件的末尾 |
isError | Boolean | 是否错误的标识 | 表示文本字段的当前值是否错误 |
visualTransformation | VisualTransformation | 视觉转换 | 如:使用PasswordVisualTransformation 创建密码文本 |
keyboardOptions | KeyboardOptions | 软键盘配置 | capitalization:通知键盘是否自动自动大写字符,单词或句子。 autoCorrect:通知键盘是否启用自动正确。 keyboardType:键盘类型。如文本、电话号码、邮件、密码等。 imeAction:IME动作。如下一步、前往、发送等 |
keyboardActions | KeyboardActions | 软键盘行为 | 配置软键盘不同IME动作的行为。 |
singleLine | Boolean | 是否单行 | |
maxLines | Int | 最大行数 | |
interactionSource | MutableInteractionSource | 交互的状态信息 | |
shape | Shape | 形状 | 组件的外观 |
colors | TextFieldColors | 颜色集 | 配置组件各状态下各部分的文本颜色 |
举例
- 设置了label、placeholder
- 支持切换可用状态,支持切换只读状态,支持切换为输入密码样式
- 当内容中包含"ERROR"时,输入的内容不合格
- IME按键功能为输出输入框中的内容
Column {
val valueState = remember { mutableStateOf("输入内容") }
val enabledState = remember { mutableStateOf(true) }
val readOnlyState = remember { mutableStateOf(false) }
val textStyleState = remember { mutableStateOf(TextStyle.Default) }
val isErrorState = valueState.value.contains("ERROR")
val visualTransformationState = remember { mutableStateOf(VisualTransformation.None) }
val outputValueState = remember { mutableStateOf("") }
TextField(
value = valueState.value,
onValueChange = { valueState.value = it },
enabled = enabledState.value,
readOnly = readOnlyState.value,
textStyle = textStyleState.value,
label = { Text(text = "label:包含"ERROR"为不合格内容") },
placeholder = { Text(text = "placeholder") },
leadingIcon = { Icon(painter = painterResource(id = R.drawable.ic_android_black_24dp), contentDescription = null) },
trailingIcon = {
Icon(
painter = painterResource(id = R.drawable.ic_baseline_clear_24),
contentDescription = null,
modifier = Modifier.clickable { valueState.value = "" })
},
isError = isErrorState,
visualTransformation = visualTransformationState.value,
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Characters,
autoCorrect = true,
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Go
),
keyboardActions = KeyboardActions(onGo = {
outputValueState.value = valueState.value
}),
singleLine = true,
maxLines = 1,
shape = RoundedCornerShape(8.dp),
)
Spacer(modifier = Modifier.height(4.dp))
Row {
Button(onClick = { enabledState.value = !enabledState.value }) {
Text(text = "切换可用状态:${if (enabledState.value) "可用" else "不可用"}")
}
Spacer(modifier = Modifier.width(4.dp))
Button(onClick = { readOnlyState.value = !readOnlyState.value }) {
Text(text = "切换只读状态:${if (readOnlyState.value) "只读" else "可读可写"}")
}
Spacer(modifier = Modifier.width(4.dp))
Button(onClick = {
visualTransformationState.value = if (visualTransformationState.value is PasswordVisualTransformation) {
VisualTransformation.None
} else {
PasswordVisualTransformation()
}
}) {
Text(text = "切换为${if (visualTransformationState.value is PasswordVisualTransformation) "正常" else "密码"}样式")
}
}
Spacer(modifier = Modifier.height(4.dp))
Text(buildAnnotatedString {
append("输入内容:")
if (isErrorState) {
withStyle(style = SpanStyle(color = Color.Red)) {
append("不合格")
}
} else {
withStyle(style = SpanStyle(color = Color.Green)) {
append("合格")
}
}
})
Spacer(modifier = Modifier.height(4.dp))
Text(text = "点击软键盘IME按钮输出输入框内容:${outputValueState.value}")
}
2、BasicTextField
BasicTextField
跟TextField
相比的区别就在于:
TextField | BasicTextField | |
---|---|---|
是否遵循 Material Design | ||
默认样式 | 填充样式 | 轮廓样式 |
是否提供了占位符等装饰 |
BasicTextField 部分参数
参数 | 类型 | 描述 | 说明 |
---|---|---|---|
cursorBrush | Brush | 光标颜色 | 默认为SolidColor(Color.Black) |
decorationBox | @Composable (innerTextField: @Composable () -> Unit) -> Unit | 可组合Lambda | 允许在文本字段周围添加装饰,例如图标,占位符,辅助消息或类似,并自动增加文本字段的命中目标区域。允许您控制内部文本字段相对于装饰的位置,文本的实现将通过你提供的innerTextField Lambda装饰盒框架来控制。 |