Android Jetpack Compose

Android Jetpack Compose是谷歌推出的一種新的搭建UI的方式逞泄,使用Kotlin DSL的形式來組合UI組件焚挠。目前還在alpha階段。蘋果早也推出了類似的Swift UI,都是模仿前端的實現(xiàn)方式篮愉,目的都是UI更加輕量化和方便數(shù)據(jù)驅(qū)動拉鹃。

Compose使用感受

Compose的使用比較簡單考杉,官方有連續(xù)的課程矫付,還有比較完善的samples可以參考,網(wǎng)絡上也能找到好多相關(guān)的教程俯萌,就不再重復寫了果录。需要注意的是,Compose還在alpha階段咐熙,API還未穩(wěn)定弱恒,網(wǎng)上的教程好多是針對舊的API的,遇到問題還是盡量查看官方文檔棋恼。下面就簡單說說在使用Compose時的一些感受返弹。

好的地方:

  1. 相對于之前的xml方式,代碼編寫起來可能更加熟練爪飘,對開發(fā)可能更親切(個人感受)
  2. 一般的UI組件使用起來更加簡單义起,如Text:
 Text(
    "text",
    style = MaterialTheme.typography.body,
    modifier = Modifier.padding(start = 4.dp) 
)

相對于xml中聲明TextView,代碼量會小很多师崎。

  1. 可能是由于目前Compose提供的組件比較少默终,布局實現(xiàn)比較單一,感覺實現(xiàn)比較復雜的布局比XML高效,簡潔很多齐蔽。而且性能也比XML高两疚,畢竟少了xml解析的過程。
  2. 可以多個UI組件一起預覽肴熏。傳統(tǒng)方式一次只能預覽一個xml布局鬼雀,使用Compose之后顷窒,只要在Compose上添加@preview注解蛙吏,在一個文件內(nèi)都可以預覽。在搭建UI時幾乎可以不再需要安裝到真機和模擬器上進行檢查鞋吉。
  3. 數(shù)據(jù)驅(qū)動UI更新更加直觀和高效鸦做,只需要聲明state即可,數(shù)據(jù)改變時會自動更新UI谓着,不再需要之前的Livedata等比較復雜的機制泼诱。
  4. Theme更加強大和自由,以前在XML中聲明Theme和自定義主題中某個組件的樣式赊锚,是比較麻煩的治筒,因為屬性太多了。現(xiàn)在就比較方便了可以直接使用Material Design主題中定義好的樣式舷蒲,更加規(guī)范高效耸袜,而且深色主題適配更加簡單。
  5. 動畫API更加簡單了牲平,不再需要復雜的寫法堤框,會自動根據(jù)之前的屬性生成對應的動畫,如更改組件的大小纵柿,只需要在modifier中添加:
modifier.animateContentSize(animSpec = TweenSpec(300)),

就可以了蜈抓,系統(tǒng)會自動監(jiān)控這個組件的大小的變化,生成動畫昂儒。

不好的地方

  1. 由于使用的是Kotlin DSL沟使,所以代碼排版縮進會比較多,相對于一般代碼結(jié)構(gòu)渊跋,直觀性比較差腊嗡。但是還是比Flutter的好一些。
  2. 由于要實現(xiàn)實時預覽刹枉,每次修改Compose都需要編譯叽唱,如果項目比較大,編譯時間很長微宝,那體驗就會很差了
  3. 組件的豐富度還比較欠缺棺亭,需要進一步完善
  4. 官方的教程,文檔包含的內(nèi)容有限蟋软,有很多之前的組件找不到在Compose中對應的組件镶摘,命名也發(fā)生了變化嗽桩。尋找起來比較麻煩。大部門只能看官方的文檔凄敢。而且對復雜的界面布局的實現(xiàn)沒有比較完善的指導文檔碌冶。
  5. jetpack組件還在推廣中,Compose已經(jīng)和好多AndroidX的組件沖突了涝缝,如果Compose推進的比較快扑庞,那還有必要學習使用AndroidX中的UI和管理UI組件嗎? 一般的項目也不會想兩套共存吧? 所以如何推廣Compose還是個問題, 好處是不像iOS的Swift UI那樣,和系統(tǒng)版本綁定拒逮。

