初識Jetpack Compose

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ù)覽能力

image.png
image.png
image.png

頂層函數(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多的包大小蒂萎。


image.png

總結(jié)

聲明式UI使我們的代碼更加簡潔,這也是擁抱大前端一次很好的嘗試淮椰。Compose 確實是一套比較難學(xué)的東西五慈,因為它畢竟太新也太大了纳寂,它是一個完整的、全新的框架泻拦,確實讓很多人感覺學(xué)不動毙芜,那怎么辦呢?學(xué)唄

學(xué)習(xí)資料

1.Compose官網(wǎng)

2.View 嵌套太深會卡争拐?來用 Jetpack Compose腋粥,隨便套——Intrinsic Measurement

3.深入詳解 Jetpack Compose | 優(yōu)化 UI 構(gòu)建

4.官方視頻-Jetpack Compose Beta 版現(xiàn)已發(fā)布!

5.Jetpack Compose 使用前后對比

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末架曹,一起剝皮案震驚了整個濱河市隘冲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌绑雄,老刑警劉巖展辞,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異万牺,居然都是意外死亡罗珍,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門脚粟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來覆旱,“玉大人,你說我怎么就攤上這事珊楼⊥ㄑ辏” “怎么了度液?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵厕宗,是天一觀的道長。 經(jīng)常有香客問我堕担,道長已慢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任霹购,我火速辦了婚禮佑惠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘齐疙。我一直安慰自己膜楷,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布贞奋。 她就那樣靜靜地躺著赌厅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪轿塔。 梳的紋絲不亂的頭發(fā)上特愿,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天仲墨,我揣著相機與錄音,去河邊找鬼揍障。 笑死目养,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的毒嫡。 我是一名探鬼主播癌蚁,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼审胚!你這毒婦竟也來了匈勋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤膳叨,失蹤者是張志新(化名)和其女友劉穎洽洁,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體菲嘴,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡饿自,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了龄坪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昭雌。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖健田,靈堂內(nèi)的尸體忽然破棺而出烛卧,到底是詐尸還是另有隱情,我是刑警寧澤妓局,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布总放,位于F島的核電站,受9級特大地震影響好爬,放射性物質(zhì)發(fā)生泄漏局雄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一存炮、第九天 我趴在偏房一處隱蔽的房頂上張望炬搭。 院中可真熱鬧,春花似錦穆桂、人聲如沸宫盔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽灼芭。三九已至,卻和暖如春驼侠,著一層夾襖步出監(jiān)牢的瞬間姿鸿,已是汗流浹背谆吴。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留苛预,地道東北人句狼。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像热某,于是被迫代替她去往敵國和親腻菇。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 何為Compose 2019 年中昔馋,Google 在 I/O 大會上公布的用于Android構(gòu)建原生界面的全新 U...
    塞上牧羊空許約閱讀 585評論 0 2
  • 發(fā)表時間:2019.11.7 在前不久的 Android Dev Summit '19 上筹吐,Jetpack Com...
    Loong_T閱讀 1,055評論 1 1
  • 表情是什么,我認(rèn)為表情就是表現(xiàn)出來的情緒秘遏。表情可以傳達很多信息丘薛。高興了當(dāng)然就笑了,難過就哭了邦危。兩者是相互影響密不可...
    Persistenc_6aea閱讀 125,086評論 2 7
  • 16宿命:用概率思維提高你的勝算 以前的我是風(fēng)險厭惡者洋侨,不喜歡去冒險,但是人生放棄了冒險倦蚪,也就放棄了無數(shù)的可能希坚。 ...
    yichen大刀閱讀 6,052評論 0 4