图片说明

/ 前言 /

Jetpack Compose是用于构建原生Android UI的现代工具包,使用更简洁的代码、强大的工具和直观的Kotlin API,简化并加速了Android上的UI开发。不同于Andorid常见的Xml+命令式Coding的UI开发范式,Compose基于Kotlin的DSL实现了一套类似React的声明式UI框架。伴随React Native、Flutter等大前端框架的兴起以及Jetpack Compose、SwiftUI等native框架的出现,声明式UI正逐渐成为客户端UI开发的新趋势,那么下面主要就来介绍一下Compose中全新的文本框。

/ 基本API介绍 /

@Composable
fun TextField(
    value: TextFieldValue,//要展示的文本
    onValueChange: (TextFieldValue) -> 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,//当前输入是否错误
    visualTransformation: VisualTransformation = VisualTransformation.None,//指定输入类型,类似inputType
    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,//自定义键盘按键
    keyboardActions: KeyboardActions = KeyboardActions(),//自定义按键事件
    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()//各种cursor、文本等颜色
)

/ 基础用法 /

日常开发中,经常会使用到文本输入框;一般样式为左边是一个搜索的图标,右边是清楚文本图标,如果文本框未输入,则提示hint关键词,效果大概是以下这种:
图片说明
那么如何用compose实现上述满足我们日常用法的输入框呢,代码如下:

@Composable
fun ShowTextField(context: MainActivity) {
    //初始化文本变量
    var text by remember { mutableStateOf("") }

    TextField(
        value = text, // 显示文本
        onValueChange = { text = it }, // 监听文本变化,并赋值给text
        label = { Text(text = "Input") }, // 设置label
        leadingIcon = @Composable {// 设置左边图标
            Image(
                imageVector = Icons.Filled.Search,
                contentDescription = "search", //image的无障碍描述
                modifier = Modifier.clickable {// 通过modifier来设置点击事件
                    Toast.makeText(
                        context,
                        "search $text",
                        Toast.LENGTH_SHORT
                    ).show()
                })
        },
        trailingIcon = @Composable {//设置右边图标
            Image(imageVector = Icons.Filled.Clear,
                contentDescription = "clear",
                modifier = Modifier.clickable { text = "" }) // 添加点击清空事件
        },
        placeholder = @Composable { Text(text = "This is placeholder") },//hint提示语
}

接入Compose的日常文本框效果就是这个样子:

图片说明
可以看到,Compose作为一款全新的UI工具包,动效并不输于基于View的系统实现的文本框;重点是代码量少了 a lot有木有?! 这些就是Compose的贴心之处,能帮助开发者花更少的精力在UI和动效搭建上,这些统统都帮实现好了,从而把节省下来的时间更多的放在业务逻辑中;
现在让我们来加几行代码,看看能不能实现更炫酷的效果:

isError = true, //是否显示错误提示
visualTransformation = PasswordVisualTransformation(),//展示密文
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),//自定义回车为搜索操作
keyboardActions = KeyboardActions(//将搜索事件自定义
onSearch = {
    Toast.makeText(
        context,
        "search $text",
        Toast.LENGTH_SHORT
    ).show()

新增这几行代码后效果就变成了这种效果:

图片说明
相较于之前的效果,文字和输入框下划线都被标上了表示错误的红色,这里红色能不能自定义呢?将在后面进行解答,同时文本显示成被加密的密文格式,软键盘的回车键也被修改成了搜索按钮;不仅能修改成搜索,还能自定义成发送、完成等按钮,查看 ImeAction 文件可知,总共有下面这些自定义按钮类型,大家可以去自己自定义试试:

val None: ImeAction = ImeAction(0)
val Default: ImeAction = ImeAction(1)
val Go: ImeAction = ImeAction(2)
val Search: ImeAction = ImeAction(3)
val Send: ImeAction = ImeAction(4)
val Previous: ImeAction = ImeAction(5)
val Next: ImeAction = ImeAction(6)
val Done: ImeAction = ImeAction(7)

那如果有的需求需要设置输入框圆角样式呢,非常简单,不需要繁琐的设置selector,只需要简单一行代码搞定:

shape = RoundedCornerShape(16.dp),//设置文本框圆角

那有人又问了,我只需要实现半圆角效果怎么办,同样也是一句代码就能实现:

shape = RoundedCornerShape(16.dp,16.dp,0.dp,0.dp),//设置文本框圆角

可以通过查看 RoundedCornerShape源码得知它能设置圆角的原因

fun RoundedCornerShape(
    topStart: Dp = 0.dp,
    topEnd: Dp = 0.dp,
    bottomEnd: Dp = 0.dp,
    bottomStart: Dp = 0.dp
)
...

fun RoundedCornerShape(corner: CornerSize) =
    RoundedCornerShape(corner, corner, corner, corner)
...

//outline通过设置bounds来绘制形状
override fun createOutline(
    size: Size,
    topStart: Float,
    topEnd: Float,
    bottomEnd: Float,
    bottomStart: Float,
    layoutDirection: LayoutDirection
) = if (topStart + topEnd + bottomEnd + bottomStart == 0.0f) {//如果未设圆角,则显示长方形
    Outline.Rectangle(size.toRect())
} else {
    Outline.Rounded(//如果设置了圆角,则显示圆角形状
        RoundRect(
            rect = size.toRect(),
            topLeft = CornerRadius(if (layoutDirection == Ltr) topStart else topEnd),
            topRight = CornerRadius(if (layoutDirection == Ltr) topEnd else topStart),
            bottomRight = CornerRadius(if (layoutDirection == Ltr) bottomEnd else bottomStart),
            bottomLeft = CornerRadius(if (layoutDirection == Ltr) bottomStart else bottomEnd)
        )
    )
}

之前上文提到的红色下划线如何自定义呢,TextField 中有个参数 colors 供开发者来自定义文本框每个状态下的颜色:

colors = TextFieldDefaults.textFieldColors(
    focusedIndicatorColor = Color.Transparent,//有焦点时 底部指示条为透明
    unfocusedIndicatorColor = Color.Green,//无焦点,为绿色
    errorIndicatorColor = Color.Red,//错误时,为红色
    disabledIndicatorColor = Color.Gray,//不可用,灰色
)

为了不让文本框一直处于错误状态,我们还需要对isError状态进行修改:

isError = false, //展示错误提示

如果不对Error状态进行更改,文本框将处于错误状态,下划线将一直是红色的;大功告成之后,现在来看下文本框的样式:![]
图片说明
可以看到,当失去焦点时,下划线是我们指定 unfocusedIndicatorColor绿色;获取到焦点并输入文本时,下划线是指定 focusedIndicatorColor透明颜色;至于不可用状态和错误状态下的颜色,大家可以模拟场景进行测试;

/ 扩展 /

轮廓式文本框OutlinedTextFieldOutlinedTextField几乎和普通的TextField有着相同的API,但它还能实现文本框描边,顾名思义,轮廓式文本框自带了轮廓描边,能够轻易实现产品和UI眼中的效果可以来个简单demo演示一下:

@Composable
fun OutlinedTextFieldDemo() {
    var text by remember { mutableStateOf("") }
    OutlinedTextField(value = text,
        label = { Text(text = "Input something") },//这里label为文本框未输入时显示的文本
        onValueChange = { text = it })
}

用法和TextField一致,只是在样式上有不同:

图片说明

基本文本框BasicTextField可以看到不管是 TextField 还是 OutlinedTextField 其实都已经帮开发者实现了常见文本框大部分的动效及功能,但实际开发中会遇到非常多特殊场景,需要一款基本文本框让开发者去高度自定义实现业务所需样式,那么大家可以尝试着使用一下 BasicTextField ,它能用来相应硬件或软键盘编辑文字,可以自定义cursor、边框等,因为边框是自定义了,label属性在 BasicTextField 中也可以剔除了;它的API和TextField存在一部分差异,可以一起来看下:

@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,//指定输入类型,类似inputType
    onTextLayout: (TextLayoutResult) -> Unit = {},//监听布局变化
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },//某个组件的交互流
    cursorBrush: Brush = SolidColor(Color.Black),//自定义cursor需要用到
    // 用来定义装饰框,innerTextField用来绘制文本
    decorationBox: @Composable (innerTextField: @Composable () -> Unit) -> Unit =
        @Composable { innerTextField -> innerTextField() }
)