Compose的實現(xiàn)

最初看到Compose的時候罐氨,以為就是對之前的View組件做了一次封裝,然后底層再做組裝滩援,渲染處理栅隐。后來看了下源碼,發(fā)現(xiàn)不是這樣的玩徊,是重新實現(xiàn)了一套租悄。如Text的具體實現(xiàn)是CoreTextCoreText的layout實現(xiàn):

Layout(
        children = if (inlineComposables.isEmpty()) {
            emptyContent()
        } else {
            { InlineChildren(text, inlineComposables) }
        },
        modifier = modifier
            .then(controller.modifiers)
            .then(
                if (selectionRegistrar != null) {
                    Modifier.longPressDragGestureFilter(
                        longPressDragObserver(
                            state = state,
                            selectionRegistrar = selectionRegistrar
                        )
                    )
                } else {
                    Modifier
                }
            ),
        minIntrinsicWidthMeasureBlock = controller.minIntrinsicWidth,
        minIntrinsicHeightMeasureBlock = controller.minIntrinsicHeight,
        maxIntrinsicWidthMeasureBlock = controller.maxIntrinsicWidth,
        maxIntrinsicHeightMeasureBlock = controller.maxIntrinsicHeight,
        measureBlock = controller.measure
    ) 

TextController控制Text的layout恩袱、state泣棋、selection、measure和draw憎蛤。底層其實都是通過TextDelegate實現(xiàn)的

    fun layout(
        constraints: Constraints,
        layoutDirection: LayoutDirection,
        prevResult: TextLayoutResult? = null,
        respectMinConstraints: Boolean = false
    ): TextLayoutResult {
        val minWidth = if (respectMinConstraints || style.textAlign == TextAlign.Justify) {
            constraints.minWidth.toFloat()
        } else {
            0f
        }
        val widthMatters = softWrap || overflow == TextOverflow.Ellipsis
        val maxWidth = if (widthMatters && constraints.hasBoundedWidth) {
            constraints.maxWidth.toFloat()
        } else {
            Float.POSITIVE_INFINITY
        }

        if (prevResult != null && prevResult.canReuse(
                text, style, maxLines, softWrap, overflow, density, layoutDirection,
                resourceLoader, constraints
            )
        ) {
            return with(prevResult) {
                copy(
                    layoutInput = layoutInput.copy(
                        style = style,
                        constraints = constraints
                    ),
                    size = computeLayoutSize(constraints, multiParagraph, respectMinConstraints)
                )
            }
        }

        val multiParagraph = layoutText(
            minWidth,
            maxWidth,
            layoutDirection
        )

        val size = computeLayoutSize(constraints, multiParagraph, respectMinConstraints)
        return TextLayoutResult(
            TextLayoutInput(
                text,
                style,
                placeholders,
                maxLines,
                softWrap,
                overflow,
                density,
                layoutDirection,
                resourceLoader,
                constraints
            ),
            multiParagraph,
            size
        )
    }

layout最終返回一個TextLayoutResult外傅,TextLayoutResult在draw的時候使用:

fun paint(canvas: Canvas, textLayoutResult: TextLayoutResult) {
       TextPainter.paint(canvas, textLayoutResult)
}

最終調(diào)用了TextPainterTextPainter的paint方法:

 fun paint(canvas: Canvas, textLayoutResult: TextLayoutResult) {
        val needClipping = textLayoutResult.hasVisualOverflow &&
            textLayoutResult.layoutInput.overflow == TextOverflow.Clip
        if (needClipping) {
            val width = textLayoutResult.size.width.toFloat()
            val height = textLayoutResult.size.height.toFloat()
            val bounds = Rect(Offset.Zero, Size(width, height))
            canvas.save()
            canvas.clipRect(bounds)
        }
        try {
            textLayoutResult.multiParagraph.paint(
                canvas,
                textLayoutResult.layoutInput.style.color,
                textLayoutResult.layoutInput.style.shadow,
                textLayoutResult.layoutInput.style.textDecoration
            )
        } finally {
            if (needClipping) {
                canvas.restore()
            }
        }
    }

