Jetpack Compose(三)-文字组件

写一些文字相关的组件,包含

一、Text文本

文本是最常见的组件之一。在Compose中,Text是遵循Material Design规范设计的上层文本组件,如果想脱离Material Design使用,也可以直接使用更底层的文本组件BasicText。

1、参数说明

Text组件的基本参数如下:

kotlin 复制代码
@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,   //最大行数
    minLines: Int = 1,   //最小行数
    onTextLayout: (TextLayoutResult) -> Unit = {},   //在文本发生变化之后,会回调到这里
    style: TextStyle = LocalTextStyle.current    //文本的风格配置,如颜色、字体、行高等
) {...}

举一个最简单的例子

kotlin 复制代码
Column  {
    Text(text = "Hello Android")
    Text(text = stringResource(id = R.string.app_name))
    BasicText(text = "Hello Android")
}

Tips:除了stringResource, Compose也提供了获取其他类型资源的方法,例如colorResourceintegerResourcepainterResource(Drawable类型资源)等。

2、style文字样式

style参数接受一个TextStyle类型,TextStyle中包含一系列设置文字样式的字段,例如行高、间距、字体大小、字体粗细等。举例如下:

kotlin 复制代码
Text(
    text = "Hello Android",
    style = TextStyle(
        fontSize = 18.sp,   //字体大小
        color = Color.Blue,   //字体颜色
        fontWeight = FontWeight.Bold,  //文字粗细
        background = Color.White,   //背景
        lineHeight = 30.sp,   //行高
        letterSpacing = 4.sp,    //字间距
        textDecoration = TextDecoration.LineThrough   //删除线

    )
)

UI效果

可以把常用的TextStyle抽取出来。TextStyle中的大部分字段也可以在Text参数中直接设置,例如fonteSizefontWeightfontStyle等。注意Text参数会覆盖对TextStyle同名属性的设置。

3、AnnotatedString多样式文字

Texttext参数除了可以给String类型还可以给AnnotatedString类型,看下AnnotatedString的源码:

kotlin 复制代码
class AnnotatedString internal constructor(
    val text: String,
    internal val spanStylesOrNull: List<Range<SpanStyle>>? = null,
    internal val paragraphStylesOrNull: List<Range<ParagraphStyle>>? = null,
    internal val annotations: List<Range<out Any>>? = null
) : CharSequence {...}

在很多应用场景中,我们需要在一段文字中对局部内容应用特别格式以示突出,比如一个超链接或者一个电话号码等,此时需要用到AnnotatedStringAnnotatedString是一个数据类,除了文本值,它还包含了一个SpanStyleParagraphStyleRange列表。SpanStyle用于描述在文本中子串的文字样式,ParagraphStyle则用于描述文本中子串的段落样式,Range确定子串的范围。

使用buildAnnotatedString{...},以DSL的方式构建一个AnnotatedString。其中append用来添加子串的文本,withStyleappend的子串指定文字或段落样式。代码如下所示:

kotlin 复制代码
Text(
    text = buildAnnotatedString {
        withStyle(style = SpanStyle(color = Color.Black)) {
            append("文字变色加粗")
        }
        withStyle(style = SpanStyle(color = Color.Blue,fontWeight = FontWeight.W900)) {
            append("Jetpack Compose")
        }
        append("\n")
        withStyle(style = SpanStyle(color = Color.Black)) {
            append("文字变色加粗添加下划线")
        }
        withStyle(style = SpanStyle(color = Color.Blue,fontWeight = FontWeight.W900, textDecoration = TextDecoration.Underline)) {
            append("Jetpack Compose")
        }
    }
)

UI效果

SpanStyle继承了TextStyle中关于文字样式相关的字段,而ParagraphStyle继承了TextStyle中控制段落的样式,例如textAlignlineHeight等。某种意义上说SpanStyleParagraphStyle分拆了TextStyle,可以对子串分别进行文字以及段落样式的设置。

注意:SpanStyleParagraphStyle中的设置优先于整个TextStyle中的同名属性设置。

有一个常见的需求就是部分文字可点击又该如何实现呢?

Compose提供了一种可点击文本组件ClickedText,可以响应对文字的点击,并返回点击位置。可以让AnnotatdString子串在相应的ClickedText中点击后,做出不同的动作。例如点击一个超链接样式子串可以打开浏览器、点击数字格式子串来拨打电话等。在AnnotatedString中可以为子串添加一个tag标签,在处理onClick事件时,可以根据tag实现不同的逻辑。示例代码:

