聲明式UI,更簡單的自定義,實時帶交互的預(yù)覽功能
Compose 并不是類似于Recyclerview的高級控件,而是直接拋棄了View,ViewGroup那套東西,從上到下魯了一套全新的框架,直白點說就是它的渲染機(jī)制,布局機(jī)制,觸摸算法,以及UI 具體寫法全都是新的。
Compose 實現(xiàn)了聲明式UI,替代傳統(tǒng)的命令是UI??赡軐τ谖覀儊碚f第一個問題就是什么是聲明式,什么是命令式UI。
首先看一下聲明式UI長什么樣。
Compose是用kotlin來寫的,它的每一個控件都是一個函數(shù)調(diào)用。Text()
Text()并不會創(chuàng)建對象,它不是一個構(gòu)造函數(shù),而是一個普通函數(shù)。
為了辨識度,Compose函數(shù)開頭都是大寫。也就是一個Composable。
有人會疑問Text()地層到底是什么,是個Textview嗎?其實并不是它是更下層Canvas那套東西。
Compose各個組件都是獨立的新實現(xiàn)。
看完它的寫法,看剛才的問題什么是聲明式UI,它這么寫怎么就聲明式了,它和我們一直以來的寫法有什么區(qū)別。
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="hello"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="world"
/>
</LinearLayout>
這是傳統(tǒng)寫法,還需要在java中 findviewbyId獲取控件對象,來進(jìn)行操作。
其實對于布局來說,寫法大同小異,但是為什么傳統(tǒng)寫法叫命令式,而Compose叫聲明式。
對于聲明式來說,你只需要把控件聲明出來,而不需要手動更新,如左邊對應(yīng)的Textview 它對應(yīng)的數(shù)據(jù)改變了,我們怎么去更改數(shù)據(jù)呢,首先是findviewbyId獲取控件對象,然后setText來進(jìn)行數(shù)據(jù)變更。
而如果用Compose呢,則不用更新。當(dāng)數(shù)據(jù)改變時Compose會自動更新到頁面上。
這個自動訂閱功能很容易使用,你只要在初始化時加上by mutableStateOf,剩下全由Compose自動搞定。
這個神奇的特性,是利用了kotlin的Property Delegation來實現(xiàn)的,也就說明了為什么Compose只能用kotlin來編寫,因為使用到了kotlin的許多特性,而這些特性使用java 不能簡單實現(xiàn)。
下面舉例說明。
這就是所謂的聲明式UI,你只要聲明頁面是什么樣子,不用手動去更新,因為頁面會自動更新,傳統(tǒng)頁面如果數(shù)據(jù)發(fā)生改變,要使用代碼,給出詳細(xì)步驟命令代碼去更新,這就是所謂的命令式。
傳統(tǒng)的所謂命令式,并不在XML部分,而是在于java部分,java代碼去指揮去命令控件去更新,這才是命令式含義所在,而Compose通過訂閱機(jī)制來更新UI,不需要指揮控件去更新,所以是聲明式。
它并不是語言角度的定義,也不是寫法角度定義,而是功能角度。
換句話說如果傳統(tǒng)的XML能通過數(shù)據(jù)和頁面進(jìn)行關(guān)聯(lián),讓頁面自動更新,而不是讓咱們?nèi)ブ付ǜ?,那么它也是聲明式?br> 聲明式UI 是強大的功能,并不是優(yōu)秀的代碼風(fēng)格。
這里就會想到data binding,他們倆都是通過頁面綁定數(shù)據(jù),來進(jìn)行自動更新,但是他們倆還是有關(guān)鍵區(qū)別的,data binding 通過數(shù)據(jù)更新只能是頁面元素的值,而compose可以更新頁面中的任何內(nèi)容,包括結(jié)構(gòu)。
如:通過一個boolean來判斷一個頁面的元素展示,當(dāng)你把變量的值改變后,這個元素會從頁面中完全消失,像從來沒出現(xiàn)過一樣,而不是像設(shè)置Visibility這種方式從視覺上隱藏,這兩種策略,看起來差不多,但其實是種機(jī)制的改變,這種機(jī)制給頁面帶來的靈活性和性能的提升是非常大的。
除了android Compose外蘋果的SwiftUi和Flutter 都是使用的是聲明式??梢娛且环N趨勢了。
在做頁面開發(fā)時大家都知道盡量避免布局的嵌套,層級的增加會大幅度拖慢頁面的加載,主要是因為各種layout的重復(fù)測量。
每增加一層都指數(shù)級增加頁面的時長,而compose是不怕頁面嵌套的,它從根源上解決了這個問題,它不允許重復(fù)測量。
它的處理方式是先對子View進(jìn)行一個粗略的測量,根據(jù)內(nèi)容尺寸,粗略的估計子View的大小。
固有尺寸測量 InTrinsic Measurement.
就是在Compose里瘋狂嵌套組件,和把組件放在同以層級它的性能是一樣的。
缺點:目前滑動列表場景,是比不過原生RecyclerView的,但是未來可期。
Compose 編程思想
聲明性編程范式
長期以來,Android 視圖層次結(jié)構(gòu)一直可以表示為界面微件樹。由于應(yīng)用的狀態(tài)會因用戶交互等因素而發(fā)生變化,因此界面層次結(jié)構(gòu)需要進(jìn)行更新以顯示當(dāng)前數(shù)據(jù)。最常見的界面更新方式是使用 findViewById() 等函數(shù)遍歷樹,并通過調(diào)用 button.setText(String)、container.addChild(View) 或 img.setImageBitmap(Bitmap) 等方法更改節(jié)點。這些方法會改變微件的內(nèi)部狀態(tài)。
手動操縱視圖會提高出錯的可能性。如果一條數(shù)據(jù)在多個位置呈現(xiàn),很容易忘記更新顯示它的某個視圖。此外,當(dāng)兩項更新以意外的方式發(fā)生沖突時,也很容易造成異常狀態(tài)。例如,某項更新可能會嘗試設(shè)置剛剛從界面中移除的節(jié)點的值。一般來說,軟件維護(hù)復(fù)雜性會隨著需要更新的視圖數(shù)量而增長。
在過去的幾年中,整個行業(yè)已開始轉(zhuǎn)向聲明性界面模型,該模型大大簡化了與構(gòu)建和更新界面關(guān)聯(lián)的工程設(shè)計。該技術(shù)的工作原理是在概念上從頭開始重新生成整個屏幕,然后僅執(zhí)行必要的更改。此方法可避免手動更新有狀態(tài)視圖層次結(jié)構(gòu)的復(fù)雜性。Compose 是一個聲明性界面框架。
重新生成整個屏幕所面臨的一個難題是,在時間、計算能力和電池用量方面可能成本高昂。為了減輕這一成本,Compose 會智能地選擇在任何給定時間需要重新繪制界面的哪些部分。這會對您設(shè)計界面組件的方式有一定影響,如重組中所述。
簡單的可組合函數(shù)
使用 Compose,您可以通過定義一組接受數(shù)據(jù)而發(fā)出界面元素的可組合函數(shù)來構(gòu)建界面。一個簡單的示例是 Greeting 微件,它接受 String 而發(fā)出一個顯示問候消息的 Text 微件。
聲明性范式轉(zhuǎn)變
在許多面向?qū)ο蟮拿钍浇缑婀ぞ甙?,您可以通過實例化微件樹來初始化界面。您通常通過膨脹 XML 布局文件來實現(xiàn)此目的。每個微件都維護(hù)自己的內(nèi)部狀態(tài),并且提供 getter 和 setter 方法,允許應(yīng)用邏輯與微件進(jìn)行交互。
在 Compose 的聲明性方法中,微件相對無狀態(tài),并且不提供 setter 或 getter 函數(shù)。實際上,微件不會以對象形式提供。您可以通過調(diào)用帶有不同參數(shù)的同一可組合函數(shù)來更新界面。這使得向架構(gòu)模式(如 ViewModel)提供狀態(tài)變得很容易,如應(yīng)用架構(gòu)指南中所述。然后,可組合項負(fù)責(zé)在每次可觀察數(shù)據(jù)更新時將當(dāng)前應(yīng)用狀態(tài)轉(zhuǎn)換為界面。
當(dāng)用戶與界面交互時,界面會發(fā)起 onClick 等事件。這些事件應(yīng)通知應(yīng)用邏輯,應(yīng)用邏輯隨后可以改變應(yīng)用的狀態(tài)。當(dāng)狀態(tài)發(fā)生變化時,系統(tǒng)會使用新數(shù)據(jù)再次調(diào)用可組合函數(shù)。這會導(dǎo)致重新繪制界面元素,此過程稱為“重組”。
動態(tài)內(nèi)容
由于可組合函數(shù)是用 Kotlin 而不是 XML 編寫的,因此它們可以像其他任何 Kotlin 代碼一樣動態(tài)。例如,假設(shè)您想要構(gòu)建一個界面,用來問候一些用戶。
狀態(tài)和組合
由于 Compose 是聲明式工具集,因此更新它的唯一方法是通過新參數(shù)調(diào)用同一可組合項。這些參數(shù)是界面狀態(tài)的表現(xiàn)形式。每當(dāng)狀態(tài)更新時,都會發(fā)生重組。因此,TextField 不會像在基于 XML 的命令式視圖中那樣自動更新。可組合項必須明確獲知新狀態(tài),才能相應(yīng)地進(jìn)行更新。
fun HelloContent() {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello!",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.h5
)
OutlinedTextField(
value = "",
onValueChange = { },
label = { Text("Name") }
)
}
}
如果運行此代碼,您將不會看到任何反應(yīng)。這是因為,TextField 不會自行更新,但會在其 value 參數(shù)更改時更新。這是因 Compose 中組合和重組的工作原理造成的。
可組合項中的狀態(tài)
可組合函數(shù)可以使用 remember 可組合項記住單個對象。系統(tǒng)會在初始組合期間將由 remember 計算的值存儲在組合中,并在重組期間返回存儲的值。remember 既可用于存儲可變對象,又可用于存儲不可變對象。
mutableStateOf 會創(chuàng)建可觀察的 MutableState<T>,后者是與 Compose 運行時集成的可觀察類型。
value 如有任何更改,系統(tǒng)會安排重組讀取 value 的所有可組合函數(shù)。對于 ExpandingCard,每當(dāng) expanded 發(fā)生變化時,都會導(dǎo)致重組 ExpandingCard。
在可組合項中聲明 MutableState 對象的方法有三種:
val mutableState = remember { mutableStateOf(default) }
var value by remember { mutableStateOf(default) }
val (value, setValue) = remember { mutableStateOf(default) }
布局
Column 豎向布局
Row 橫向布局
Box 可將一個元素放在另一個元素上。
如需在 Row 中設(shè)置子項的位置,請設(shè)置 horizontalArrangement 和 verticalAlignment 參數(shù)。對于 Column,請設(shè)置 verticalArrangement 和 horizontalAlignment 參數(shù):
滾動修飾符
verticalScroll 和 horizontalScroll 修飾符提供一種最簡單的方法,可讓用戶在元素內(nèi)容邊界大于最大尺寸約束時滾動元素。利用 verticalScroll 和 horizontalScroll 修飾符,您無需轉(zhuǎn)換或偏移內(nèi)容。
列表
系統(tǒng)會對所有列表項進(jìn)行組合和布局,無論它們是否可見,因此如果您需要顯示大量列表項(或長度未知的列表),則使用 Column 等布局可能會導(dǎo)致性能問題。
Compose 提供了一組組件,這些組件只會對在組件視口中可見的列表項進(jìn)行組合和布局。這些組件包括 LazyColumn 和 LazyRow。
內(nèi)容內(nèi)邊距 contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
內(nèi)容外邊距 verticalArrangement = Arrangement.spacedBy(4.dp)
修飾符
借助修飾符,您可以修飾或擴(kuò)充可組合項。您可以使用修飾符來執(zhí)行以下操作:
- 更改可組合項的大小、布局、行為和外觀
- 添加信息,如無障礙標(biāo)簽
- 處理用戶輸入
- 添加高級互動,如使元素可點擊、可滾動、可拖動或可縮放
修飾符是標(biāo)準(zhǔn)的 Kotlin 對象。您可以通過調(diào)用某個 Modifier 類函數(shù)來創(chuàng)建修飾符。您可以將以下函數(shù)連在一起以將其組合起來:
修飾符順序很重要
修飾符函數(shù)的順序非常重要。由于每個函數(shù)都會對上一個函數(shù)返回的 Modifier 進(jìn)行更改,因此順序會影響最終結(jié)果。讓我們來看看這方面的一個示例:
@Composable
fun ArtistCard(/*...*/) {
}
為什么我們需要一個新的UI 工具?
在Android中,UI工具包的歷史可追溯到至少10年前。自那時以來,情況發(fā)生了很大變化,例如我們使用的設(shè)
備,用戶的期望,以及開發(fā)人員對他們所使用的開發(fā)工具和語言的期望。
以上只是我們需要新UI工具的一個原因,另外一個重要的原因是 View.java 這個類實在是太大了,有太多的代
碼,它大到你甚至無法在Githubs上查看該文件,因為它實際上包含了 30000 行代碼,這很瘋狂,而我們所使用的幾
乎每一個Android UI 組件都需要繼承于View。
GogleAndroid團(tuán)隊的Anna-Chiara表示,他們對已經(jīng)實現(xiàn)的一些API感到遺憾,因為他們也無法在不破壞功能的
情況下收回、修復(fù)或擴(kuò)展這些API,因此現(xiàn)在是一個嶄新起點的好時機(jī)。
這就是為什么Jetpack Compose 讓我們看到了曙光。
Jetpack Compose的著重點
包括一下幾個方面:
- 加速開發(fā)
- 強大的UI工具
- 直觀的Kotlin API
Compose API 的原則
Compose是一個聲明式UI系統(tǒng),其中,我們用一組函數(shù)來聲明UI,并且一個Compose
函數(shù)可以嵌套另一個Compose函數(shù),并以樹的結(jié)構(gòu)來構(gòu)造所需要的UI。
在Compose中,我們稱該樹為UI 圖,當(dāng)UI需要改變的時候會刷新此UI圖,比如Compose函數(shù)中有 if 語句,那
么Kotlin編譯器就需要注意了。
@Composable
fun checkbox ( ... )
@Composable
fun TextView ( ... )
@Composable
fun Edittext ( ... )
@Composable
fun Image ( ... )
在此過程中,Compose函數(shù)始終根據(jù)接收到的輸入生成相同的UI,因此,放棄類結(jié)構(gòu)不會有任何害處。從類結(jié)
構(gòu)構(gòu)建UI過渡到頂層函數(shù)構(gòu)建UI對開發(fā)者和Android 團(tuán)隊都是一個巨大的轉(zhuǎn)變,頂層函數(shù)還在討論之中,還沒有發(fā)布
release 版。
組合優(yōu)于繼承
Jetpack Compose首選組合而不是繼承。 Compose會基于其他部分構(gòu)建UI,但不會繼承行為。
如果你經(jīng)常關(guān)注Android或者對Android有所了解,你就會知道,Android中的幾乎所有組件都繼承于View類
(直接或間接繼承)。比如 EidtText 繼承于 TextView ,而同時 TextView 又繼承于其他一些View,這樣的繼承機(jī)構(gòu)
最終會指向跟View即 View.java 。并且 View.java 又非常多的功能。
而Compose團(tuán)隊則將整個系統(tǒng)從繼承轉(zhuǎn)移到了頂層函數(shù)。 Textview , EditText , 復(fù)選框 和所有UI組件都是
它們自己的Compose函數(shù),而它們構(gòu)成了要創(chuàng)建UI的其他函數(shù),代替了從另一個類繼承。
深入了解Compose

Core
基本上,核心包含四個構(gòu)建模塊:
繪制(Draw)
布局(Layout)
輸入(Input)
語義(Semantics)
1、Draw — Draw 給了你訪問Canvas的能力,因此你可以繪制你要的任何自定義View
2、Layout — 通過布局,我們可以測量事物并相應(yīng)地放置視圖。
3、Input — 開發(fā)人員可以通過輸入訪問事件并執(zhí)行手勢
4、Semantics — 我們可以提供有關(guān)樹的語義信息。
Foundation
Foundation的核心是收集上面提到的所有內(nèi)容,并共同創(chuàng)建一個 抽象層 ,以使開發(fā)人員更輕松調(diào)用。
Material
在這一層,所有的Material組件將會被提供,并且我們可以通過提供的這些組件來構(gòu)建復(fù)雜的UI。
這是Compose團(tuán)隊所做的出色工作中最精彩的部分,在這里,所有提供的View都有Material支持,因此,使用
Compose來構(gòu)建APP, 默認(rèn)就Material風(fēng)格的,這使得開發(fā)者少了很多工作。