Jetpack Compose | 控件篇(一) -- Text、TextField、Button
这一篇,我们一起学习 Jetpack Compose 中的部分控件 --
Text、TextField、Button
,掌握其使用方式和特性。
文中代码均基于 1.0.1版本
如无特殊说明,下文中的
Compose
均指代
Jetpack compose
上一篇文章:
Compose | 一文理解神奇的Modifier
在郭婶的号上有读者评论到不知道Compose从何学起,
无从下手,这里简单的谈一谈我的看法:
Compose 以及 Jetpack Compose 对于Android从业人员而言确实是一门
全新的技术
目前才兴起,还没有达到一个辉煌的阶段
在此背景下,开始学习研究Compose 都可以算的上
先行者
,而且这门技术还做不到诸如:"一年内全面替代老技术"、"不掌握就找不到工作" 这种程度。
那么按部就班的学习它就行了,没有时间上的紧迫感。而学习一样东西,有这样几个阶段:
掌握如何使用
掌握实现细节,逐渐理解其本质
从其本质出发,利用对程序的理解,优化使用方式甚至优化这门技术
显然,我们现在要做的就是:
先结合官方资料以及源码,先掌握如何使用:
它解决哪些问题,怎么使用它解决问题
鉴于部分读者已经有一定的基础,对类似技术有了一定的理解,在此过程中就可以提前阅读该技术的实现细节,尝试理解其本质。或者再掌握了使用方式后再开始。
再之后的道路就很清晰了
下面我们进入今天的正文。
在Android中,有
TextView
这一控件,用于展示文本,Compose中对应的是
Text
。
先看源码:
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,
maxLines: Int = Int.MAX_VALUE,
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current
fun Text(
text: AnnotatedString,
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,
maxLines: Int = Int.MAX_VALUE,
inlineContent: Map<String, InlineTextContent> = mapOf(),
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current
这两个方法原型的唯一差别就是形参 text
的类型,AnnotatedString 类似于Android中的 SpannableString, 可以标记各类效果。
其实阅读源码后可以发现,Text 基于 BasicText 实现,应用了样式。
val textColor = color.takeOrElse {
style.color.takeOrElse {
LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
val mergedStyle = style.merge(
TextStyle(
color = textColor,
fontSize = fontSize,
fontWeight = fontWeight,
textAlign = textAlign,
lineHeight = lineHeight,
fontFamily = fontFamily,
textDecoration = textDecoration,
fontStyle = fontStyle,
letterSpacing = letterSpacing
BasicText(
text,
modifier,
mergedStyle,
onTextLayout,
overflow,
softWrap,
maxLines,
inlineContent
参数含义:(翻译自API文档)
text - 要显示的内容
modifier - 需要应用的修饰器.
color - 文字色. 如果是 Color.Unspecified
, 同时 style 没有配饰颜色, 将会使用 LocalContentColor
.
fontSize - 字号. See TextStyle.fontSize.
fontStyle - 文字样式,例如斜体,See TextStyle.fontStyle.
fontWeight - 字重,例如加粗.
fontFamily - 字体系列. See TextStyle.fontFamily.
letterSpacing - 字间距. See TextStyle.letterSpacing.
textDecoration - 文字装饰效果,例如下划线. See TextStyle.textDecoration.
textAlign - 文字段落对齐方式. See TextStyle.textAlign.
lineHeight - 行高. See TextStyle.lineHeight.
overflow - 溢出时的处理方案,所谓溢出即文本框显示不下这么多文字.
softWrap - 是否应用换行符. 如果不应用,则一行写完,overflow
、TextAlign
无效.
maxLines - 最大行数,必须大于0.
inlineContent - 占位的替代信息匹配
onTextLayout - 绘制文字计算布局时的回调
style - 样式,例如: color, font, line height 等.
WorkShop 中按照这些参数编写了一些样例代码,效果如下,因过度图片压缩导致有锯齿感,非Compose问题
考虑到阅读体验,代码请移步WorkShop
TextField
Android中有 EditText
控件,用于接收 用户的文本输入
,Compose中为 TextField
和 TextField
类似的还有 OutlinedTextField
,使用上和 TextField
一致,多一个描边外框效果
方法原型:
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,
visualTransformation: VisualTransformation = VisualTransformation.None,
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()
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,
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()
很巧,和Text类似,除了value 和 onValueChange 的类型不一致,其他均一致。
简单追溯代码后可以发现:
和Android不一致,它并没有依赖Text的实现,而Android中 Edittext 继承自 TextView
TextField 同样是结合 较为通用的设计
组合而成的一个控件,并不仅仅只有文字相关部分
本篇注重于学习如何使用,故而略去源码部分
参数含义:
value: TextFieldValue
输入框中要显示的文本,包含了输入框编辑状态的信息,这个功能很强大,可以用来更新文本,光标等,然后还可以从其他位置直接观察到这些值的变化。也就是相当于双向绑定的意思;
value: 显示的文本
onValueChange: 更新后的回调
modifier:修饰器
enabled:是否可用,如果为false,将不可选中,不可输入,呈现出禁用状态
readOnly:是否只读,如果是true,则不可编辑,但是可以选中,可以触发复制
textStyle: 文字样式,前文中Text的诸多参数亦用于构建TextStyle
label: 显示在文本字段内的可选标签,未获得焦点时呈现
placeholder: 获得焦点时的默认呈现 类似Tint的效果
leadingIcon: 输入框前部的图标;
trailingIcon: 输入框后部的图标;
isError: 输入内容是否错误,如果为true,则label,Icon等会相应的展示错误的显示状态;
visualTransformation: 内容显示转变,例如输入密码时可以变成特定效果
keyboardOptions: 软件键盘选项
keyboardActions: ImeAction
singleLine: 是否单行输入
maxLines:最大行数,需要≥1。如果将singleLine设置为true,则将忽略此参数,
interactionSource: 目前的知识体系暂不深入
shape: 输入框的形状
colors: 各种状态下的颜色 类似Android的ColorStateList
TextField(
value = "文字",
onValueChange = {
如果我们测试这样一段代码,会发现无论输入什么,显示内容都不会改变。Compose 需要我们在外部维护状态
一个有效的输入框代码示例:
var text by rememberSaveable { mutableStateOf("文字") }
TextField(
value = text,
onValueChange = {
text = it
如果读者仔细的观察一下,会发现这里的value 依旧对应 String 类型!这里充分利用了Delegate的特性!!
接下来,我们尝试一下几个有趣的属性。
而下述的一些简单属性,相信读者已经心中有数,就不在WorkShop中演示了:
modifier
enabled
readOnly
textStyle
visualTransformation
singleLine
maxLines
shape
colors
label & placeholder
var text by rememberSaveable { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
label = { Text("Label") },
placeholder = { Text("PlaceHolder") }
未获得焦点时,显示label:这里输入的是啥,获取焦点后,label缩小,如果没有初始值,则显示PlaceHolder,否则初始文字
PlaceHolder:输入示例
效果在章节末呈现
leadingIcon&trailingIcon
var text by rememberSaveable { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
placeholder = { Text("PlaceHolder") },
leadingIcon = { Icon(Icons.Filled.Favorite, contentDescription = "Favorite") },
trailingIcon = { Icon(Icons.Filled.Clear, contentDescription = "Clear",modifier = Modifier.clickable {
text = ""
如果在Android原生SDK下,做法可以是:
完全自定义View -- 完全通过继承
继承ViewGroup或者特定ViewGroup,内部通过组合控件方式 反射xml布局或者代码构建 实现逻辑 -- 继承 + 组合
定义类,内部通过组合控件方式 反射xml布局或者代码构建 实现逻辑 -- 组合
每种做法都有自身的优势和劣势,但代码量都会很多
这个例子下我们还无法去讨论 组合与继承的优劣对比
,但代码量的感性对比非常明显
isError & keyboardActions & 输入校验
var text by rememberSaveable { mutableStateOf("") }
var isError by rememberSaveable { mutableStateOf(false) }
fun validate(text: String) {
isError = text.count() < 5
TextField(
value = text,
onValueChange = {
text = it
isError = false
singleLine = true,
label = { Text(if (isError) "Email*" else "Email") },
isError = isError,
keyboardActions = KeyboardActions { validate(text) },
modifier = Modifier.semantics {
// Provide localized description of the error
if (isError) {
Toast.makeText(this@P26TextFieldSample,"输入错误",Toast.LENGTH_SHORT).show()
代码含义清晰明了
上述例子的效果
读者可以clone项目后自行体验
相信有读者已经在思考Compose是如何实现 双向绑定
的了,按照我们的学习计划,这将在后续的文章中展开。
相信看到这里,有读者已经在思考一个问题了:
Modifier 中有点击相关的内容,为什么还需要有Button呢?它真的是一个视图控件吗?还是一个特定的、带有点击效果的样式组合?
其实 Button
在人机交互中,是一个类似 隐喻
的存在,指代点击可触发特定行为的交互区,在设计发展中,
逐渐形成了一些约定:
可触发和不可触发的状态要可识别
从其所在环境中可以被轻易地识别出来
点击或者按压要有视觉反馈效果
所以样式上是一个不可忽略的侧重点。但是也要客观的承认一点:中式UI和欧美UI确实不是一个风格
,所以多数情况下我们会修改掉默认效果。
看一下方法原型:
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: RowScope.() -> Unit
): @Composable Unit
onClick: 点击事件回调
modifier: 修饰器
enabled:是否可点击触发
interactionSource:
elevation: z轴投影效果
shape: button和投影的形状
border: 描边
colors: 背景色、内容色、各个状态配色
contentPadding: 容器和内容的间距
content: 内容
一个最简单的文字按钮:
Button(
onClick = {
toast("onClick")
modifier = Modifier.clickable {
toast("Modifier.onClick")
Text(
text = "Button",
显然,点击生效的是 onClick
的回调函数。
在 后面的效果图 或者 运行WorkShop后 可发现,这个文字Button已经运用了许多样式
Button的样式部分,读者可自行编码探索实践一二,可以很轻易的和Android原生内容对应上,不再展开。
前文的方法原型中,content: RowScope.() -> Unit
显然可以包含更多的东西。Row 的布局特性会在后续文章展开
Button(
onClick = {
toast("onClick")
Icon(
Icons.Filled.Favorite,
contentDescription = "Favorite"
Text(
text = "Button",
可以在文字左边放置一个 Favorite
图标
结合样式的衍生物
而Compose中,还有一些内容,代表着Button的操作含义,但有更特殊的样式含义,例如:
OutlinedButton:有边线的Button, 但非实质的,借用Android原生的内容比喻:有Stroke效果,无Solid效果
IconButton:显示一个Icon的button 但编码上未强制约束
IconToggleButton:两个状态图标的icon,相互切换,例如:收藏、取消收藏,表现含义上有别于 Switch
,表现类似无文字的 CheckBox
fun OutlinedButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
elevation: ButtonElevation? = null,
shape: Shape = MaterialTheme.shapes.small,
border: BorderStroke? = ButtonDefaults.outlinedBorder,
colors: ButtonColors = ButtonDefaults.outlinedButtonColors(),
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
content: @Composable RowScope.() -> Unit
和Button参数含义一致
fun IconButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable () -> Unit
参数含义参考Button
适用场景如返回键、关闭按钮等
IconButton(
onClick = {
toast("onClick")
Icon(
imageVector = Icons.Filled.Favorite,
contentDescription = "Favorite"
fun IconToggleButton(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable () -> Unit
checked:默认状态
onCheckedChange:状态变化后的回调
适用场景如:收藏、取消收藏等
val checkedState = remember { mutableStateOf(true) }
IconToggleButton(
checked = checkedState.value,
onCheckedChange = {
checkedState.value = it
Icon(
imageVector = Icons.Filled.Favorite,
contentDescription = "Favorite",
tint = if (checkedState.value) {
Color.Red
} else {
Color.Gray
上述所有内容的效果:
在本篇文章中,我们一起学习了Compose的部分基础内容,这些内容学起来也比较枯燥,但适应了Compose之后,学习这些基础内容就会越来越快。
读者可以结合 WorkShop 实践一波,加深印象!
我们下一篇见。
2021-08-23 23:34:13