kotlin 复制代码
@Composable
fun Greeting() {
    //文本内容
    val annotatedString = buildAnnotatedString {
        withStyle(style = SpanStyle(color = Color.Black)) {
            append("跳转到官网:")
        }
        pushStringAnnotation(tag = "juejin", annotation = "https://juejin.cn/")
        withStyle(style = SpanStyle(color = Color.Blue, fontWeight = FontWeight.W900)) {
            append("juejin")
        }
        pop()
        append("\n")
        withStyle(style = SpanStyle(color = Color.Black)) {
            append("跳转到官网:")
        }
        pushStringAnnotation(tag = "baidu", annotation = "https://www.baidu.com/")
        withStyle(
            style = SpanStyle(color = Color.Blue, fontWeight = FontWeight.W900)
        ) {
            append("baidu")
        }
        pop()
    }
    //可点击的文本控件
    ClickableText(
        text = annotatedString,
        onClick = { offset ->
            annotatedString.getStringAnnotations(tag = "juejin", start = offset, end = offset)
                .firstOrNull()?.let { annotation ->
                    //跳转到掘金官网
                    val uri = Uri.parse(annotation.item)
                    val intent = Intent(Intent.ACTION_VIEW, uri)
                    startActivity(intent)
                }
            annotatedString.getStringAnnotations(tag = "baidu", start = offset, end = offset)
                .firstOrNull()?.let { annotation ->
                    //跳转到百度官网
                    val uri = Uri.parse(annotation.item)
                    val intent = Intent(Intent.ACTION_VIEW, uri)
                    startActivity(intent)
                }
        }
    )
}

使用可点击的文本ClickableTextpop()AnnotatedString的方法,作用是结束添加的style和annotation,让文本恢复默认样式。UI效果如下:

4、SelectionContainer选中文字

Text自身默认是不能被长按选择的,如果希望文本能被长按选中需要使用SelectionContainer包裹。示例如下:

二、TextField输入框

TextField组件是我们最常使用的文本输入框,它有两种风格,一种是默认的,也就是TextField,另一种是OutlinedTextField。它也有一个低级别的底层组件,叫作BasicTextField,后面会介绍。

1、参数说明

TextField组件的基本参数如下:

kotlin 复制代码
@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,    //占位文本,相当于hint
    leadingIcon: @Composable (() -> Unit)? = null,    //输入框开头显示的前置图标
    trailingIcon: @Composable (() -> Unit)? = null,   //输入框末尾显示的后置图标
    isError: Boolean = false,        //指示输入框的当前值是否有错误,当值为"true"时,标签、底部指示器和尾部图标将以错误颜色显示
    visualTransformation: VisualTransformation = VisualTransformation.None,    //输入框内的文本视觉,例如可以设置PasswordVisualTransformation来达到显示密码文本的效果
    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,    //软键盘选项,包含键盘类型和ImeAction等配置
    keyboardActions: KeyboardActions = KeyboardActions(),   //当输入发出一个IME动作时,相应的回调会被调用
    singleLine: Boolean = false,   //是否单行
    maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,   //最大行数
    minLines: Int = 1,    //最小行数
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },   //用于监听组件状态,便于自定义组件不同状态下的样式
    shape: Shape =
        MaterialTheme.shapes.small.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),   //输入框的外观形状
    colors: TextFieldColors = TextFieldDefaults.textFieldColors()   //输入框的颜色组
) {...}

下面是一个简单的示例

kotlin 复制代码
var userName by remember { mutableStateOf("") }
TextField(
    value = userName,
    modifier = Modifier.fillMaxWidth(),
    onValueChange = { userName = it },
    placeholder = { Text(text = "用户名不允许使用特殊符号") },
    label = { Text(text = "用户名") },
    maxLines = 1
)

UI效果

2、为输入框添加装饰

(1)添加前后图标

kotlin 复制代码
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,    //Android内置图
                contentDescription = ""
            )
        }
    )

    TextField(
        value = password,
        onValueChange = { password = it },
        label = { Text(text = "密码") },
        trailingIcon = {                     //设置后置图标
            IconButton(onClick = { showToast("显示密码") }) {
                Icon(
                    painter = painterResource(id = R.mipmap.eye_invisiable),
                    contentDescription = "",
                    modifier = Modifier     //修改图片大小
                        .width(20.dp)
                        .height(20.dp),
                )
            }
        }
    )
}

UI效果

(2) OutlinedTextField边框样式输入框

OutlinedTextField是一个带边框的输入框,系统还提供了一个带边框的按钮OutlinedButton,下面是一个示例,包含了如何修改自带的边框的一些属性:

kotlin 复制代码
var userName by remember { mutableStateOf("") }
Column {
    //带边框的输入框
    OutlinedTextField(
        value = userName,
        onValueChange = { userName = it },
        label = { Text(text = "用户名") },
        leadingIcon = {
            Icon(
                imageVector = Icons.Filled.AccountBox,
                contentDescription = ""
            )
        }
    )
    //加点间距
    Spacer(modifier = Modifier.height(20.dp))
    //带边框的按钮
    OutlinedButton(onClick = {},
        colors = ButtonDefaults.outlinedButtonColors(     //设置颜色
            backgroundColor = Color.Gray,
            contentColor = Color.White
        ),                
        shape = MaterialTheme.shapes.small.copy(     //设置边框圆角
            topStart = CornerSize(30),   //左上30dp
            topEnd = CornerSize(10)      //右上20dp
        ),               //设置边框的圆角
        border = BorderStroke(width = 2.dp, color = Color.Blue),    //设置边框的宽度和边框颜色
        content = {
            Text(text = "带边框的按钮")    //按钮的内容
        })
}

