本文同步發(fā)表于我的微信公眾號(hào),掃一掃文章底部的二維碼或在微信搜索 郭霖 即可關(guān)注,每個(gè)工作日都有文章更新。
終于下定決心要寫這個(gè)系列了。
前段時(shí)間剛在公眾號(hào)上分享了一篇關(guān)于 Jetpack Compose動(dòng)畫 的文章,看到了評(píng)論區(qū)有這樣一條留言。
不管這個(gè)問題是疑問還是反問,其實(shí)類似的觀點(diǎn)我也著實(shí)看過不少。因此,在正式開始寫這個(gè)系列的文章之前,我覺得有必要先寫一篇序章,我們真的就來純粹地聊一聊,到底為什么要學(xué)習(xí)Jetpack Compose?
事實(shí)上,Jetpack Compose對(duì)于我來說是必寫的一個(gè)系列,只是時(shí)間早晚的問題。
想一想,之前大家還經(jīng)常會(huì)吐槽Google怎么又又又出新技術(shù)了,直呼跟不上了,學(xué)不動(dòng)了之類的。而現(xiàn)在,隨著移動(dòng)開發(fā)熱度的逐漸降低,有沒有發(fā)現(xiàn)其實(shí)Android已經(jīng)挺長時(shí)間沒什么重大的新技術(shù)讓我們學(xué)習(xí)了。
即使Android系統(tǒng)仍然還是保持每年一個(gè)版本的更新,但現(xiàn)在基本都是一些隱私和安全性上的提升,很少再能看到有什么重大的功能突破了。
而Jetpack Compose可以說是近幾年里Android開發(fā)領(lǐng)域最大的一次更新,且未來的Android程序開發(fā)一定會(huì)全面向Jetpack Compose轉(zhuǎn)型。只要你還在從事Android開發(fā)工作,這就是你必然不可能跳過的知識(shí)。
當(dāng)然,嚴(yán)格意義上講,Jetpack Compose也不能算是新鮮技術(shù)了。Google最早在2019年的I/O大會(huì)上就推出了Jetpack Compose的首個(gè)alpha版,并于2021年發(fā)布了1.0正式版。如今算下來,也已經(jīng)有四個(gè)年頭了。
我記得我應(yīng)該是在很早的階段就對(duì)Jetpack Compose進(jìn)行了嘗鮮,但當(dāng)時(shí)體驗(yàn)下來的結(jié)果讓我直搖頭。
首先,alpha版的Jetpack Compose性能很差,開發(fā)工具兼容得也不好,記得當(dāng)時(shí)必須得用Canary版的Android Studio進(jìn)行開發(fā)。
當(dāng)然,這都不算什么,最重要的是,API極其不穩(wěn)定。
要知道,那個(gè)時(shí)候網(wǎng)上關(guān)于Jetpack Compose的資料還很少,好不容易找到一篇講解的文章,照著去實(shí)現(xiàn)的時(shí)候發(fā)現(xiàn)API已經(jīng)變了,按文章中的寫法連編譯都過不去。因此,我當(dāng)時(shí)也就打消了寫Jetpack Compose文章的念頭。
但是現(xiàn)在一切都不一樣了。
經(jīng)過四年多的迭代,Jetpack Compose現(xiàn)在已經(jīng)相當(dāng)成熟和穩(wěn)定,并且絕大多數(shù)使用View能完成的效果,現(xiàn)在使用Jetpack Compose同樣都能夠完成。
再加上考慮到現(xiàn)在國內(nèi)Jetpack Compose的普及率仍然很低,因此我覺得現(xiàn)在是時(shí)候開始寫寫Compose相關(guān)的文章了。
我給這個(gè)系列起名叫“寫給初學(xué)者的Jetpack Compose教程”,這是因?yàn)槲易约壕褪浅鯇W(xué)者。我希望能夠完全站在初學(xué)者的角度上邊學(xué)邊寫,看完這個(gè)系列后大家能對(duì)Jetpack Compose有一個(gè)比較全面的認(rèn)識(shí)。
簡單起見,從這里開始,我們將Jetpack Compose簡稱為Compose。
本篇文章是這個(gè)系列的第一篇文章。
第一篇文章我并不打算直接去講Compose很細(xì)節(jié)的知識(shí)點(diǎn),我們先從比較宏觀的角度認(rèn)識(shí)一下什么是Compose?以及什么我們要使用Compose?
首先解釋一下什么是Compose。
Compose是一個(gè)由Google Android團(tuán)隊(duì)官方推出的聲明式UI框架,它的本質(zhì)就是用來編寫界面以及處理與用戶交互相關(guān)的邏輯的,你可以理解成它是View的替代品。
聲明式的UI框架和傳統(tǒng)的View區(qū)別非常大,但是具體的區(qū)別我感覺無法在這里進(jìn)行詳細(xì)的描述,因?yàn)閷?duì)于初學(xué)者來說可能很難看懂這些純概念性的描述。等隨著后面具體的學(xué)習(xí),大家就能直觀性地感覺到聲明式UI框架與View的巨大區(qū)別了。
接下來我們討論一下,為什么要使用Compose?
有不少朋友可能在之前就已經(jīng)或多或少了解過Compose,也有讀者朋友也跟我反饋過,并不喜歡Compose的這種聲明式寫法,以前的View用得好好的,為什么Google還要再發(fā)明一個(gè)新的UI框架來替代View?
那么我們可以先來審視一下,View真的是好好的嗎?
首先,站在開發(fā)者的角度,View有一個(gè)不太友好的地方,就是界面通常都是使用XML來編寫的,而系統(tǒng)先需要讀取并解析每一個(gè)XML文件,然后再將它們展示到界面上。
讀取并解析XML是需要時(shí)間的,在主線程中進(jìn)行這個(gè)操作還有可能會(huì)造成ANR,因此Google為此還推出了像AsyncLayoutInflater這樣的API來異步加載解析XML。
而如果你嘗試完全不用XML,全部都是通過在代碼中手寫UI布局,具體有多么難寫相信大家都是知道的。
所以其實(shí)你也可以將Compose理解成是Google官方提供了一個(gè)允許我們以純代碼的形式手寫UI布局的方式。
其次,站在Google的角度,View也并不是好好的。我們都知道,View是Android系統(tǒng)中的一個(gè)極其重要的組件,它是隨著系統(tǒng)發(fā)布的。而隨著系統(tǒng)發(fā)布的組件都會(huì)有一個(gè)頭疼的問題,更新和維護(hù)會(huì)非常困難。
舉一個(gè)具體的例子,ImageView就是隨系統(tǒng)發(fā)布的一個(gè)View,我們無須引入任何第三方庫就能直接使用它。但是,Google如果現(xiàn)在想要對(duì)ImageView的功能進(jìn)行更新怎么辦?你會(huì)發(fā)現(xiàn),除了升級(jí)操作系統(tǒng)外是沒有任何其他辦法更新的。
但即使是升級(jí)了操作系統(tǒng),舊版系統(tǒng)上的ImageView仍然還是老的,為此Google就只好推出像ImageViewCompat這樣的API,Android里各種Compat的API相信大家也都見過不少。
所以,后來Google就很少再推出隨系統(tǒng)發(fā)布的View了,更多都是隨著AndroidX發(fā)布的,而Compose當(dāng)然也是屬于AndroidX的一部分。如果想要了解更多關(guān)于AndroidX的內(nèi)容,可以參考我的這篇文章 總是聽到有人說AndroidX,到底什么是AndroidX? 。
最后,View真的已經(jīng)太老太老了,它是隨著Android 1.0系統(tǒng)發(fā)布的,至今已經(jīng)過去了十幾個(gè)年頭。View的歷史包袱太重,維護(hù)起來也變得越來越困難。因此,Google無論如何都覺得應(yīng)該推出一套全新的UI框架了,而這就是Compose。
剛才有說過,Compose是一個(gè)聲明式的UI框架。不管你喜不喜歡這種聲明式的形式,這都不是Google首創(chuàng)的,Google只是順應(yīng)了時(shí)代的潮流。
至于誰是引領(lǐng)時(shí)代潮流的聲明式UI框架?那首先肯定不可能是Compose,當(dāng)然也不會(huì)是隔壁的SwiftUI。大家最先想到的或許會(huì)是Flutter,畢竟這個(gè)名氣很大,用的人也很多,Compose和SwiftUI在很大程度上還是借鑒的Flutter。
但實(shí)際上,前端框架React早在10年前就已經(jīng)開始使用這種聲明式的語法理念,并且逐漸將它發(fā)揚(yáng)光大。
那么說了這么久的聲明式,那么到底什么才是聲明式呢?
我們可以將聲明式理解成是一種編程思維,只要你的UI框架是基于這種編程思維來使用的,那么就可以稱之為聲明式UI框架。
具體是什么編程思維呢?我們還是拿View來進(jìn)行舉例。
View肯定不是聲明式的,它更多是一種過程式的思維。我們在描述一個(gè)View的時(shí)候是不會(huì)描述它的狀態(tài)的,或者只會(huì)描述它的初始狀態(tài)。
那么后期想要更新這個(gè)View的狀態(tài)怎么辦呢?這個(gè)大家一定熟悉,就是先調(diào)用findViewById()方法來獲取到這個(gè)View的實(shí)例,然后再通過setXXX來更改它的狀態(tài),如setVisibility、setBackground等等。
這種就是過程式的思維。
那這種編程思維有什么不好的嗎?好不好其實(shí)都是對(duì)比出來的,在沒有聲明式之前我也沒有覺得這種過程式的思維有什么問題,待會(huì)我們會(huì)看具體的比較示例。
那聲明式的思維又是什么樣的呢?
它的工作流程有點(diǎn)像是刷新網(wǎng)頁一樣。即我們?nèi)匀徽5厝ッ枋鲆粋€(gè)控件,但這次要附帶上它的狀態(tài)。然后當(dāng)有任何狀態(tài)需要發(fā)生改變時(shí),只需要像刷新網(wǎng)頁一樣,讓整個(gè)界面上的所有元素全部刷新一遍,那么自然所有狀態(tài)都能得到更新了。
如果你是初次聽到這種邏輯,一定會(huì)感到震驚。什么?為了更新一個(gè)控件的狀態(tài),讓整個(gè)界面上的所有元素全部刷新一遍?那這程序的運(yùn)行效率不得卡到完全沒辦法使用?
沒錯(cuò),如果不做任何優(yōu)化的話,確實(shí)會(huì)是這個(gè)樣子,但很明顯Google不會(huì)讓這樣的事情發(fā)生。
事實(shí)上,所有的聲明式UI框架在這里都會(huì)采取相似的優(yōu)化策略,那就是在刷新界面的時(shí)候只會(huì)去更新那些狀態(tài)有變化的控件,而那些狀態(tài)沒有變化的控件在界面刷新的時(shí)候則會(huì)跳過執(zhí)行。
空口講了這么久,我們現(xiàn)在稍微看一段簡單的代碼來理解一下這部分邏輯吧:
@Composable
fun HelloCompose(date: String, weather: String) {
Column {
Text("$date")
Text("$weather")
}
}
這是一段Compose代碼,在這里我們放置了兩個(gè)Text控件用來顯示兩行文字,其中date用于顯示日期,weather用于顯示天氣。
那么當(dāng)界面內(nèi)容發(fā)生更新的時(shí)候,只需要對(duì)這個(gè)HelloCompose()函數(shù)進(jìn)行刷新,并傳入相應(yīng)的參數(shù)即可。但是請(qǐng)注意,如果傳入的參數(shù)和上次并沒有發(fā)生變化,那么就沒有任何控件會(huì)發(fā)生更新。如果date有變化,而weather沒有,那么就只有第一個(gè)Text控件會(huì)發(fā)生更新,第二個(gè)Text控件絲毫不會(huì)受影響,反之亦然。
重新刷新界面以此來更新界面內(nèi)容的這個(gè)過程我們稱之為重組。Compose會(huì)保證,每次重組永遠(yuǎn)都只會(huì)去更新那些必要的控件,狀態(tài)沒有發(fā)生變化的控件是不會(huì)更新的,以此來保證運(yùn)行效率。
至于Compose是如何做到這點(diǎn)的,它的基本原理是利用觀察者的機(jī)制來去記錄哪些控件需要更新,但如果想要詳細(xì)地解釋清楚會(huì)非常復(fù)雜。我覺得作為初學(xué)者,能使用好Compose就已經(jīng)很不錯(cuò)了,暫時(shí)沒有必要去卷它底層的工作原理。等到了這個(gè)系列的后期,我可能會(huì)再去寫一寫這方面的內(nèi)容。
現(xiàn)在,你已經(jīng)對(duì)Compose和聲明式UI有一點(diǎn)點(diǎn)的了解了。剛才我們有說到,要對(duì)過程式和聲明式的寫法進(jìn)行一下對(duì)比,現(xiàn)在就來看看吧。
比如用戶進(jìn)入我們App的首頁,首頁內(nèi)容的加載是需要一些時(shí)間的,通常這個(gè)時(shí)候我們會(huì)先顯示一個(gè)加載框或者是占位圖給用戶,等首頁內(nèi)容加載完成之后,再把加載框隱藏掉,將正常的首頁呈現(xiàn)給用戶。而如果加載過程中遇到了一些問題,比如說用戶的手機(jī)沒網(wǎng),這個(gè)時(shí)候就展示一個(gè)錯(cuò)誤頁面給用戶。
這是一個(gè)非常常見的需求,那么長久以來我們都是如何實(shí)現(xiàn)這個(gè)功能的呢?代碼如下所示:
fun refreshHomePageVisibility(Status status) {
when (status.current) {
Status.SUCCESS -> {
homePageView.visibility = View.VISIBLE
loadingView.visibility = View.GONG
errorView.visibility = View.GONG
}
Status.LOADING -> {
loadingView.visibility = View.VISIBLE
homePageView.visibility = View.GONG
errorView.visibility = View.GONG
}
Status.ERROR -> {
errorView.visibility = View.VISIBLE
loadingView.visibility = View.GONG
homePageView.visibility = View.GONG
}
}
}
可以看到,這里我們通過一個(gè)refreshHomePageVisibility()函數(shù)來去刷新首頁可見控件的狀態(tài)。homePageView表示正常的首頁內(nèi)容,loadingView表示加載等待框,errorView表示錯(cuò)誤頁面。
當(dāng)首頁內(nèi)容加載成功的時(shí)候,我們將homePageView設(shè)置成VISIBLE,將loadingView和errorView設(shè)置成GONE。
當(dāng)正在加載首頁內(nèi)容的時(shí)候,我們將loadingView設(shè)置成VISIBLE,將homePageView和errorView設(shè)置成GONE。
當(dāng)首頁內(nèi)容加載失敗的時(shí)候,我們將errorView設(shè)置成VISIBLE,將loadingView和homePageView設(shè)置成GONE。
有沒有覺得這段代碼真的很繁瑣,而且如果不細(xì)心的話還很容易寫錯(cuò),從而會(huì)導(dǎo)致應(yīng)該顯示的View和應(yīng)該隱藏的View出現(xiàn)混亂。
這種代碼其實(shí)我們經(jīng)常會(huì)寫,但是沒有人會(huì)去指出這有什么問題,因?yàn)椴贿@么寫還能怎么寫呢?過程式的思維就只能是這個(gè)樣子。
但是有了Compose就一樣了,用聲明式的思維來去編寫這類UI界面,你會(huì)發(fā)現(xiàn)邏輯會(huì)變得特別清晰明了。代碼如下所示:
@Composable
fun HomePage(Status status) {
when (status.current) {
Status.SUCCESS -> {
HomePageContent()
}
Status.LOADING -> {
LoadingContent()
}
Status.ERROR -> {
ErrorContent()
}
}
}
這里的HomePageContent()、LoadingContent()和ErrorContent()都是Compose函數(shù),分別用來顯示正常的首頁內(nèi)容、加載等待框和錯(cuò)誤頁面。
然后我們在HomePage()函數(shù)中只需要根據(jù)參數(shù)中傳入的狀態(tài)來決定是調(diào)用HomePageContent()、LoadingContent()還是ErrorContent()函數(shù)即可。
為什么使用Compose來實(shí)現(xiàn)同樣的功能邏輯會(huì)變得這么簡單?因?yàn)檫@就是聲明式UI的特點(diǎn)。當(dāng)HomePage()函數(shù)傳入的參數(shù)發(fā)生變化時(shí),這個(gè)函數(shù)就會(huì)觸發(fā)重組,從而對(duì)界面內(nèi)容進(jìn)行刷新。那么界面都刷新了,首頁內(nèi)容、加載等待框和錯(cuò)誤頁面的可見性自然都會(huì)調(diào)整為正確的狀態(tài),所以不需要我們再去手動(dòng)設(shè)置visibility屬性了。
另外效率的事情大家也完全不用擔(dān)心,剛才已經(jīng)說了,重組永遠(yuǎn)都只會(huì)去更新那些必要的控件。HomePageContent()、LoadingContent()和ErrorContent()這3個(gè)函數(shù)都沒有接收任何參數(shù),因此它們內(nèi)部的控件在重組過程中都不會(huì)被更新,只有最外層控件的可見性狀態(tài)會(huì)發(fā)生改變,效率等同于我們剛才手動(dòng)設(shè)置visibility屬性的方式。
這樣,我們就通過一個(gè)非常簡單的例子比較直觀地理解了Compose的優(yōu)越性,希望這能回答許多人心中的“為什么要學(xué)習(xí)Compose”的疑問。
當(dāng)然,Compose的優(yōu)越性遠(yuǎn)不止這些,但是也不要因?yàn)檫@一個(gè)例子就覺得Compose很美好。因?yàn)檫@個(gè)例子實(shí)在太簡單了,而Compose總體上來說仍然是非常龐大的一套系統(tǒng),想要把它完全搞懂并不容易,還是要花很大的功夫的。
今天的這篇文章是這個(gè)系列的序章,我們還并沒有通過一個(gè)實(shí)際可運(yùn)行的例子來去學(xué)習(xí)如何編寫Compose程序,但是希望這篇文章能提起大家對(duì)Compose的興趣,我們會(huì)在本系列后續(xù)的文章當(dāng)中慢慢上手Compose編程。文章來源:http://www.zghlxwxcb.cn/news/detail-504064.html
Compose是基于Kotlin語言的聲明式UI框架,如果想要學(xué)習(xí)Kotlin和最新的Android知識(shí),可以參考我的新書 《第一行代碼 第3版》,點(diǎn)擊此處查看詳情。文章來源地址http://www.zghlxwxcb.cn/news/detail-504064.html
到了這里,關(guān)于寫給初學(xué)者的Jetpack Compose教程,為什么要學(xué)習(xí)Compose?的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!