android compose

聲明式UI,更簡(jiǎn)單的自定義,實(shí)時(shí)帶交互的預(yù)覽功能
Compose 并不是類似于Recyclerview的高級(jí)控件,而是直接拋棄了View,ViewGroup那套東西,從上到下魯了一套全新的框架,直白點(diǎn)說就是它的渲染機(jī)制,布局機(jī)制,觸摸算法,以及UI 具體寫法全都是新的。

Compose 實(shí)現(xiàn)了聲明式UI,替代傳統(tǒng)的命令是UI??赡軐?duì)于我們來說第一個(gè)問題就是什么是聲明式,什么是命令式UI。
首先看一下聲明式UI長(zhǎng)什么樣。

Compose是用kotlin來寫的,它的每一個(gè)控件都是一個(gè)函數(shù)調(diào)用。Text()
Text()并不會(huì)創(chuàng)建對(duì)象,它不是一個(gè)構(gòu)造函數(shù),而是一個(gè)普通函數(shù)。
為了辨識(shí)度,Compose函數(shù)開頭都是大寫。也就是一個(gè)Composable。

有人會(huì)疑問Text()地層到底是什么,是個(gè)Textview嗎?其實(shí)并不是它是更下層Canvas那套東西。
Compose各個(gè)組件都是獨(dú)立的新實(shí)現(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獲取控件對(duì)象,來進(jìn)行操作。
其實(shí)對(duì)于布局來說,寫法大同小異,但是為什么傳統(tǒng)寫法叫命令式,而Compose叫聲明式。

對(duì)于聲明式來說,你只需要把控件聲明出來,而不需要手動(dòng)更新,如左邊對(duì)應(yīng)的Textview 它對(duì)應(yīng)的數(shù)據(jù)改變了,我們?cè)趺慈ジ臄?shù)據(jù)呢,首先是findviewbyId獲取控件對(duì)象,然后setText來進(jìn)行數(shù)據(jù)變更。
而如果用Compose呢,則不用更新。當(dāng)數(shù)據(jù)改變時(shí)Compose會(huì)自動(dòng)更新到頁面上。

這個(gè)自動(dòng)訂閱功能很容易使用,你只要在初始化時(shí)加上by mutableStateOf,剩下全由Compose自動(dòng)搞定。
這個(gè)神奇的特性,是利用了kotlin的Property Delegation來實(shí)現(xiàn)的,也就說明了為什么Compose只能用kotlin來編寫,因?yàn)槭褂玫搅薻otlin的許多特性,而這些特性使用java 不能簡(jiǎn)單實(shí)現(xiàn)。

下面舉例說明。

這就是所謂的聲明式UI,你只要聲明頁面是什么樣子,不用手動(dòng)去更新,因?yàn)轫撁鏁?huì)自動(dòng)更新,傳統(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),讓頁面自動(dòng)更新,而不是讓咱們?nèi)ブ付ǜ?,那么它也是聲明式?br> 聲明式UI 是強(qiáng)大的功能,并不是優(yōu)秀的代碼風(fēng)格。

這里就會(huì)想到data binding,他們倆都是通過頁面綁定數(shù)據(jù),來進(jìn)行自動(dòng)更新,但是他們倆還是有關(guān)鍵區(qū)別的,data binding 通過數(shù)據(jù)更新只能是頁面元素的值,而compose可以更新頁面中的任何內(nèi)容,包括結(jié)構(gòu)。

如:通過一個(gè)boolean來判斷一個(gè)頁面的元素展示,當(dāng)你把變量的值改變后,這個(gè)元素會(huì)從頁面中完全消失,像從來沒出現(xiàn)過一樣,而不是像設(shè)置Visibility這種方式從視覺上隱藏,這兩種策略,看起來差不多,但其實(shí)是種機(jī)制的改變,這種機(jī)制給頁面帶來的靈活性和性能的提升是非常大的。

除了android Compose外蘋果的SwiftUi和Flutter 都是使用的是聲明式??梢娛且环N趨勢(shì)了。

在做頁面開發(fā)時(shí)大家都知道盡量避免布局的嵌套,層級(jí)的增加會(huì)大幅度拖慢頁面的加載,主要是因?yàn)楦鞣Nlayout的重復(fù)測(cè)量。
每增加一層都指數(shù)級(jí)增加頁面的時(shí)長(zhǎng),而compose是不怕頁面嵌套的,它從根源上解決了這個(gè)問題,它不允許重復(fù)測(cè)量。

它的處理方式是先對(duì)子View進(jìn)行一個(gè)粗略的測(cè)量,根據(jù)內(nèi)容尺寸,粗略的估計(jì)子View的大小。
固有尺寸測(cè)量 InTrinsic Measurement.

