聲明式 UI?Android 官方怒推的 Jetpack Compose 到底是什么
視頻先行
下面是視頻內(nèi)容的腳本整理稿。如果你看了視頻,那下面的文稿就不用看了,直接翻到底部就行。
開始
聲明式 UI;更簡單的自定義;實時的、帶交互的預(yù)覽功能;還有更強的性能和功能。這就是 Android 官方全新推出的 UI 框架——Jetpack Compose。
大家好,我是扔物線朱凱。
2019 年中,Google 在 I/O 大會上公布了 Android 最新的 UI 框架:Jetpack Compose。Compose 可以說是 Android 官方有史以來動作最大的一個庫了。它在 2019 年中就公布了,但要到今年也就是 2021 年才會正式發(fā)布。這兩年的時間 Android 團隊在干嘛?在開發(fā)這個庫,在開發(fā) Compose。一個 UI 框架而已,為什么要花兩年來打造呢?因為 Compose 并不是像 RecyclerView、ConstraintLayout 這種做了一個或者幾個高級的 UI 控件,而是直接拋棄了我們寫了 N 年的 View 和 ViewGroup 那一套東西,從上到下擼了一整套全新的 UI 框架。直白點說就是,它的渲染機制、布局機制、觸摸算法以及 UI 的具體寫法,全都是新的。
Compose 的寫法
Compose 從一出現(xiàn),最受到官方推崇以及關(guān)注者贊揚的就是它實現(xiàn)了聲明式 UI,說它比我們傳統(tǒng)寫法的「命令式 UI」怎么怎么好——我們傳統(tǒng)的 View 和 ViewGroup 那一套系統(tǒng)的寫法叫「命令式」。但是對于大多數(shù) Android 開發(fā)者來說,我們的第一個問題就是:什么是「聲明式 UI」?
在講「聲明式 UI」之前,我們先看一下 Compose 的代碼長什么樣。Compose 是用 Kotlin 來寫的,它的每個控件都是一個函數(shù)調(diào)用。比如你要顯示一塊文字,你就這么寫:
Text("Hello")
看起來好像只是調(diào)用構(gòu)造函數(shù)創(chuàng)建了一個對象,但這么寫就已經(jīng)可以顯示出一塊文字來了。
另外——這其實也并沒有創(chuàng)建對象,這個?Text()?也不是一個構(gòu)造函數(shù),而是一個普通函數(shù)。
Text("Hello")
...
@Composable
fun?Text(...)?{
????...
}
普通函數(shù)用大寫開頭干嘛?很簡單,為了辨識度。Compose 規(guī)定了這種大寫開頭的命名方式,這樣我們就能一眼認出來:哦,這是個 Compose 的函數(shù)——或者用更官方的叫法:這是一個 Composable。
到這兒有人可能就會想:這個 Text() 它實質(zhì)上是個什么?是個 TextView 嗎?不是的。剛才我說過一次,Compose 的渲染機制、布局機制、觸摸機制全都是新寫的,所以這個 Text() 的底層不是 TextView,也不是任何一個原生控件,而是直接調(diào)用了更下層的繪制 API,也就是 Canvas 那一套東西。同理,Compose 里的各個組件,都是獨立的新實現(xiàn)。
好繼續(xù)說。一個函數(shù)調(diào)用是一個組件;兩個函數(shù)調(diào)用就是兩個組件;
Text("Hello")
Image()
多個函數(shù)組合起來,就是一個完整的界面:
Column?{
????Text("Hello")
????Image()
}
這,就是 Compose 的寫法??赐晁膶懛ǎ覀兙涂梢曰氐絼偛诺膯栴}:什么是「聲明式 UI」?這段代碼怎么就「聲明式」了?它和我們一直以來的寫法有什么區(qū)別?
首先,我們一般怎么寫 UI 的?xml 文件,對吧?比如這個界面,上下排列的一塊文字和一個圖片,它的等價傳統(tǒng)寫法是這樣的:
<!--?代碼經(jīng)過一定簡化?-->
<LinearLayout>
??<TextView?android:text="Hello"?/>
??<ImageView?/>
</LinearLayout>
一個 LinearLayout,里面包著一個 TextView 和一個 ImageView。
看了以后什么感覺?大同小異是吧?除了名字換換、格式變變,大體上是一樣的。對吧?
那為什么左邊就叫命令式,右邊就叫聲明式呢?xml 命令誰了?以及,右邊這寫法怎么就更優(yōu)秀了?我為什么要學一個看起來并沒有什么本質(zhì)區(qū)別的新寫法來為難自己?
其實所謂「聲明式 UI」,指的是你只需要把界面給「聲明」出來,而不需要手動更新。關(guān)鍵在于「不需要手動更新」。比如左邊這個布局里的 TextView,如果它對應(yīng)的數(shù)據(jù)改變了,我要怎么把新的文字更新到它?很簡單:findViewById()、setText() 對吧?
findViewById()
setText()
而如果用 Compose 呢?怎么更新?不用更新。因為 Compose 的界面會隨著數(shù)據(jù)自動更新。
Compose 會對界面中用到的數(shù)據(jù)自動進行訂閱——不管是字符串還是圖像還是別的什么,Compose 全部能夠自動訂閱——這樣當數(shù)據(jù)改變的時候,Compose 會直接把新的數(shù)據(jù)更新到界面。
var?text?=?"Hello"
...
Column?{
????Text(text)
????Image()
}
這個「自動訂閱」的功能很容易使用,你只要在初始化的時候加上一個?by mutableStateOf()?,剩下的全都由 Compose 自動搞定。
var?text?by?mutableStateOf("Hello")
...
Column?{
????Text(text)
????Image()
}
這個神奇的功能是利用 Kotlin 的 Property Delegation 屬性委托來實現(xiàn)的。這也在一定程度上回答了一個問題:?為什么 Compose 只能用 Kotlin 寫,而不能用 Java?因為它用了大量的 Kotlin 特性,而這些特性用 Java 不能簡單實現(xiàn)。注意,雖然 Kotlin 和 Java 是兼容的,Kotlin 能做到的事 Java 也能做到,但是有些東西它「不能簡單實現(xiàn)」就約等于不能實現(xiàn)了,因為不實用啊!對吧?所以 Android 自稱永遠不放棄對 Java 的支持,他們就這么一說,你就這么一聽,不要真的就不學 Kotlin,不然會越來越難受。你看這 Compose 不是已經(jīng)在逼著我們用 Kotlin 了嗎?
好拐回來,這就是所謂的「聲明式 UI」:你只要聲明界面是什么樣子,不用手動去更新,因為界面會自動更新。而傳統(tǒng)的寫法里,數(shù)據(jù)發(fā)生了改變,我們得手動用 Java 代碼或者 Kotlin 代碼去把新數(shù)據(jù)更新到界面。你給出詳細的步驟,去命令界面進行更新,這就是所謂的「命令式 UI」。
那么現(xiàn)在我們再往回拐:傳統(tǒng)的 xml 寫法和 Compose 的 Kotlin 寫法,為什么一個是「命令式」,一個是「聲明式」?這個問題其實本身就是錯的。單單一段 xml 代碼并不能稱作是命令式 UI。傳統(tǒng)寫法的「命令式」并不在于 xml 部分,而在于 Java 部分:Java 代碼去指揮、去命令界面更新,這才是「命令式」的含義所在;而 Compose 通過訂閱機制來自動更新,所以不需要做這種「命令」,所以是「聲明式」。
所以你看,不管是聲明式還是命令式,跟 xml 和 Kotlin 是無關(guān)的,它們并不是語言角度的定義,也不是寫法角度的定義,而是——功能角度。一個 UI 框架,如果可以讓開發(fā)者只聲明出界面的樣子,而不用去寫各種界面更新的代碼,它就是一個聲明式的 UI 框架。換句話說,如果 Android 可以讓我們用 xml 寫的界面也和數(shù)據(jù)做關(guān)聯(lián),讓界面自動更新而不需要開發(fā)者手寫更新代碼,那么它就也是聲明式 UI。聲明式 UI 是一種強大的功能,而不是一種優(yōu)秀的代碼風格。
哎?數(shù)據(jù)和界面做關(guān)聯(lián),界面跟著數(shù)據(jù)自動更新,這不就是數(shù)據(jù)綁定嗎?Android 已經(jīng)有這樣的官方庫了??!就叫 Data Binding,是吧?我用它不就得了,為什么要費這么大勁去用 Compose 呢?
首先,對!Data Binding 和 Compose 本質(zhì)上都是通過界面對數(shù)據(jù)進行訂閱來實現(xiàn)了界面的自動更新,但!它們是有關(guān)鍵區(qū)別的。區(qū)別就在于,Data Binding 通過數(shù)據(jù)更新的只能是界面元素的值,而 Compose 可以更新界面中的任何內(nèi)容,包括界面的結(jié)構(gòu)。比如你用一個 Boolean 類型的變量控制界面中某個元素是否顯示,
var?text?=?...
var?showImage?=?...
Column?{
????Text(text)
????if?(showImage)?{
????????Image()
????}
}
當你把變量的值從 true 變成 false 的時候,
var?text?=?...
var?showImage?=?...
Column?{
????Text(text)
????if?(showImage)?{
????????Image()
????}
}
...
showImage?=?false
這個元素會從界面中完全消失,就像從來沒有出現(xiàn)過一樣,而不是用 setVisibility(GONE) 這種方式從視覺上隱藏。這兩種策略看起來好像區(qū)別不大,那是因為我舉的例子簡單,實際上這是一種機制的改變,而這種機制的改變給界面開發(fā)帶來的靈活性和性能的提升是非常大的。你想一下,是不是?
總結(jié)
所以「聲明式 UI」還真的不是個噱頭,它讓 Compose 比傳統(tǒng)的 UI 系統(tǒng)強大得多。而且現(xiàn)在除了 Android 的 Compose 之外,iOS 的 SwiftUI 以及跨平臺的 Flutter 也都是聲明式的。聲明式 UI 已經(jīng)是一種趨勢了。
當然了不管怎么說,客觀地講,Compose 確實是一套比較難學的東西,因為它畢竟太新也太大了,它是一個完整的、全新的框架,確實讓很多人感覺「學不動」,這也是個事實。那怎么辦呢?學不動怎么辦呢?
我的建議是——
學。