設計思想還是View的那套俩檬,但是所有組件的實現(xiàn)更加扁平了萎胰。不再有View或Viewgroup的繼承關(guān)系,大部分組件都是直接自己直接實現(xiàn)layout棚辽,measure和draw技竟。所以看起來更加簡潔。

State

Compose的更新數(shù)據(jù)顯示是通過State來實現(xiàn)的屈藐,而且比之前的LiveData更加簡單榔组,如:
數(shù)據(jù):

val list = listOf(
    "ListItem1“, 
    "ListItem2“,
    "ListItem3“,
    "ListItem4“,
    "ListItem5“
)

val data by remember{ mutableStateOf(list) }

UI:

LazyColumnFor(
     items = data,
     modifier = Modifier.weight(1f),
     contentPadding = PaddingValues(top = 8.dp))
{ text ->
       Text(text = text)
 }

添加數(shù)據(jù):

data.value = list += listOf("ListItem6")

添加之后列表會自動刷新數(shù)據(jù)和UI。不需要跟之前一樣去主動notify UI更新联逻。
其中remember{}的表達的意思是跟字面意思一致搓扯,就是記住后面block中產(chǎn)生的value, 只有在UI組合時才會產(chǎn)生值。
mutableStateOf產(chǎn)生一個SnapshotMutableState包归,里面的value的讀和寫都是被監(jiān)控的锨推。在UI組合完成之后Composer會一直監(jiān)控state, 如果有值發(fā)生變化則會觸發(fā)Recomposition, 進行UI更新。

Recomposition

官方翻譯為重組换可,就是重新調(diào)用compose組合UI的過程椎椰,系統(tǒng)會根據(jù)需要使用新數(shù)據(jù)重新繪制函數(shù)發(fā)出的組件。因為UI是一直顯示的沾鳄,重組可能會很頻繁慨飘,為了保證流暢性,官方做了很多優(yōu)化译荞,如并行處理瓤的,自動跳過不需要重組的組件和使用樂觀算法優(yōu)化重組〈沤罚總之和之前的UI實現(xiàn)一樣堤瘤,不要在compose組合期間執(zhí)行比較耗時的邏輯玫芦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末浆熔,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子桥帆,更是在濱河造成了極大的恐慌医增,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件老虫,死亡現(xiàn)場離奇詭異叶骨,居然都是意外死亡,警方通過查閱死者的電腦和手機祈匙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門忽刽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人夺欲,你說我怎么就攤上這事跪帝。” “怎么了些阅?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵伞剑,是天一觀的道長。 經(jīng)常有香客問我市埋,道長黎泣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任缤谎,我火速辦了婚禮抒倚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘坷澡。我一直安慰自己托呕,他們只是感情好,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著镣陕,像睡著了一般谴餐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上呆抑,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天岂嗓,我揣著相機與錄音,去河邊找鬼鹊碍。 笑死厌殉,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的侈咕。 我是一名探鬼主播公罕,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼耀销!你這毒婦竟也來了楼眷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤熊尉,失蹤者是張志新(化名)和其女友劉穎罐柳,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狰住,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡张吉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了催植。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肮蛹。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖创南,靈堂內(nèi)的尸體忽然破棺而出伦忠,到底是詐尸還是另有隱情,我是刑警寧澤扰藕,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布缓苛,位于F島的核電站,受9級特大地震影響邓深,放射性物質(zhì)發(fā)生泄漏未桥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一芥备、第九天 我趴在偏房一處隱蔽的房頂上張望冬耿。 院中可真熱鬧,春花似錦萌壳、人聲如沸亦镶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缤骨。三九已至爱咬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間绊起,已是汗流浹背精拟。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留虱歪,地道東北人蜂绎。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像笋鄙,于是被迫代替她去往敵國和親师枣。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353