就是在Compose里瘋狂嵌套組件,和把組件放在同以層級(jí)它的性能是一樣的。

缺點(diǎn):目前滑動(dòng)列表場(chǎng)景,是比不過原生RecyclerView的,但是未來可期。

Compose 編程思想
聲明性編程范式
長(zhǎng)期以來,Android 視圖層次結(jié)構(gòu)一直可以表示為界面微件樹。由于應(yīng)用的狀態(tài)會(huì)因用戶交互等因素而發(fā)生變化,因此界面層次結(jié)構(gòu)需要進(jìn)行更新以顯示當(dāng)前數(shù)據(jù)。最常見的界面更新方式是使用 findViewById() 等函數(shù)遍歷樹,并通過調(diào)用 button.setText(String)container.addChild(View)img.setImageBitmap(Bitmap) 等方法更改節(jié)點(diǎn)。這些方法會(huì)改變微件的內(nèi)部狀態(tài)。
手動(dòng)操縱視圖會(huì)提高出錯(cuò)的可能性。如果一條數(shù)據(jù)在多個(gè)位置呈現(xiàn),很容易忘記更新顯示它的某個(gè)視圖。此外,當(dāng)兩項(xiàng)更新以意外的方式發(fā)生沖突時(shí),也很容易造成異常狀態(tài)。例如,某項(xiàng)更新可能會(huì)嘗試設(shè)置剛剛從界面中移除的節(jié)點(diǎn)的值。一般來說,軟件維護(hù)復(fù)雜性會(huì)隨著需要更新的視圖數(shù)量而增長(zhǎng)。

在過去的幾年中,整個(gè)行業(yè)已開始轉(zhuǎn)向聲明性界面模型,該模型大大簡(jiǎn)化了與構(gòu)建和更新界面關(guān)聯(lián)的工程設(shè)計(jì)。該技術(shù)的工作原理是在概念上從頭開始重新生成整個(gè)屏幕,然后僅執(zhí)行必要的更改。此方法可避免手動(dòng)更新有狀態(tài)視圖層次結(jié)構(gòu)的復(fù)雜性。Compose 是一個(gè)聲明性界面框架。

重新生成整個(gè)屏幕所面臨的一個(gè)難題是,在時(shí)間、計(jì)算能力和電池用量方面可能成本高昂。為了減輕這一成本,Compose 會(huì)智能地選擇在任何給定時(shí)間需要重新繪制界面的哪些部分。這會(huì)對(duì)您設(shè)計(jì)界面組件的方式有一定影響,如重組中所述。

簡(jiǎn)單的可組合函數(shù)
使用 Compose,您可以通過定義一組接受數(shù)據(jù)而發(fā)出界面元素的可組合函數(shù)來構(gòu)建界面。一個(gè)簡(jiǎn)單的示例是 Greeting 微件,它接受 String 而發(fā)出一個(gè)顯示問候消息的 Text 微件。

聲明性范式轉(zhuǎn)變
在許多面向?qū)ο蟮拿钍浇缑婀ぞ甙?,您可以通過實(shí)例化微件樹來初始化界面。您通常通過膨脹 XML 布局文件來實(shí)現(xiàn)此目的。每個(gè)微件都維護(hù)自己的內(nèi)部狀態(tài),并且提供 getter 和 setter 方法,允許應(yīng)用邏輯與微件進(jìn)行交互。

在 Compose 的聲明性方法中,微件相對(duì)無狀態(tài),并且不提供 setter 或 getter 函數(shù)。實(shí)際上,微件不會(huì)以對(duì)象形式提供。您可以通過調(diào)用帶有不同參數(shù)的同一可組合函數(shù)來更新界面。這使得向架構(gòu)模式(如 ViewModel)提供狀態(tài)變得很容易,如應(yīng)用架構(gòu)指南中所述。然后,可組合項(xiàng)負(fù)責(zé)在每次可觀察數(shù)據(jù)更新時(shí)將當(dāng)前應(yīng)用狀態(tài)轉(zhuǎn)換為界面。

當(dāng)用戶與界面交互時(shí),界面會(huì)發(fā)起 onClick 等事件。這些事件應(yīng)通知應(yīng)用邏輯,應(yīng)用邏輯隨后可以改變應(yīng)用的狀態(tài)。當(dāng)狀態(tài)發(fā)生變化時(shí),系統(tǒng)會(huì)使用新數(shù)據(jù)再次調(diào)用可組合函數(shù)。這會(huì)導(dǎo)致重新繪制界面元素,此過程稱為“重組”。

動(dòng)態(tài)內(nèi)容
由于可組合函數(shù)是用 Kotlin 而不是 XML 編寫的,因此它們可以像其他任何 Kotlin 代碼一樣動(dòng)態(tài)。例如,假設(shè)您想要構(gòu)建一個(gè)界面,用來問候一些用戶。

狀態(tài)和組合
由于 Compose 是聲明式工具集,因此更新它的唯一方法是通過新參數(shù)調(diào)用同一可組合項(xiàng)。這些參數(shù)是界面狀態(tài)的表現(xiàn)形式。每當(dāng)狀態(tài)更新時(shí),都會(huì)發(fā)生重組。因此,TextField 不會(huì)像在基于 XML 的命令式視圖中那樣自動(dòng)更新??山M合項(xiàng)必須明確獲知新狀態(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ùn)行此代碼,您將不會(huì)看到任何反應(yīng)。這是因?yàn)?,TextField 不會(huì)自行更新,但會(huì)在其 value 參數(shù)更改時(shí)更新。這是因 Compose 中組合和重組的工作原理造成的。

可組合項(xiàng)中的狀態(tài)
可組合函數(shù)可以使用 remember 可組合項(xiàng)記住單個(gè)對(duì)象。系統(tǒng)會(huì)在初始組合期間將由 remember 計(jì)算的值存儲(chǔ)在組合中,并在重組期間返回存儲(chǔ)的值。remember 既可用于存儲(chǔ)可變對(duì)象,又可用于存儲(chǔ)不可變對(duì)象。
mutableStateOf 會(huì)創(chuàng)建可觀察的 MutableState<T>,后者是與 Compose 運(yùn)行時(shí)集成的可觀察類型。
value 如有任何更改,系統(tǒng)會(huì)安排重組讀取 value 的所有可組合函數(shù)。對(duì)于 ExpandingCard,每當(dāng) expanded 發(fā)生變化時(shí),都會(huì)導(dǎo)致重組 ExpandingCard。

在可組合項(xiàng)中聲明 MutableState 對(duì)象的方法有三種:

val mutableState = remember { mutableStateOf(default) }
var value by remember { mutableStateOf(default) }
val (value, setValue) = remember { mutableStateOf(default) }

布局
Column 豎向布局
Row 橫向布局
Box 可將一個(gè)元素放在另一個(gè)元素上。

如需在 Row 中設(shè)置子項(xiàng)的位置,請(qǐng)?jiān)O(shè)置 horizontalArrangement 和 verticalAlignment 參數(shù)。對(duì)于 Column,請(qǐng)?jiān)O(shè)置 verticalArrangement 和 horizontalAlignment 參數(shù):

滾動(dòng)修飾符

verticalScrollhorizontalScroll 修飾符提供一種最簡(jiǎn)單的方法,可讓用戶在元素內(nèi)容邊界大于最大尺寸約束時(shí)滾動(dòng)元素。利用 verticalScrollhorizontalScroll 修飾符,您無需轉(zhuǎn)換或偏移內(nèi)容。

列表
系統(tǒng)會(huì)對(duì)所有列表項(xiàng)進(jìn)行組合和布局,無論它們是否可見,因此如果您需要顯示大量列表項(xiàng)(或長(zhǎng)度未知的列表),則使用 Column 等布局可能會(huì)導(dǎo)致性能問題。

Compose 提供了一組組件,這些組件只會(huì)對(duì)在組件視口中可見的列表項(xiàng)進(jìn)行組合和布局。這些組件包括 LazyColumnLazyRow。

內(nèi)容內(nèi)邊距 contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
內(nèi)容外邊距 verticalArrangement = Arrangement.spacedBy(4.dp)

修飾符

借助修飾符,您可以修飾或擴(kuò)充可組合項(xiàng)。您可以使用修飾符來執(zhí)行以下操作:

  • 更改可組合項(xiàng)的大小、布局、行為和外觀
  • 添加信息,如無障礙標(biāo)簽
  • 處理用戶輸入
  • 添加高級(jí)互動(dòng),如使元素可點(diǎn)擊、可滾動(dòng)、可拖動(dòng)或可縮放

修飾符是標(biāo)準(zhǔn)的 Kotlin 對(duì)象。您可以通過調(diào)用某個(gè) Modifier 類函數(shù)來創(chuàng)建修飾符。您可以將以下函數(shù)連在一起以將其組合起來:

修飾符順序很重要
修飾符函數(shù)的順序非常重要。由于每個(gè)函數(shù)都會(huì)對(duì)上一個(gè)函數(shù)返回的 Modifier 進(jìn)行更改,因此順序會(huì)影響最終結(jié)果。讓我們來看看這方面的一個(gè)示例:

@Composable
fun ArtistCard(/*...*/) {
   
}