UI效果

3、BasicTextField参数说明

BasicTextField是一个更低级别的Composable组件,与TextField、OutlinedTextField不同的是,BasicTextField拥有更多的自定义效果。

kotlin 复制代码
@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 = if (singleLine) 1 else Int.MAX_VALUE,
    minLines: Int = 1,
    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有很多共同的地方,自定义BasicTextField的关键在于最后一个参数decorationBoxdecorationBox是一个Composable,它回调了一个innerTextField函数给我们。innerTextField是框架定义好给我们使用的东西,它就是文字输入的入口,所以需要创建好一个完整的输入框界面,并在合适的地方调用这个函数。看下面的例子:

kotlin 复制代码
var userName by remember { mutableStateOf("") }
var basicText by remember { mutableStateOf("") }
Column {
    //TextField
    TextField(
        value = userName,
        onValueChange = { userName = it },
        modifier = Modifier.height(50.dp)
    )
    //加点间距
    Spacer(modifier = Modifier.height(20.dp))
    //BasicTextField
    BasicTextField(
        value = basicText,
        onValueChange = { basicText = it },
        modifier = Modifier.height(50.dp),
        decorationBox = { innerTextField ->
            //直接调用
            //innerTextField()
            //或添加其他的装饰
            Column {
                innerTextField()
                //加分割线
                Divider(
                    thickness = 2.dp, //分割线的宽度
                    modifier = Modifier
                        .fillMaxWidth()
                        .background(Color.Blue)
                )
            }
        }
    )
}

UI效果

代码中将TextFieldBasicTextField放在一起对比,如果BasicTextField不调用innerTextField()那么点击是没有任何效果的,所以自定义BasicTextField不要忘记调用innerTextField()

4、BasicTextField的简单封装示例

基于BasicTextField可以封装多种多样的输入框,项目是二个简单示例

(1)限制输入长度的输入框

kotlin 复制代码
@Composable
fun Greeting() {
    var text by remember { mutableStateOf("") }
    LimitedLengthTextField(
        modifier = Modifier
            .border(1.dp, Color.Gray, shape = RoundedCornerShape(4.dp))
            .padding(8.dp),
        value = text,
        maxLength = 5,
        onValueChange = { text = it },
    )
}


//限制输入的长度
@Composable
fun LimitedLengthTextField(
    modifier: Modifier = Modifier,
    value: String,
    maxLength: Int,
    onValueChange: (String) -> Unit,
) {
    var text by remember { mutableStateOf(value) }

    BasicTextField(
        value = text,
        onValueChange = {
            if (it.length <= maxLength) {
                text = it
                onValueChange(it)
            } else {
                text = it.take(maxLength)   //超过就截断
                onValueChange(text)
            }
        },
        modifier = modifier,
        singleLine = true,
        textStyle = TextStyle(color = Color.Black),
        visualTransformation = VisualTransformation.None
    )
}

(2)限制只能输入字母和数字

kotlin 复制代码
@Composable
fun Greeting() {
    var text by remember { mutableStateOf("") }
    AlphanumericTextField(
        modifier = Modifier
            .border(1.dp, Color.Gray, shape = RoundedCornerShape(4.dp))
            .padding(8.dp),
        value = text,
        onValueChange = {
            text = it
        },
    )
}

//限制只能输入大小写字母和数字
@Composable
fun AlphanumericTextField(
    modifier: Modifier,
    value: String,
    onValueChange: (String) -> Unit
) {
    var text by remember { mutableStateOf(value) }

    BasicTextField(
        modifier = modifier,
        value = text,
        onValueChange = {
            // 使用正则表达式过滤非字母和非数字的字符
            val filteredText = it.replace(Regex("[^a-zA-Z0-9]"), "")
            text = filteredText
            onValueChange(filteredText)
        },
        textStyle = TextStyle(color = Color.Black), // 根据需要设置文本样式
        keyboardOptions = KeyboardOptions(
            keyboardType = KeyboardType.Text,
            imeAction = ImeAction.Done
        ),
        keyboardActions = KeyboardActions(
            onDone = {
                // 执行完成操作(如果需要的话)
                showToast("点了键盘上的ok按钮")
            }
        )
    )
}

三、 总结

在不熟练的情况下用起来还是不顺手的,有时候一个简单的效果要琢磨一会儿。作为初学者,以上内容难免会有错误或不足,欢迎批评指正。

参考了以下内容

Jetpack Compose 从入门到实战

Jetpack Compose博物馆

相关推荐
闲暇部落38 分钟前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
诸神黄昏EX3 小时前
Android 分区相关介绍
android
大白要努力!4 小时前
android 使用SQLiteOpenHelper 如何优化数据库的性能
android·数据库·oracle
Estar.Lee4 小时前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip
Winston Wood4 小时前
Perfetto学习大全
android·性能优化·perfetto
Dnelic-7 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
Eastsea.Chen9 小时前
MTK Android12 user版本MtkLogger
android·framework
长亭外的少年16 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿19 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神20 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri