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博物馆

相关推荐
欧简墨15 分钟前
kotlin Android Extensions插件迁移到viewbinding总结
android·trae
货拉拉技术44 分钟前
优雅解决Android app后台悬浮窗权限问题
android
用户69371750013841 小时前
Android 手机终于能当电脑用了
android·前端
用户5172231574802 小时前
android资源类型与布局资源详细介绍
android
优选资源分享3 小时前
GKD v1.11.6 | 安卓开屏广告跳过工具 可用版
android
robotx3 小时前
安卓zygote启动相关
android
Mac的实验室4 小时前
2026年最新真实社交怎么注册?手把手教你如何成功注册Truth Social账号
android
毕设源码-郭学长5 小时前
【开题答辩全过程】以 基于Android的点餐APP的设计为例,包含答辩的问题和答案
android
polaris06305 小时前
学生成绩管理系统(MySQL)
android·数据库·mysql
__Yvan5 小时前
Kotlin 的 ?.let{} ?: run{} 真的等价于 if-else 吗?
android·开发语言·前端·kotlin