原文地址: Jetpack Compose学习(3)——图标(Icon) 按钮(Button) 输入框(TextField) 的使用 | Stars-One的杂货小窝
本篇分别对常用的组件:图标(Icon) 按钮(Button) 输入框(TextField)的使用方法及各参数使用进行讲解,参考了不少文章,且费了不少时间去时间去一一实践,希望对各位带来些帮助 ??
本系列以往文章请查看此分类链接jetpackcompose学习
图标Icon使用
Icon接收三种参数,如下图
//第一种就不多说,就是一个drawble对象
//获取图片资源,R.drawble.xx或R.mipmap.xx
Icon(painter = painterResource(id = R.drawable.head1_1024), null)
//自带的图标
Icon(Icons.Filled.Search, null)
Compose内置了几十个常用的图标,我们使用枚举类型即可使用
Icons里面定了5种类型Outlined
Filled
Sharp
TwoTone
Rounded
,可以根据自己的需要选择不同的类型,如填充型(Filled)或者是轮廓型(Outlined)
Icon的构造方法参数简单说明下contentDescription
是给无障碍人使用的文本描述,考虑到一些视觉障碍的人使用,所以有个这个属性,会使用TTS语音播放将contentDescription
属性读出来,告知用户此按钮的作用
tint
则是图标颜色的设置
Row() {
Icon(Icons.Outlined.Settings, contentDescription = null, tint = Color.Red)
Icon(Icons.Filled.Settings, contentDescription = null, tint = Color.Blue)
Icon(Icons.Sharp.Settings, contentDescription = null, tint = Color.Green)
Icon(Icons.TwoTone.Settings, contentDescription = null, tint = Color.Red)
Icon(Icons.Rounded.Settings, contentDescription = null, tint = Color.Black)
}
效果如下图所示
PS:具体的图标名称写的时候会有代码提示
不过默认常用的就那40几个,其他的图标就没有包含在内,当然,如果你想用的话,也有方法实现,需要导入material-icons-extended
依赖即可
dependencies {
...
implementation "androidx.compose.material:material-icons-extended:$compose_version"
}
但是全套图标会导致打包后的apk文件过大,所以官方推荐使用导入图标文件的方法,详情可参考官方文档
按钮 Button
Button这个组件,官方已经实现了Material Design的效果,一般来说我们直接使用这个即可
除此之外,官方也是给我们封装了不同类型的Button,分别为IconButton
TextButton
OutlinedButton
IconToggleButton
上面我们刚讲了图标,下面就先讲些图标按钮IconButton的使用方式吧
基本使用
和以往我们使用的按钮不一样,这里的按钮可以看做是一个布局控件,我们需要设置文字也就是往里面添加一个Text组件,这就是compose和传统Android的xml的不同之处
由上面这点,所以我们在代码层面就十分灵活,可以实现各种效果(如带有图标的按钮),下面来个例子
Button(onClick = { println("点击了按钮")}){
Icon(Icons.Default.Search,contentDescription = null)
Text(text = "测试")
}
上面的代码实现的效果就是有个图标在左侧
参数讲解
我们先看下Button的定义,其实封装好的方法,代码如下所示
fun Button(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
elevation: ButtonElevation? = ButtonDefaults.elevation(),
shape: Shape = MaterialTheme.shapes.small,
border: BorderStroke? = null,
colors: ButtonColors = ButtonDefaults.buttonColors(),
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
content: @Composable RowScope.() -> Unit
)
Button
的content
参数(也就是上面lambda),传入多个组件,Button
会将其按照水平方式排列(即Button
可视为Row
布局)
由于kotlin的语法特性,所以我们可以在后面以花括号写个lambda函数
这里先讲下比较简单的参数:
-
onClick
是点击事件.也是接收一个函数 -
modifier
是修饰符,本章先不使用,之后出个篇文章,专门讲解下这个的用法 -
enabled
按钮是否可用(不可用默认是灰色,可用默认是蓝色),当然这里的默认的禁用和可用的颜色可可以调整,详情请见下面的colors
参数
接下来就是稍微有点复杂的参数说明了,因为用法与之前原生Button有所区别,这里特别分成一小节讲解,方便目录查阅
1.elevation 阴影
Button的阴影参数是有有默认值的,我们也可以使用下面的方法进行数值的修改
ButtonDefaults.elevation(defaultElevation,pressedElevation,disabledElevation)
-
defaultElevation
表示默认的阴影 -
pressedElevation
表示按下时的阴影 -
disabledElevation
表示未启用时候的阴影
Button(
enabled = true,
onClick = { /*TODO*/ },
elevation = ButtonDefaults.elevation(4.dp, 10.dp, 0.dp)
) {
Text(text = "阴影按钮")
}
Button(
enabled = false,
onClick = { /*TODO*/ },
elevation = ButtonDefaults.elevation(4.dp, 10.dp, 0.dp)
) {
Text(text = "禁用状态的阴影按钮")
}
PS:使用的时候,发现导包会失败,给了些奇怪的东西...建议复制下ButtonDefaults.elevation()
,再输入参数
2.shape 形状
Android官方给我们提供了以下四种形状,我从代码提示里只看到有这四种
-
RoundedCornerShape
圆角形状 -
CutCornerShape
切角形状 -
AbsoluteRoundedCornerShape
绝对圆角形状 -
AbsoluteCutCornerShape
绝对切角形状
这里从字面翻译知道其的意思,但是具体圆角形状和绝对圆角形状有什么区别,实际测试也有,但是没法看出来有什么区别,官方的文档也是解释的有点模糊
后来者如果知道,可以在评论区回复下,感谢~
上面四种类的接收参数其实是一样的,这里就截个图给大家看看
我们常用就是使用dp定位进行设置,如
RoundedCornerShape(10.dp) //设置10dp的圆角
RoundedCornerShape(topStart = 5.dp,topEnd = 6.dp,bottomEnd = 10.dp,bottomStart = 10.dp)
-
topStart
左上角 -
topEnd
右上角 -
bottomStart
左下角 -
bottomEnd
右下角
PS: 记住start是左,end是右,上面就比较好记了
Button(
onClick = { /*TODO*/ },
elevation = ButtonDefaults.elevation(4.dp, 10.dp, 0.dp),
shape = RoundedCornerShape(topStart = 5.dp,topEnd = 6.dp,bottomEnd = 10.dp,bottomStart = 10.dp)
) {
Text(text = "按钮")
}
我们可以实现如下图的效果
代码如下:
Modifier.size(50.dp,50.dp)
是用来设置宽高的
Row() {
//固定长宽一样,圆角设置为50%即为圆形
Button(
modifier = Modifier.size(50.dp,50.dp),
onClick = { /*TODO*/ },
shape = RoundedCornerShape(50),
) {
Text(text = "")
}
//固定长宽一样,切角设置为50%即为菱形
Button(
modifier = Modifier.size(50.dp,50.dp),
onClick = { /*TODO*/ },
shape = CutCornerShape(50.dp),
) {
Text(text = "")
}
//左上角设置圆角
Button(
onClick = { /*TODO*/ },
shape = RoundedCornerShape(topStart = 20.dp),
) {
Text(text = "按钮")
}
//圆角设置为50%
Button(
onClick = { /*TODO*/ },
shape = RoundedCornerShape(50),
border = BorderStroke(1.dp, Color.Green),
colors = ButtonDefaults.buttonColors(),
) {
Text(text = "按钮111")
}
Button(
modifier = Modifier.size(50.dp,50.dp),
onClick = { /*TODO*/ },
shape = CutCornerShape(25),
border = BorderStroke(1.dp, Color.Green),
colors = ButtonDefaults.buttonColors(),
) {
Text(text = "按钮111")
}
}
3.border 边框
边框就简单了,使用BorderStroke
,接收两个参数,一个是边框的宽度,另外一个则是边框的颜色
BorderStroke(1.dp,color = Color.Black)
Button(
onClick = { /*TODO*/ },
elevation = ButtonDefaults.elevation(4.dp, 10.dp, 0.dp),
shape = RoundedCornerShape(topStart = 5.dp,topEnd = 6.dp,bottomEnd = 10.dp,bottomStart = 10.dp),
border = BorderStroke(1.dp, Color.Green)
) {
Text(text = "边框按钮")
}
4.colors 颜色
可以通过下面的方法进行颜色的参数的设置
ButtonDefaults.buttonColors(backgroundColor,contentColor,disabledBackgroundColor,disabledContentColor)
-
backgroundColor
表示设置背景颜色 -
contentColor
表示设置内容颜色这里比如说是登录文本的颜色 -
disabledBackgroundColor
表示enable
等于false
的时候的背景颜色 -
disabledContentColor
表示enable
等于false
时候的内容的颜色
PS:这个和之前的一样,直接导包会报错,使用复制大法
ButtonDefaults.buttonColors()
解决
5.contentPadding 内容内边距
contentPadding参数接收一个PaddingValues对象,这个对象的构造方法如下:
PaddingValues(all)
PaddingValues(horizontal: Dp, vertical: Dp)
PaddingValues(start: Dp = 0.dp,top: Dp = 0.dp,end: Dp = 0.dp,bottom: Dp = 0.dp)
PaddingValues(10.dp) //所有内边距为10dp
PaddingValues(10.dp,20.dp) //左右内边距ge10dp,上下内边距各20dp
PaddingValues(10.dp,15.dp,20.dp,25.dp) //左内边距10dp,上内边距15dp,右内边距20dp,下内边距25dp
6.interactionSource 状态变化
这个主要是用来按钮的状态说明,我们可以使用这个来达到动态切换按钮样式的效果(如按下按钮的样式效果,松开后按钮的样式),类似我们之前常用selector
的xml文件给按钮设置样式
可以处理状态的,比如按下的时候什么效果,正常时候什么效果。类似之前再布局文件里写Selector
interactionSource是一个接口,我们需要使用其的实现类MutableInteractionSource
MutableInteractionSource中提供了三个属性用来获取状态
-
collectIsPressedAsState
按压状态 -
collectIsDraggedAsState
拖动状态 -
collectIsFocusedAsState
焦点状态
我们可以可以此状态来动态更改按钮的样式,如下面的代码
@Preview(showBackground = true)
@Composable
fun DefaultPreview2() {
val myInteractionSource = remember {
MutableInteractionSource()
}
val pressState = myInteractionSource.collectIsPressedAsState()
//如果是按压状态则是切角形状,否则则是圆角形状
val myShape = if(pressState.value) CutCornerShape(10.dp) else RoundedCornerShape(10.dp)
Column(
Modifier.padding(20.dp)
) {
Button(
onClick = { /*TODO*/ },
//设置我们定义的shape
shape = myShape,
//设置创建的MutableInteractionSource对象
interactionSource = myInteractionSource
) {
Text("你好")
}
}
}
效果如下(要按住才会变化):
补充:
构造一个可观察的状态对象可以使用下面的三种方法,唯一有所区别的是,返回值不一样
val mutableState = remember { mutableStateOf(default) }
var value by remember { mutableStateOf(default) }
val (value, setValue) = remember { mutableStateOf(default) }
如下面有个例子:
val mutableState = remember { mutableStateOf("") } //mutableState是State<String>对象
var value by remember { mutableStateOf("") } //value是String对象
val (value, setValue) = remember { mutableStateOf("") }
一般选用by关键字的那种,代码就比较方便,如果是第一种的话,需要通过mutableState.value
才能拿到其保存的数值
这里强烈建议看下官方的文档状态和Jetpack Compose
图标按钮IconButton
IconButton 可以帮助我们生成一个可点击的图标按钮,点击按钮默认会有水波涟漪的点击效果
IconButton(onClick = { /*TODO*/ }) {
Icon(Icons.Filled.Search, null)
}
其实这里里面也可以传多个组件,但是效果可能会变得怪怪的,所以我们就是按照规范来使用吧
TextButton
这个其实是扁平按钮,之前有个FlatButton
,然后改名成这个了,用法和Button一样,就是样式有所调整
TextButton(onClick = { /*TODO*/ }) {
Icon(Icons.Default.Search,contentDescription = null)
Text(text = "测试")
}
OutlinedButton
这个的话,看效果觉得应该是带有边框的按钮,我们也可以根据实际需求改造
OutlinedButton(onClick = { /*TODO*/ }) {
Text(text = "测试")
}
TextField
TextField在第一篇登录页面也是有提及到,这里再深入了解下各个属性
TextField 实现分为两个级别:
1.TextField 是 Material Design 实现。我们建议您选择此实现,因为它遵循的是 Material Design 指南:
- 默认样式为填充
OutlinedTextField
是轮廓样式版本
2.BasicTextField 允许用户通过硬件或软件键盘编辑文字,但没有提供提示或占位符等装饰
TextField(value = "", onValueChange = {},label = {Text("用户名")})
OutlinedTextField(value = "", onValueChange = {},label = {Text("用户名")})
BasicTextField(value = "", onValueChange = {})
简单来说,就是BasicTextField
是超级原生的输入框,其什么样式都没有,可以让我们进行高度的自定义,而TextField
和OutlinedTextField
则是Android官方给我们封装好Material Design样式的控件
1.label
获得输入焦点,顶头的文字提示,接收一个组件的lambda
表达式,一般传Text
,示例代码如下
TextField(value = "", onValueChange = {},label = {Text("用户名")})
效果如下图所示
2.leadingIcon
输入框左边显示内容,leadingIcon
接收来自一个组件的lambda
表达式,可以是图标、文本或者其他组件
TextField(
value = text,
onValueChange = {
text = it
},
leadingIcon = {
Icon(Icons.Filled.Search, null)
},
)
3.trailingIcon
输入框右边的内容,和上面的leadingIcon一样的使用,这里不再赘述
PS:可以在右边放个x的图标,点击删除输入全部文本功能哦 ??
4.singleLine
设置是否单行,接收一个boolean值
注: 此参数不能和
maxLines
参数联用
TextField(
value = text,
onValueChange = {
text = it
},
singleLine =true,
)
5.color
设置各种颜色,参数如下(参数真的多,应该够灵活了吧??)
@Composable
fun textFieldColors(
// 输入的文字颜色
textColor: Color = LocalContentColor.current.copy(LocalContentAlpha.current),
// 禁用 TextField 时,已有的文字颜色
disabledTextColor: Color = textColor.copy(ContentAlpha.disabled),
// 输入框的背景颜色,当设置为 Color.Transparent 时,将透明
backgroundColor: Color = MaterialTheme.colors.onSurface.copy(alpha = BackgroundOpacity),
// 输入框的光标颜色
cursorColor: Color = MaterialTheme.colors.primary,
// 当 TextField 的 isError 参数为 true 时,光标的颜色
errorCursorColor: Color = MaterialTheme.colors.error,
// 当输入框处于焦点时,底部指示器的颜色
focusedIndicatorColor: Color = MaterialTheme.colors.primary.copy(alpha = ContentAlpha.high),
// 当输入框不处于焦点时,底部指示器的颜色
unfocusedIndicatorColor: Color = MaterialTheme.colors.onSurface.copy(alpha = UnfocusedIndicatorLineOpacity),
// 禁用 TextField 时,底部指示器的颜色
disabledIndicatorColor: Color = unfocusedIndicatorColor.copy(alpha = ContentAlpha.disabled),
// 当 TextField 的 isError 参数为 true 时,底部指示器的颜色
errorIndicatorColor: Color = MaterialTheme.colors.error,
// TextField 输入框前头的颜色
leadingIconColor: Color = MaterialTheme.colors.onSurface.copy(alpha = IconOpacity),
// 禁用 TextField 时 TextField 输入框前头的颜色
disabledLeadingIconColor: Color = leadingIconColor.copy(alpha = ContentAlpha.disabled),
// 当 TextField 的 isError 参数为 true 时 TextField 输入框前头的颜色
errorLeadingIconColor: Color = leadingIconColor,
// TextField 输入框尾部的颜色
trailingIconColor: Color = MaterialTheme.colors.onSurface.copy(alpha = IconOpacity),
// 禁用 TextField 时 TextField 输入框尾部的颜色
disabledTrailingIconColor: Color = trailingIconColor.copy(alpha = ContentAlpha.disabled),
// 当 TextField 的 isError 参数为 true 时 TextField 输入框尾部的颜色
errorTrailingIconColor: Color = MaterialTheme.colors.error,
// 当输入框处于焦点时,Label 的颜色
focusedLabelColor: Color = MaterialTheme.colors.primary.copy(alpha = ContentAlpha.high),
// 当输入框不处于焦点时,Label 的颜色
unfocusedLabelColor: Color = MaterialTheme.colors.onSurface.copy(ContentAlpha.medium),
// 禁用 TextField 时,Label 的颜色
disabledLabelColor: Color = unfocusedLabelColor.copy(ContentAlpha.disabled),
// 当 TextField 的 isError 参数为 true 时,Label 的颜色
errorLabelColor: Color = MaterialTheme.colors.error,
// Placeholder 的颜色
placeholderColor: Color = MaterialTheme.colors.onSurface.copy(ContentAlpha.medium),
// 禁用 TextField 时,placeholder 的颜色
disabledPlaceholderColor: Color = placeholderColor.copy(ContentAlpha.disabled)
)
代码使用:
TextField(
value = text,
onValueChange = {
text = it
},
leadingIcon = {
Icon(Icons.Filled.Search, null)
},
colors = TextFieldDefaults.textFieldColors(
textColor = Color(0xFF0079D3),
backgroundColor = Color.Transparent
)
)
效果:
6.visualTransformation 视图变化
视图变化是我自己翻译出来的,也不知道准不准确,个人更倾向于理解成输入类型(inputType) ??
这个有点类似之前原生的inputType,可以改变输入的字符串(如密码或者是输入手机号时候多个-
),不过官方目前只实现了PasswordVisualTransformation
,其他的需要我们自定义
使用的话也很简单
var inputText by remember { mutableStateOf("") }
TextField(value = inputText, onValueChange = {value-> inputText= value},visualTransformation = PasswordVisualTransformation())
我们如果想实现Android那种带有个图标,点击可以显示密码的输入框,该怎么实现呢?
其实也很简单,设置个可观察的boolean值,点击图标改变数值即可,具体可参考下面代码
//密码内容
var inputText by remember { mutableStateOf("") }
//是否展示密码(默认是false)
var isShowPwd by remember { mutableStateOf(false) }
//显示效果(true:显示内偶然你 false:显示密码的"*"好
val myVisualTransformation =
if (isShowPwd) VisualTransformation.None else PasswordVisualTransformation()
TextField(
value = inputText,
colors=TextFieldDefaults.textFieldColors(backgroundColor = Color.Transparent),
onValueChange = { value -> inputText = value },
visualTransformation = myVisualTransformation,
trailingIcon = {
//根据表示不同,显示不同的图标
if (isShowPwd) {
//当前是显示密码,则图标为眼睛
IconButton(onClick = {
//更改标志
isShowPwd = !isShowPwd
}) {
Icon(painter = painterResource(id = R.drawable.eye_show), null)
}
} else {
//当前是隐藏密码,则图标为眼睛禁止
IconButton(onClick = {
//更改标志
isShowPwd = !isShowPwd
}) {
Icon(painter = painterResource(id = R.drawable.eye_hide), null)
}
}
})
上面的两个图标是我自己去iconfont-阿里巴巴矢量图标库上找,效果如下:
补充(自定义VisualTransformation)
注意: 经过实践发现,这个只是改变了显示的数值而已??,实际上你输入什么,保存的数值还是那个,单纯只是TextField没显示而已,不是很清楚这个操作,那这样是不能实现限制长度的功能,密码显示星号这种效果应该没啥问题
此功能有待讨论,或者是可能官方后面会更新长度限制等功能?
上面说到官方只实现了一个简单的密码输入类型,那如果我们想自定义该如何实现呢?
好在官方也是在API文档中给了个例子,可以实现输入信用卡号,以-
隔开的效果
我们先看下官方的代码及效果(有点坑,官方只给出了一部分代码,稍微琢磨了一番才知道它是实现了VisualTransformation
接口,并重写了filter()
方法)
class CardVisualTransformation : VisualTransformation{
override fun filter(text: AnnotatedString): TransformedText {
// Making XXXX-XXXX-XXXX-XXXX string.
val trimmed = if (text.text.length >= 16) text.text.substring(0..15) else text.text
var out = ""
for (i in trimmed.indices) {
out += trimmed[i]
if (i % 4 == 3 && i != 15) out += "-"
}
/**
* The offset translator should ignore the hyphen characters, so conversion from
* original offset to transformed text works like
* - The 4th char of the original text is 5th char in the transformed text.
* - The 13th char of the original text is 15th char in the transformed text.
* Similarly, the reverse conversion works like
* - The 5th char of the transformed text is 4th char in the original text.
* - The 12th char of the transformed text is 10th char in the original text.
*/
val creditCardOffsetTranslator = object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
if (offset <= 3) return offset
if (offset <= 7) return offset + 1
if (offset <= 11) return offset + 2
if (offset <= 16) return offset + 3
return 19
}
override fun transformedToOriginal(offset: Int): Int {
if (offset <= 4) return offset
if (offset <= 9) return offset - 1
if (offset <= 14) return offset - 2
if (offset <= 19) return offset - 3
return 16
}
}
return TransformedText(AnnotatedString(out), creditCardOffsetTranslator)
}
}
之后我们将TextField设置为上面的对象,代码如下
//密码内容
var inputText by remember { mutableStateOf("") }
//我们定义的卡号VisualTransformation
val myVisualTransformation = CardVisualTransformation()
TextField(
value = inputText,
label={
Text(text = "卡号")
},
colors=TextFieldDefaults.textFieldColors(backgroundColor = Color.Transparent),
onValueChange = { value -> inputText = value },
visualTransformation = myVisualTransformation
)
效果如下:
可以看见,每输入4个字符,后面会自动加上-
,且输入了16个字符后,就无法继续输入了,删除的时候,也会自动将-
删除,我们分析下代码
//这里的text是之前提到的AnnotatedString类型
//最大程度过滤,只要16个字符,大于16个字符,后面的字符就忽略掉
val trimmed = if (text.text.length >= 16) text.text.substring(0..15) else text.text
//TextFiel显示的数据,满足条件即追加"-"
var out = ""
for (i in trimmed.indices) {
out += trimmed[i]
//最后个字符不需要加"-"(即中间每隔四个字符追加"-")
if (i % 4 == 3 && i != 15) out += "-"
}
接下来是实现了一个接口OffsetMapping
官方文档关于此类说明: 提供原始文本和转换文本(transformed text)之间的双向偏移映射
看到这里,相信各位对原理已经有了一定的了解,VisualTransformation
这个类其实就是将原始文本转为转换文本,所以我们看到filter(text: AnnotatedString)
最后是返回的一个TransformedText
对象
val creditCardOffsetTranslator = object : OffsetMapping {
//原始文本对应的转换文本的下标映射
override fun originalToTransformed(offset: Int): Int {
if (offset <= 3) return offset
if (offset <= 7) return offset + 1
if (offset <= 11) return offset + 2
if (offset <= 16) return offset + 3
//转换文本最大长度为19
return 19
}
//转换文本对应的原始文本下标映射
override fun transformedToOriginal(offset: Int): Int {
//4 9 14都是"-"的下标位置
if (offset <= 4) return offset
if (offset <= 9) return offset - 1
if (offset <= 14) return offset - 2
if (offset <= 19) return offset - 3
//原始文本的最大长度为16
return 16
}
}
映射这里稍微想下就明白了,如有个
abcdefgh
,其对应的转换文本就为abcd-efgh
,其中,a-d
是下标没变,都对应得上,但从e
开始,由于多了个-
,所以原始文本中e
的下标为4
,而在转换文本中,e
的下标变为了5
,后面的以此类推,反过来也是同理
我们根据官方的,改下手机号的,代码如下
class PhoneVisualTransformation : VisualTransformation{
override fun filter(text: AnnotatedString): TransformedText {
val trimmed = if (text.text.length >= 11) text.text.substring(0,11) else text.text
var out = ""
for (i in trimmed.indices) {
out += trimmed[i]
if (i==2 || i==6 ) out += "-"
}
// 147-9611-2406
// 14796112406
val creditCardOffsetTranslator = object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
if (offset <= 2) return offset
if(offset<=6) return offset + 1
if (offset <= 11) return offset + 2
return 13
}
override fun transformedToOriginal(offset: Int): Int {
if (offset <= 3) return offset
if (offset <= 8) return offset - 1
if (offset <= 14) return offset - 2
return 11
}
}
return TransformedText(AnnotatedString(out), creditCardOffsetTranslator)
}
}
效果如下所示:
参考
- TextField - Jetpack Compose
- Jetpack Compose - Button_乐翁龙-CSDN博客
- Compose Button - 简书
- Jetpack Compose Button,IconButton等各种Button的讲解 - 掘金
- API文档——PasswordVisualTransformation
- 状态和Jetpack Compose
Jetpack Compose学习(3)——图标(Icon) 按钮(Button) 输入框(TextField) 的使用