文本是UI界面中最常见的元素之一,在Compose中,文字组件扮演着重要的角色,文字组件是遵循Material Design规范设计的上层组件,如果我们不想使用Material Design,我们也可以直接使用更底层的文本组件,如Text组件对应的更底层的文本组件是BasicText,文本组件不得不提输入框的使用,本文会主要介绍Text和输入框,并且实现一个现在App中都在用的好看的输入框
因为Composable组件都是函数,所有的配置都来自于参数的传递,所以我们通过参数列表就可以了解组件的所有功能,Text组件的参数列表如下:
@Composable
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,// 控制文本是否能够换行,如果为false,则会定位
maxLines: Int = Int.MAX_VALUE,// 文本最多的展示行数
onTextLayout: (TextLayoutResult) -> Unit = {},// 在文本发生变化之后,会回调一个TextLayoutResult,包含此文本的各种信息
style: TextStyle = LocalTextStyle.current // 文本的风格配置,如颜色,字体,行高等
)
Text组件的参数会按照其使用的频度排序,比如text和modifier排在靠前的位置,并尽量添加默认实现。
Text组件的基本功能是显示一段文字,可以为text参数传入要显示的文字内容,Compose也提供了stringResource方法,通过R 资源文件获取字符串,方便做多语言适配
// 指定字符串
Text(text = "Hello World")
// 指定文字资源
Text(text = stringResource(id = R.string.app_name))
style参数接受一个TextStyle类型,TextStyle中包含了一系列设置文字样式的字段,例如行高,间距,字体大小,字体粗细等,演示代码如下所示:
@Composable
fun TextStyleDemo() {
Column {
Text(
text = "Zhongxj",
style = TextStyle(
fontSize = 25.sp,
fontWeight = FontWeight.Bold,
background = Color.Cyan,
lineHeight = 35.sp
)
)
Text(
text = stringResource(id = R.string.app_name), style = TextStyle(
color = Color.Cyan,
letterSpacing = 4.sp
)
)
Text(
text = "Hello world", style = TextStyle(
textDecoration = TextDecoration.LineThrough
)
)
Text(
text = "Walt-zhong",
style = MaterialTheme.typography.h6.copy(fontStyle = FontStyle.Italic)
)
}
}
运行结果如下所示:
代码中的TextDecoration可以为文字增加删除线或下划线,FontStyle可以用来设置文字是否是斜体
maxLines参数可以帮助我们将文本限制在指定行数之间,当文本超过了参数设置的阈值时,文本会被截断,而overflow可以处理文字过多的场景,文字过多的时候会以“…”结尾,这个参数可以用来实现一个阅读文字时的展开和收起功能,当我们显示很多条数据时,每一条数据都有很多的文字,这时我们可以使用这个参数,默认只展示指定的行数,提供一个展开按钮,当用户点击展开的时候,就显示所有的文本。展示收起按钮,点击收起按钮又可以显示指定的行数。这个功能后面有时间我们会演示,这里我们先看下基本的演示代码:
@Composable
fun MaxLinesDemo() {
Column {
Text(
text = "在Compose中,每一个组件都是带有@Compose注解的函数,被称为Composable。" +
"Compose已经预置了很多的Compose UI组件,这些组件都是基于Material Design规范设计的",
style = MaterialTheme.typography.body1,
color = Color.Red
)
Spacer(modifier = Modifier.size(10.dp))
Text(
text = "在Compose中,每一个组件都是带有@Compose注解的函数,被称为Composable。" +
"Compose已经预置了很多的Compose UI组件,这些组件都是基于Material Design规范设计的",
style = MaterialTheme.typography.body1,
maxLines = 1,
color = Color.Green
)
Spacer(modifier = Modifier.size(10.dp))
Text(
text = "在Compose中,每一个组件都是带有@Compose注解的函数,被称为Composable。" +
"Compose已经预置了很多的Compose UI组件,这些组件都是基于Material Design规范设计的",
style = MaterialTheme.typography.body1,
maxLines = 1,
color = Color.Blue,
overflow = TextOverflow.Ellipsis
)
}
}
fontFamily参数用来设置文字字体,演示代码如下:
@Composable
fun FontFamilyDemo() {
Column {
Text(text = "Hello World", color = Color.Red)
Spacer(modifier = Modifier.size(10.dp))
Text(text = "Hello World", color = Color.Green, fontFamily = FontFamily.Monospace)
Spacer(modifier = Modifier.size(10.dp))
Text(text = "Hello World", color = Color.Blue, fontFamily = FontFamily.Cursive)
}
}
运行结果
当使用系统中没有的字体时,可以右击res文件夹,选择New->Android Resource Directory->Resource type ->font创建font文件夹,然后将自己的字体拖入文件夹即可
AnnotatedString 其实很像传统View中的SpannableString,在很多的场景中,我们需要在一段文字中突出某些内容,比如超链接和电话号码,我们点击超链接和电话号码能跳到链接指向的页面和拨打电话。在Compose中就需要使用AnnotatedString多样式文字,AnnotatedString是一个数据类,除了文本值,它还包含了一个SpanStyle和ParagraphStyle的Range列表,SpanStyle用于描述在文本中字串的文字样式,ParagraphStyle则用于描述文本字串中的段落样式,Range确定字串的范围,示例代码如下:
@Composable
fun AnnotatedStringDemo() {
Text(text = buildAnnotatedString {
withStyle(style = SpanStyle(fontSize = 24.sp)) {
append("I am Iron man,我是钢铁侠")
}
withStyle(style = SpanStyle(fontWeight = FontWeight.W900, fontSize = 24.sp)) {
append("zhongxj")
}
append("\n")
withStyle(style = ParagraphStyle(lineHeight = 25.sp)) {
append(
"在Compose中,每一个组件都是带有@Compose注解的函数,被称为Composable。" +
"Compose已经预置了很多的Compose UI组件,这些组件都是基于Material Design规范设计的"
)
}
append("\n")
append("Now,We are working hard")
append(annotatedText)
})
}
SpanStyle继承了TextStyle中关于文字样式相关的字段,而ParagraphyStyle继承了TextStyle中控制锻炼的样式,例如textAlign,lineHeight等。SpanStyle和ParagraphyStyle中的设置优先于整个TextStyle中同名属性的设置
看到上图中的绿色文字,是不是有一种想点击的冲动,可惜现在是无法点击的,若要实现点击,我们需要借助于ClickedText.演示代码如下:
@Composable
fun ClickTextDemo() {
ClickableText(text = annotatedText, onClick = { offset ->
annotatedText.getStringAnnotations(
tag = "URL",
start = offset,
end = offset
).firstOrNull()?.let {
Log.d("zhongxj", "click me, content: $it")
}
})
}
val annotatedText = buildAnnotatedString {
withStyle(style = ParagraphStyle(lineHeight = 25.sp)) {
append(
"在Compose中,每一个组件都是带有@Compose注解的函数,被称为Composable。" +
"Compose已经预置了很多的Compose UI组件,这些组件都是基于Material Design规范设计的"
)
}
append("\n")
pushStringAnnotation(tag = "URL", annotation = "http://xxxxx")
withStyle(
style = SpanStyle(
fontWeight = FontWeight.W900,
textDecoration = TextDecoration.Underline,
color = Color(0xFF59A869)
)
) {
append("点击我,了解更多")
}
}
运行结果如下:
这样才可以点击,点击的时候我们打了一个Log,证明点击生效了
Text自身默认是不能被长按选择的,否则在Button使用的时候,就会出现传统View的Button是可粘贴的Button的按钮,Compose提供了专门的SelectionContainer组件对包裹的Text进行选中,演示代码如下:
@Composable
fun SelectionContainerDemo() {
SelectionContainer {
Text(text = "我是可以被复制的文字")
}
}
TextField组件是我们最常用的文本输入框,遵循Material Design设计准则,它有一个低级别的底层组件,BasicTextField.TextField有两种风格,默认的(filled)和OutlinedTextField我们可以先看下输入框的参数
@Composable
fun TextField(
value: String, // 输入框显示的文本
onValueChange: (String) -> Unit, // 当输入框内的文字发生改变时的回调,其中带有最新的文本参数
modifier: Modifier = Modifier,// 修饰符
enabled: Boolean = true,// 是否启用
readOnly: Boolean = false, // 控制输入框的可编辑状态
textStyle: TextStyle = LocalTextStyle.current, // 输入框内文字样式
label: @Composable (() -> Unit)? = null, // 可选的标签,将显示在输入框内
placeholder: @Composable (() -> Unit)? = null, // 占位符,当输入框处于焦点位置且输入框文本为空时显示
leadingIcon: @Composable (() -> Unit)? = null, // 输入框开头的前置图标
trailingIcon: @Composable (() -> Unit)? = null,// 输入框末尾的后置图标
isError: Boolean = false, // 指示输入框当前值是否有错,当值为true时,标签底部指示器和尾部图标将以错误的颜色显示
visualTransformation: VisualTransformation = VisualTransformation.None,// 输入框内的文字视觉
keyboardOptions: KeyboardOptions = KeyboardOptions.Default, // 软键盘选项,包含键盘类型和ImeAction等配置
keyboardActions: KeyboardActions = KeyboardActions(), // 当输入服务发出一个IME动作时,相应的回调被调用
singleLine: Boolean = false,// 输入框是否只能输入一行
maxLines: Int = Int.MAX_VALUE, // 输入框能输入的最大行数
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, // 监听组件的状态便于自定义
// 组件不同状态下的样式
shape: Shape =
MaterialTheme.shapes.small.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),// 输入框的外观形状
colors: TextFieldColors = TextFieldDefaults.textFieldColors() // 输入框的颜色组
)
演示示例如下所示:
@Composable
fun TextFieldDemo() {
var text by remember {
mutableStateOf("")
}
TextField(value = text, onValueChange = {
text = it
},
label = { Text(text = "用户名") })
}
运行结果
输入框附带一个label,label会更具输入框获得焦点而呈现出不同的效果,底部会有一个高亮的图标,代码中有关于State的使用,此处不做讲解,这里用于展示使用
我们还可以给输入框添加装饰,代码如下:
@Composable
fun TextFieldWithDec() {
var username by remember {
mutableStateOf("")
}
var password by remember {
mutableStateOf("")
}
Column {
TextField(value = username, onValueChange = {
username = it
}, label = { Text(text = "用户名") }, leadingIcon = {
Icon(imageVector = Icons.Filled.AccountBox, contentDescription = "用户名")
})
TextField(value = password, onValueChange = {
password = it
}, label = { Text(text = "密码") }, trailingIcon = {
IconButton(onClick = { /*TODO*/ }) {
Icon(
painter = painterResource(id = R.drawable.password),
modifier = Modifier.size(16.dp),
contentDescription = "password"
)
}
})
}
}
OutlinedTextField是按照Material Design规范设计的另一种风格的输入框,除了外观上带有一个边框,其他用法和TextField基本一致,演示代码如下:
@Composable
fun OutlineTextFieldDemo()
{
var text by remember {
mutableStateOf("")
}
OutlinedTextField(value = text, onValueChange = {text = it},
label = { Text(text = "用户名")})
}
BasicTextField是一个更低级别的Compose组件,与TextField,OutlinedTextField不同的是,BasicTextField拥有更多的自定义效果,我们可以看下BasicTextField为我们提供的可选参数列表:
@Composable
fun BasicTextField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
readOnly: Boolean = false,
textStyle: TextStyle = TextStyle.Default,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
singleLine: Boolean = false,
maxLines: Int = Int.MAX_VALUE,
visualTransformation: VisualTransformation = VisualTransformation.None,
onTextLayout: (TextLayoutResult) -> Unit = {}, // 当输入框文本更新时的回调包括当前文本的各种信息
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
cursorBrush: Brush = SolidColor(Color.Black), // 输入框光标的颜色
decorationBox: @Composable (innerTextField: @Composable () -> Unit) -> Unit =
@Composable { innerTextField -> innerTextField() } // 允许在TextField周围添加修饰的Composable lambda,需要在布局中
// 中调用innerTextField()才能完成TextField的创建
)
我们可以看到,BasicTextField的参数和TextField的参数有很多共同的地方,我们自定义的关键在于最后一个参数decorationBox,decorationBox是一个Composable,它回调了一个innerTextField函数给我们,innerTextField是框架定义好给我们使用的,它就是文字输入的入口,所以需要创建一个完整的输入框界面,并且在合适的地方调用这个函数,我们实现一个仿当前大多是app都会使用的输入框结束今天的内容:代码如下
@Composable
fun SearchBar() {
var text by remember {
mutableStateOf("")
}
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFD3D3D3)),
contentAlignment = Alignment.Center
)
{
BasicTextField(value = text, onValueChange = {
text = it
}, decorationBox = { innerTextField ->
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 2.dp, horizontal = 8.dp)
) {
Icon(imageVector = Icons.Filled.Search, contentDescription = null)
Box(
modifier = Modifier.padding(horizontal = 10.dp),
contentAlignment = Alignment.CenterStart
) {
if (text.isEmpty()) {
Text(
text = "请输入", style = TextStyle(
color = Color(0, 0, 0, 128)
)
)
}
innerTextField()
}
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.CenterEnd
) {
if (text.isNotEmpty()) {
IconButton(onClick = { text = "" }, modifier = Modifier.size(16.dp)) {
Icon(
imageVector = Icons.Filled.Close, contentDescription = null
)
}
}
}
}
}, modifier = Modifier
.padding(horizontal = 10.dp)
.background(Color.White, CircleShape)
.height(30.dp)
.fillMaxWidth()
)
}
}
至此,我们的文字组件就介绍完了,本文只是简单的介绍了文字组件的入门功能,如果读者想要实现更加复杂绚丽的功能,请查看官方文档和其他大牛的博客。感谢阅读