為什么我們需要一個(gè)新的UI 工具?
在Android中,UI工具包的歷史可追溯到至少10年前。自那時(shí)以來,情況發(fā)生了很大變化,例如我們使用的設(shè)
備,用戶的期望,以及開發(fā)人員對(duì)他們所使用的開發(fā)工具和語言的期望。
以上只是我們需要新UI工具的一個(gè)原因,另外一個(gè)重要的原因是 View.java 這個(gè)類實(shí)在是太大了,有太多的代
碼,它大到你甚至無法在Githubs上查看該文件,因?yàn)樗鼘?shí)際上包含了 30000 行代碼,這很瘋狂,而我們所使用的幾
乎每一個(gè)Android UI 組件都需要繼承于View。
GogleAndroid團(tuán)隊(duì)的Anna-Chiara表示,他們對(duì)已經(jīng)實(shí)現(xiàn)的一些API感到遺憾,因?yàn)樗麄円矡o法在不破壞功能的
情況下收回、修復(fù)或擴(kuò)展這些API,因此現(xiàn)在是一個(gè)嶄新起點(diǎn)的好時(shí)機(jī)。
這就是為什么Jetpack Compose 讓我們看到了曙光。

Jetpack Compose的著重點(diǎn)
包括一下幾個(gè)方面:

  1. 加速開發(fā)
  2. 強(qiáng)大的UI工具
  3. 直觀的Kotlin API

Compose API 的原則
Compose是一個(gè)聲明式UI系統(tǒng),其中,我們用一組函數(shù)來聲明UI,并且一個(gè)Compose
函數(shù)可以嵌套另一個(gè)Compose函數(shù),并以樹的結(jié)構(gòu)來構(gòu)造所需要的UI。
在Compose中,我們稱該樹為UI 圖,當(dāng)UI需要改變的時(shí)候會(huì)刷新此UI圖,比如Compose函數(shù)中有 if 語句,那
么Kotlin編譯器就需要注意了。

@Composable
fun checkbox ( ... )
@Composable
fun TextView ( ... )
@Composable
fun Edittext ( ... )
@Composable
fun Image ( ... )

在此過程中,Compose函數(shù)始終根據(jù)接收到的輸入生成相同的UI,因此,放棄類結(jié)構(gòu)不會(huì)有任何害處。從類結(jié)
構(gòu)構(gòu)建UI過渡到頂層函數(shù)構(gòu)建UI對(duì)開發(fā)者和Android 團(tuán)隊(duì)都是一個(gè)巨大的轉(zhuǎn)變,頂層函數(shù)還在討論之中,還沒有發(fā)布
release 版。

組合優(yōu)于繼承
Jetpack Compose首選組合而不是繼承。 Compose會(huì)基于其他部分構(gòu)建UI,但不會(huì)繼承行為。
如果你經(jīng)常關(guān)注Android或者對(duì)Android有所了解,你就會(huì)知道,Android中的幾乎所有組件都繼承于View類
(直接或間接繼承)。比如 EidtText 繼承于 TextView ,而同時(shí) TextView 又繼承于其他一些View,這樣的繼承機(jī)構(gòu)
最終會(huì)指向跟View即 View.java 。并且 View.java 又非常多的功能。
而Compose團(tuán)隊(duì)則將整個(gè)系統(tǒng)從繼承轉(zhuǎn)移到了頂層函數(shù)。 Textview , EditText , 復(fù)選框 和所有UI組件都是
它們自己的Compose函數(shù),而它們構(gòu)成了要?jiǎng)?chuàng)建UI的其他函數(shù),代替了從另一個(gè)類繼承。

深入了解Compose

compose.png

Core
基本上,核心包含四個(gè)構(gòu)建模塊:
繪制(Draw)
布局(Layout)
輸入(Input)
語義(Semantics)

1、Draw — Draw 給了你訪問Canvas的能力,因此你可以繪制你要的任何自定義View
2、Layout — 通過布局,我們可以測(cè)量事物并相應(yīng)地放置視圖。
3、Input — 開發(fā)人員可以通過輸入訪問事件并執(zhí)行手勢(shì)
4、Semantics — 我們可以提供有關(guān)樹的語義信息。

Foundation
Foundation的核心是收集上面提到的所有內(nèi)容,并共同創(chuàng)建一個(gè) 抽象層 ,以使開發(fā)人員更輕松調(diào)用。

Material
在這一層,所有的Material組件將會(huì)被提供,并且我們可以通過提供的這些組件來構(gòu)建復(fù)雜的UI。
這是Compose團(tuán)隊(duì)所做的出色工作中最精彩的部分,在這里,所有提供的View都有Material支持,因此,使用
Compose來構(gòu)建APP, 默認(rèn)就Material風(fēng)格的,這使得開發(fā)者少了很多工作。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容