同样的,拿到API后我们先来写个简单demo:

@Composable
fun ShowBasicTextField(context: MainActivity) {
    var input by remember {
        mutableStateOf("hello")
    }

    BasicTextField(
        value = input, // 显示文本
        onValueChange = { input = it }, // 文字改变时,就赋值给text
    )
}

展示效果如下:
图片说明
emmm咋就一个hello呢,文本框的边界都看不到,那如果我设定初始值为空,那岂不连这个文本框都不知道在哪🤦🏻‍♀️;真的是够原始,真的够Basic,但正是因为这样所以能去高度自定义文本框样式,让我们看看 BasicTextField 正确的打开方式是怎样的:首先给它来加个背景吧,要不然连它在哪都不知道了:

modifier = Modifier.background(Color.Green, RoundedCornerShape(8.dp)),//设置绿底色,8dp圆角的背景,也可通过上述方法来设置半圆角

效果如下:

图片说明
怎么可能仅仅满足于只加个背景颜色呢,让我们画个文本框,添加如下代码:

decorationBox = @Composable { innerTextField ->
    // 通过canvas来画文本框的修饰等, modifier决定了文本框长宽
    Canvas(modifier = Modifier.size(width = 100.dp, height =50.dp)) {、```
        //画个圆
        drawCircle(color = Color.Red, style = Stroke(width = 1F))
    }
    // 然后再画文字
    innerTextField()
}

上述代码操作很简单,就是先通过Canvas画布对文本框的边界进行绘制,然后在内部绘制了一个圆形,最后调用innerTextField绘制文本框内部的文字,效果如下:

图片说明

其实Canvas画布上不仅仅能绘制边框和圆形,查看源码之后发现还能绘制icon、占位符placeHolder等等,而且绘制文字的innerTextField方法只能调用一次:

* @param decorationBox 
* Composable lambda that allows to add decorations around text field, such
* as icon, placeholder, helper messages or similar, and automatically increase the hit target area
* of the text field. To allow you to control the placement of the inner text field relative to your
* decorations, the text field implementation will pass in a framework-controlled composable
* parameter "innerTextField" to the decorationBox lambda you provide. You must call
* innerTextField exactly once.

/ 总结 /

如果普通的文本框没法满足业务需求,各位看官可以尝试使用BasicTextField,能高度自定义文本框样式;如果普通文本框正好是我们想要的,使用TextField即可;如果想用空心的轮廓式文本框,则使用OutlinedTextField;TextField和OutlinedTextField一个是实心一个是空心文本框,其余API完全一致;而BasicTextField在API上有着一定的差异,可以高度自定义样式;

/ 谢谢支持 /

以上便是本次分享的全部内容,希望对你有所帮助^_^
喜欢的话别忘了 分享、点赞、收藏 三连哦~
欢迎关注公众号 程序员巴士,一辆有趣、有范儿、有温度的程序员巴士,涉猎大厂面经、程序员生活、实战教程、技术前沿等内容,关注我,交个朋友。
还可以添加小巴微信号:hwwd8584 免费进群领取学习资料哦。