Jetpack Compose 是什么
Jetpack Compose是Google推出的一個新的UI工具包富岳,旨在幫助開發(fā)者更快谨垃、更輕松地在Android 平臺上構(gòu)建Native應(yīng)用。Jetpack Compose是一個聲明式的UI框架墓拜,它提供了現(xiàn)代化的聲明式Kotlin API(取代Android 傳統(tǒng)的xml布局)港柜,可幫助開發(fā)者用更少的代碼構(gòu)建美觀、響應(yīng)迅速的應(yīng)用程序。
2019 年夏醉,Google 在 I/O 大會上公布了 Android 最新的 UI 框架:Jetpack Compose爽锥。Compose 可以說是 Android 官方有史以來動作最大的一個庫了。它在 2019 年中就公布了畔柔,但要到今年也就是 2021 年才會正式發(fā)布氯夷。這兩年的時間 Android 團隊在干嘛?在開發(fā) Compose靶擦。一個 UI 框架而已腮考,為什么要花兩年來打造呢?因為 Compose 并不是像 RecyclerView玄捕、ConstraintLayout 這種做了一個或者幾個高級的 UI 控件踩蔚,而是直接拋棄了我們寫了 N 年的 View 和 ViewGroup 那一套東西,從上到下擼了一整套全新的 UI 框架枚粘。直白點說就是馅闽,它的渲染機制、布局機制馍迄、觸摸算法以及 UI 的具體寫法福也,全都是新的。
基于View UI體系有哪些痛點
歷史包袱攀圈,10多個大版本的迭代暴凑,View類已經(jīng)3w多行,而絕大部分的UI控件都繼承于View赘来。意味你寫一個按鈕或者一個TextView都會受這個父類影響搬设,繼承了很多沒有用到的特性和功能;
解析xml的額外開銷撕捍,而且需要反射創(chuàng)建對象 拿穴;
預(yù)覽和Reload不方便,和Flutter毫秒級的hot reload完全不能比忧风;
布局嵌套層級過深導(dǎo)致的性能問題默色,比如LinearLayout 二次測量或者三次測量問題。
Compose特點
聲明式
上面有一個詞:聲明式 狮腿,那么什么是聲明式腿宰?假設(shè)我們需要在界面 上顯示一個文本
命令式方式:
1、首先需要一個xml文件缘厢,里面有一個TextView
...
<TextView
android:id="@+id/my_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
2吃度、通過findViewById獲取到TextView控件
TextView textView = findViewById<TextView>(R.id.my_text);
3、通過setText()更新數(shù)據(jù)贴硫,顯示到界面
textView.setText(content);
聲明式方式:
@Composable
fun Greeting() {
val count = remember { mutableStateOf(0) }
Column{
Button(onClick = { count.value++ }) {
Text("I've been clicked ${count.value} times")
}
}
}
為什么第一種方式是命令式椿每,第二種方式是聲明式伊者?主要體現(xiàn)在界面更新上,命令式下:數(shù)據(jù)更新時间护,Java代碼手動調(diào)用xml組件引用來更新界面亦渗,也就是Java代碼命令xml界面更新,這就是命令方式汁尺。而聲明式呢法精?只描述界面,當(dāng)數(shù)據(jù)狀態(tài)更新時痴突,自動更新界面搂蜓,這就是聲明式。
簡短總結(jié):
命令式是操作界面 (How)辽装;
聲明式是描述界面 (What)帮碰。
除了Jetpack Compose ,F(xiàn)lutter如迟,React-Native,Swift-UI 都是聲明式的攻走,這也是現(xiàn)在的一種趨勢殷勘。
強大的UI預(yù)覽能力
頂層函數(shù)
Compose是一個聲明式UI系統(tǒng),其中昔搂,我們用一組函數(shù)來聲明UI玲销,并且一個Compose函數(shù)可以嵌套另一個Compose函數(shù),并以樹的結(jié)構(gòu)來構(gòu)造所需要的UI摘符。在此過程中贤斜,Compose函數(shù)始終根據(jù)接收到的輸入生成相同的UI,因此逛裤,放棄類結(jié)構(gòu)不會有任何害處瘩绒。從類結(jié)構(gòu)構(gòu)建UI過渡到頂層函數(shù)構(gòu)建UI對開發(fā)者和Android 團隊都是一個巨大的轉(zhuǎn)變。
@Composable
fun checkbox ( ... ) //錯誤的命名带族,應(yīng)該大寫開頭
@Composable
fun TextView ( ... )
@Composable
fun Edittext ( ... )
@Composable
fun Image ( ... )
Jetpack Compose首選組合而不是繼承锁荔,Android中的幾乎所有組件都繼承于View類(直接或間接繼承)。比如EidtText 繼承于TextView蝙砌,而同時TextView又繼承于其他一些View,這樣的繼承結(jié)構(gòu)最終會指向跟View
而Compose團隊則將整個系統(tǒng)從繼承轉(zhuǎn)移到了頂層函數(shù)阳堕。 Textview , EditText 择克, 復(fù)選框 和所有UI組件都是 它們自己的Compose函數(shù)恬总,而它們構(gòu)成了要創(chuàng)建UI的其他函數(shù),代替了從另一個類繼承肚邢。
重組
在命令式界面模型中壹堰,如需更改某個微件,您可以在該微件上調(diào)用 setter 以更改其內(nèi)部狀態(tài)。在 Compose 中缀旁,您可以使用新數(shù)據(jù)再次調(diào)用可組合函數(shù)记劈。這樣做會導(dǎo)致函數(shù)進行重組 -- 系統(tǒng)會根據(jù)需要使用新數(shù)據(jù)重新繪制函數(shù)發(fā)出的微件。Compose 框架可以智能地僅重組已更改的組件并巍。重組整個界面樹在計算上成本高昂目木,Compose 使用智能重組來解決此問題。
重組是指在輸入更改時再次調(diào)用可組合函數(shù)的過程懊渡,Compose 可以高效地重組刽射。
可組合函數(shù)可能會像每一幀一樣頻繁地重新執(zhí)行,例如在呈現(xiàn)動畫時剃执∈慕可組合函數(shù)應(yīng)快速執(zhí)行,以避免在播放動畫期間出現(xiàn)卡頓肾档。如果您需要執(zhí)行成本高昂的操作(例如從共享偏好設(shè)置讀取數(shù)據(jù))摹恰,請在后臺協(xié)程中執(zhí)行,并將值結(jié)果作為參數(shù)傳遞給可組合函數(shù)怒见。
當(dāng)您在 Compose 中編程時俗慈,有許多事項需要注意:
- 可組合函數(shù)可以按任何順序執(zhí)行;
- 可組合函數(shù)可以并行執(zhí)行遣耍;
- 重組會跳過盡可能多的可組合函數(shù)和 lambda闺阱;
- 重組是樂觀的操作,可能會被取消舵变;
- 可組合函數(shù)可能會像動畫的每一幀一樣非常頻繁地運行酣溃。
示例
和flutter比較像,https://flutter.cn/docs/development/ui/widgets-intro
/**
* Colume , Row ,Box
*/
@Preview(showBackground = true)
@Composable
fun DemoLayout() {
Row(
modifier = Modifier
.size(200.dp)
.background(Color.Yellow),
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically
) {
Box(
Modifier
.size(50.dp)
.background(Color.Red)
)
Box(
Modifier
.size(50.dp)
.background(Color.Blue)
)
Column(
Modifier
.size(100.dp)
.background(Color.Cyan)
) {
Text("Android")
Text(
"iOS"
)
Text(
"H5",
Modifier
.background(Color.Green),
fontSize = 15.sp
)
}
}
}
/**
* Text
*/
@Preview(showBackground = true)
@Composable
fun DemoText() {
val txt = remember { mutableStateOf(0) }
Text(
text = "${txt.value}",
Modifier
.background(Color.Magenta)
.size(200.dp, 200.dp)
.clickable(
enabled = true,
role = Role.Button
) {
txt.value += 1
},
fontStyle = FontStyle.Italic,
fontWeight = FontWeight(1000),
fontFamily = FontFamily.SansSerif,
letterSpacing = 10.sp,
textDecoration = TextDecoration.Underline,
textAlign = TextAlign.Center,
lineHeight = 20.sp,
maxLines = 3,
softWrap = true,
overflow = TextOverflow.Clip,
)
}
/**
* AppendText
*/
@Preview(showBackground = true)
@Composable
fun DemoAppendText() {
Text(
buildAnnotatedString {
withStyle(
style = SpanStyle(
color = Color.Blue,
fontWeight = FontWeight.Bold
)
) {
append("Jetpack ")
}
append("Compose ")
withStyle(
style = SpanStyle(
color = Color.Red,
fontWeight = FontWeight.Bold,
fontSize = 30.sp
)
) {
append("is ")
}
append("wonderful")
}
)
}
/**
* List
*/
@ExperimentalFoundationApi
@Preview(showBackground = true)
@Composable
fun DemoLazyColumn() {
Box {
val listState = rememberLazyListState()
LazyColumn(
Modifier.size(200.dp),
state = listState
) {
stickyHeader {
Text(text = "stickyHeader")
}
// Add a single item
item {
Text(text = "First item")
}
// Add 50 items
items(50) { index ->
Text(text = "Item: $index")
}
// Add another single item
item {
Text(text = "Last item")
}
}
val showButton by remember {
derivedStateOf {
listState.firstVisibleItemIndex > 10
}
}
Text(
text = if (showButton) {
"".plus(showButton)
} else {
"".plus(showButton)
},
modifier = Modifier
.size(30.dp)
.background(Color.Yellow)
)
}
}
/**
* Image, 圖片庫用coil : https://zhuanlan.zhihu.com/p/287752448
*/
@Preview(showBackground = true)
@Composable
fun ImageDemo() {
Image(
painter = painterResource(id = R.drawable.ic_launcher_background),
contentDescription = null,
)
}
/**
* Canvas
*/
@Preview(showBackground = true)
@Composable
fun CanvasDemo() {
Canvas(
modifier = Modifier
.height(300.dp)
.width(300.dp)
) {
val canvasWidth = size.width
val canvasHeight = size.height
drawCircle(
color = Color.Blue,
center = Offset(x = canvasWidth / 2, y = canvasHeight / 2),
radius = size.minDimension / 4
)
drawLine(
start = Offset(x = canvasWidth, y = 0f),
end = Offset(x = 0f, y = canvasHeight),
color = Color.Blue,
strokeWidth = 5F
)
drawLine(
start = Offset(x = 0f, y = 0f),
end = Offset(x = canvasWidth, y = canvasHeight),
color = Color.Blue,
strokeWidth = 5F
)
rotate(degrees = 45F) {
drawRect(
color = Color.Gray,
topLeft = Offset(x = canvasWidth / 3F, y = canvasHeight / 3F),
size = size / 3F
)
}
}
}
/**
* 手勢
*/
@Preview(showBackground = true)
@Composable
fun GestureDemo() {
Box(modifier = Modifier.fillMaxSize()) {
var offsetX by remember { mutableStateOf(0f) }
var offsetY by remember { mutableStateOf(0f) }
Box(
Modifier
.offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
.background(Color.Blue)
.size(50.dp)
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consumeAllChanges()
offsetX += dragAmount.x
offsetY += dragAmount.y
}
}
)
}
}
enum class BoxState { Collapsed, Expanded }
/**
* 動畫, https://developer.android.com/codelabs/jetpack-compose-animation#3
*/
@Preview(showBackground = true)
@Composable
fun AnimatingBox(boxState: BoxState = BoxState.Expanded) {
val transitionData = updateTransitionData(boxState)
// UI tree
Box(
modifier = Modifier
.background(transitionData.color)
.size(transitionData.size)
)
}
// Holds the animation values.
private class TransitionData(
color: State<Color>,
size: State<Dp>
) {
val color by color
val size by size
}
// Create a Transition and return its animation values.
@Composable
private fun updateTransitionData(boxState: BoxState): TransitionData {
val transition = updateTransition(boxState)
val color = transition.animateColor { state ->
when (state) {
BoxState.Collapsed -> Color.Gray
BoxState.Expanded -> Color.Red
}
}
val size = transition.animateDp { state ->
when (state) {
BoxState.Collapsed -> 32.dp
BoxState.Expanded -> 300.dp
}
}
return remember(transition) { TransitionData(color, size) }
}
遇到問題
1.如果要追蹤具體的實現(xiàn)纪隙,需要反編譯代碼赊豌;
2.Preview功能還需要進一步增強,由于要實現(xiàn)實時預(yù)覽绵咱,每次修改Compose都需要編譯亿絮,如果項目比較大,編譯時間很長麸拄,那體驗就會很差了派昧;
3.某些API設(shè)計上有些混淆,比如Text AlignText只能設(shè)置水平居中拢切;
4.引入Compose會帶來3M多的包大小蒂萎。
總結(jié)
聲明式UI使我們的代碼更加簡潔,這也是擁抱大前端一次很好的嘗試淮椰。Compose 確實是一套比較難學(xué)的東西五慈,因為它畢竟太新也太大了纳寂,它是一個完整的、全新的框架泻拦,確實讓很多人感覺學(xué)不動毙芜,那怎么辦呢?學(xué)唄
學(xué)習(xí)資料
2.View 嵌套太深會卡争拐?來用 Jetpack Compose腋粥,隨便套——Intrinsic Measurement
3.深入詳解 Jetpack Compose | 優(yōu)化 UI 構(gòu)建