這一篇,我們一起學習 Jetpack Compose 中的部分控制元件 – Text、TextField、Button,掌握其使用方式和特性,
文中代碼均基于 1.0.1版本
如無特殊說明,下文中的 Compose 均指代 Jetpack compose
小互動
上一篇文章:Compose | 一文理解神奇的Modifier 在郭嬸的號上有讀者評論到不知道Compose從何學起,
無從下手,這里簡單的談一談我的看法:
- Compose 以及 Jetpack Compose 對于Android從業人員而言確實是一門
全新的技術 - 目前才興起,還沒有達到一個輝煌的階段
在此背景下,開始學習研究Compose 都可以算的上 先行者,而且這門技識訓做不到諸如:“一年內全面替代老技術”、“不掌握就找不到作業” 這種程度,
那么按部就班的學習它就行了,沒有時間上的緊迫感,而學習一樣東西,有這樣幾個階段:
- 掌握如何使用
- 掌握實作細節,逐漸理解其本質
- 從其本質出發,利用對程式的理解,優化使用方式甚至優化這門技術
顯然,我們現在要做的就是:
- 先結合官方資料以及原始碼,先掌握如何使用:
它解決哪些問題,怎么使用它解決問題 - 鑒于部分讀者已經有一定的基礎,對類似技術有了一定的理解,在此程序中就可以提前閱讀該技術的實作細節,嘗試理解其本質,或者再掌握了使用方式后再開始,
- 再之后的道路就很清晰了
下面我們進入今天的正文,
Text
在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是如何實作
雙向系結的了,按照我們的學習計劃,這將在后續的文章中展開,
Button
相信看到這里,有讀者已經在思考一個問題了:
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
OutlinedButton
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引數含義一致
IconButton
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"
)
}
IconToggleButton
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 實踐一波,加深印象!
我們下一篇見,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/295206.html
標籤:其他
