Compose基礎(chǔ)-創(chuàng)建你的第一個(gè)Compose應(yīng)用

第一章 創(chuàng)建你的第一個(gè)Compose應(yīng)用

Jetpack Compose是谷歌針對(duì)Android的聲明式UI框架,它大大簡(jiǎn)化了UI的創(chuàng)建。但在進(jìn)一步學(xué)習(xí)之前,我們知道,Jetpack Compose僅適用于Kotlin。這意味著我們接下來(lái)創(chuàng)建的工程都必須用Kotlin編程。所以在學(xué)習(xí)本專題之前,讀者應(yīng)該對(duì)Kotlin語(yǔ)法和函數(shù)式編程有基本的了解。后續(xù)我會(huì)陸續(xù)推出關(guān)于Kotlin的專題,歡迎一起學(xué)習(xí)交流。

本章包含以下三個(gè)主題:
  • 第一個(gè)Compose程序:HelloWord
  • 使用預(yù)覽函數(shù)
  • 運(yùn)行 Compose 項(xiàng)目

(一)第一個(gè)Compose程序:HelloWord

接下來(lái)你會(huì)看到,在Jetpack Compose中,可組合函數(shù)是UI的基本元素,通過(guò)可組合函數(shù)我們可以構(gòu)建復(fù)雜的UI界面。所以我們首先通過(guò)一個(gè)HelloWord程序來(lái)學(xué)習(xí)可組合函數(shù)。這個(gè)程序會(huì)有一個(gè)輸入名稱的按鈕還有完成按鈕,輸入名字點(diǎn)擊完成后,界面會(huì)出現(xiàn)一段問(wèn)候文本。
根據(jù)需求分析,這個(gè)程序包含以下內(nèi)容:

  • 第一是一段歡迎文本
  • 第二是一個(gè)輸入框和一個(gè)完成按鈕
  • 第三是一段問(wèn)候文本
HelloWord

接下來(lái)讓我們馬上開(kāi)始吧!

1.一段歡迎文本

接下來(lái)我們編寫(xiě)我們的第一個(gè)Compose函數(shù),一段歡迎文本。
MainActivity.kt

@Composable
fun Welcome() {
    Text(text = stringResource(id = R.string.welcome),
    style = MaterialTheme.typography.subtitle1)
}

strings.xml

<string name="welcome">歡迎</string>

我們可以通過(guò)@Composable注解來(lái)標(biāo)識(shí)可組合函數(shù)。它們不需要有特定的返回類型,而是發(fā)出界面元素,從而被其他可組合函數(shù)調(diào)用。Composable表示函數(shù)/lambda表達(dá)式可作為組合的一部分,將應(yīng)用程序數(shù)據(jù)轉(zhuǎn)換為樹(shù)或?qū)哟谓Y(jié)構(gòu)。
這個(gè)Welcome可組合函數(shù)里面包含一個(gè)Text()元素,他有兩個(gè)參數(shù),text參數(shù)引用了strings.xml文件的welcome文本,style參數(shù)調(diào)用了預(yù)置的Material主題的subtitle1。
接下來(lái)我們?cè)賱?chuàng)建一個(gè)@Composable函數(shù),一段問(wèn)候文本??纯磁c之前的Welcome函數(shù)有何不同?
MainActivity.kt

@Composable
fun Greeting(name: String) {
    Text(
        text = stringResource(id = R.string.hello,name),
        textAlign = TextAlign.Center,
        style = MaterialTheme.typography.subtitle1
    )
}

這里的text參數(shù)我們使用了帶參數(shù)name的文本,可以非常方便地替代文本中的變量。
strings.xml

<string name="hello">你好,%1$s.\n非常高興見(jiàn)到你。</string>

上面的%1代表第一個(gè)參數(shù),$s代表替代的文本。

2.包含輸入框和完成按鈕的一行

這個(gè)輸入框和完成按鈕在同一行,Row屬于非常常見(jiàn)的三大基本布局(Row,Column,Box)之一。與其他的Composable函數(shù)一樣,Row(){},我們可以向小括號(hào)()里面?zhèn)魅肴舾蓞?shù),及向大括號(hào){}里面?zhèn)魅肴舾勺釉貋?lái)組成界面。
MainActivity.kt

@Composable
fun TextAndButton(name: MutableState<String>, nameEntered: MutableState<Boolean>) {
    Row(modifier = Modifier.padding(top = 8.dp)) {
        TextField(
            value = name.value,
            onValueChange = {
                name.value = it
            },
            placeholder = {
                Text(text = stringResource(id = R.string.hint))
            },
            modifier = Modifier
                .alignByBaseline()
                .weight(1.0F),
            singleLine = true,
            keyboardOptions = KeyboardOptions(
                autoCorrect = false,
                capitalization = KeyboardCapitalization.Words
            ),
            keyboardActions = KeyboardActions(onAny = {
                nameEntered.value = true
            })
        )
        Button(modifier = Modifier
            .alignByBaseline()
            .padding(8.dp),
        onClick = {
            nameEntered.value = true
        }) {
            Text(text = stringResource(id = R.string.done))
        }
    }
}

strings.xml

<string name="hint">你的名字</string>
<string name="done">完成</string>

上面我們創(chuàng)建一行,使用Row函數(shù),在這一行里面添加一個(gè)輸入框TextField和一個(gè)按鈕Button。
輸入框TextField可以傳入很多參數(shù),(注意:我們使用了value = name.value這樣的形式,這樣可以不需要考慮參數(shù)的位置)但大部分是可選的。
TextAndButton函數(shù)要求傳入兩個(gè)函數(shù),name和nameEntered。這兩個(gè)參數(shù)使用了MutableState類型,MutableState對(duì)象攜帶的值是可變的。value 值的狀態(tài)如有任何更改,系統(tǒng)會(huì)安排重組讀取 value 的所有可組合函數(shù),這就是可組合函數(shù)的狀態(tài)與重組。至于為什么要在onValueChange和keyboardActions這兩個(gè)地方修改參數(shù)的值,我將在后面進(jìn)行說(shuō)明。
Button函數(shù)我們使用alignByBaseline()使按鈕和輸入框基線對(duì)齊,使用padding設(shè)置按鈕的內(nèi)邊距。

3.顯示一段問(wèn)候文本

我們使用Box()布局,當(dāng)用戶輸入名字后,顯示一段問(wèn)候文本,否則顯示輸入框和按鈕。
MainActivity.kt

@Composable
fun Hello(){
    val name = remember { mutableStateOf("")}
    val nameEntered = remember { mutableStateOf(false)}
    Box(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        contentAlignment = Alignment.Center
    ){
        if (nameEntered.value) {
            Greeting(name = name.value)
        } else {
            Column(horizontalAlignment = Alignment.CenterHorizontally) {
                Welcome()
                TextAndButton(name = name, nameEntered = nameEntered)
            }
        }
    }
}

這里你可能注意到了remembermutableStateOf,這兩個(gè)關(guān)鍵字對(duì)可組合函數(shù)狀態(tài)的創(chuàng)建和和控制非常重要。狀態(tài)涉及到界面元素中的變量,回顧前面的Welcome函數(shù):

@Composable
fun Welcome() {
    Text(text = stringResource(id = R.string.welcome),
    style = MaterialTheme.typography.subtitle1)
}

Welcome()可以說(shuō)是無(wú)狀態(tài)的,因?yàn)樗匦戮幾g的值始終保持不變。而Hello()是有狀態(tài)的,因?yàn)樗褂胣ame和nameEntered變量,傳遞給TextAndButton(),并在那里進(jìn)行修改,使得它不斷變化。
前面提到的,為什么要在onValueChange和keyboardActions這兩個(gè)地方修改參數(shù)的值?TextAndButton()在onValueChange的地方組件狀態(tài)會(huì)發(fā)生改變,我們以參數(shù)的形式,由Hello()組件傳遞進(jìn)來(lái),使TextAndButton()由有狀態(tài)變?yōu)闊o(wú)狀態(tài),方便不同的情況調(diào)用,這種模式稱為狀態(tài)提升。所以狀態(tài)提升是一種將狀態(tài)移至可組合項(xiàng)的調(diào)用方以使可組合項(xiàng)無(wú)狀態(tài)的模式。
我們編寫(xiě)了一個(gè)Composable函數(shù)之后,需要確認(rèn)UI編寫(xiě)是否正確并對(duì)細(xì)節(jié)進(jìn)行微調(diào),這時(shí)候就需要使用Compose的預(yù)覽函數(shù)。

(二)使用預(yù)覽函數(shù)

1.帶參數(shù)的預(yù)覽函數(shù)

使用Compose的預(yù)覽函數(shù),我們需要在Composable函數(shù)上再添加一個(gè)注解@Preview,如果我們?cè)贕reeting(name: String)上面添加@Preview,你會(huì)看到程序報(bào)錯(cuò):

Composable functions with non-default parameters are not supported in Preview unless they are annotated with @PreviewParameter. 

所以,我們應(yīng)該怎么預(yù)覽帶參數(shù)的Composable函數(shù)呢?
最簡(jiǎn)單的方法,是給改函數(shù)外面再包上一層不帶參數(shù)的Composable函數(shù)。

@Preview
@Composable
fun PreviewGreeting(){
    Greeting(name = "Jetpack Compose")
}

這意味著我們每次都需要重新寫(xiě)一個(gè)多余的函數(shù)來(lái)達(dá)到預(yù)覽的效果。如果我們帶參數(shù)的可組合函數(shù)非常多,這樣工作量就會(huì)非常大。
好在我們還有其他方法,例如,我們可以加一個(gè)函數(shù)參數(shù)的默認(rèn)值。

@Preview
@Composable
fun Greeting(name: String = "Jetpack Compose") {
    Text(
        text = stringResource(id = R.string.hello,name),
        textAlign = TextAlign.Center,
        style = MaterialTheme.typography.subtitle1
    )
}

這樣就方便很多,但同樣存在一個(gè)問(wèn)題,如果我們這個(gè)可組合函數(shù)不需要,或者說(shuō)不能給他設(shè)置默認(rèn)值,那這種方法也不可行。
根據(jù)提示,使用@PreviewParameter,我們可以給可組合函數(shù)傳遞參數(shù)值只影響預(yù)覽函數(shù)。這個(gè)方法有一點(diǎn)麻煩之處在于,我們需要編寫(xiě)一個(gè)新的類:

class HelloProvider: PreviewParameterProvider<String> {
    override val values: Sequence<String>
    get() = listOf("PreviewParameterProvider").asSequence()
}

這樣,我們只需要在composable函數(shù)里面添加@PreviewParameter注解,這個(gè)類就會(huì)提供一個(gè)參數(shù)給預(yù)覽函數(shù)。

@Preview
@Composable
fun Greeting(@PreviewParameter(HelloProvider::class)name: String) {
    Text(
        text = stringResource(id = R.string.hello,name),
        textAlign = TextAlign.Center,
        style = MaterialTheme.typography.subtitle1
    )
}

對(duì)于帶參數(shù)的預(yù)覽函數(shù),以上幾種方法都可以使用,根據(jù)個(gè)人喜好和具體情況而定。此外,@Preview注解還可以通過(guò)設(shè)置一些參數(shù),來(lái)修改預(yù)覽界面的外觀。

2.@Preview注解參數(shù)配置

我們可以為預(yù)覽設(shè)置背景顏色,當(dāng)然,首先要確認(rèn)設(shè)置顯示背景為true。

@Preview(showBackground = true, backgroundColor = 0xffff0000)
@Composable
fun DefaultPreview() {
    Hello()
}
HelloWord紅色背景

同理,預(yù)覽的尺寸一般是自適應(yīng)的,但是我們也可以為預(yù)覽設(shè)置固定的寬高。

@Preview(widthDp = 100, heightDp = 100)
@Composable
fun DefaultPreview() {
    Hello()
}

測(cè)試多國(guó)語(yǔ)言的時(shí)候,如果我們?cè)趕tring-zh-rCN里設(shè)置了翻譯語(yǔ)言,就可以通過(guò)locale參數(shù),設(shè)置預(yù)覽顯示的語(yǔ)言。

@Preview(locale = "zh-rCN")
@Composable
fun DefaultPreview() {
    Hello()
}

如果想顯示狀態(tài)欄和動(dòng)作欄,我們可以設(shè)置showSystemUi:

@Preview(showSystemUi = true)
@Composable
fun DefaultPreview() {
    Hello()
}

3.分組預(yù)覽

當(dāng)代碼中我們?cè)O(shè)置了多個(gè)預(yù)覽函數(shù)時(shí),我們可以選擇這些預(yù)覽函數(shù)在右側(cè)預(yù)覽面板以網(wǎng)格或者垂直的方式展示。


分組預(yù)覽

當(dāng)我們代碼中設(shè)置了非常多的預(yù)覽函數(shù),導(dǎo)致預(yù)覽面板看起來(lái)十分混亂,這時(shí)我們可以設(shè)置在右側(cè)預(yù)覽面板上面進(jìn)行分組預(yù)覽。
新建一個(gè)預(yù)覽分組:

@Preview(group = "group1")
@Composable
fun Welcome() {
    Text(text = stringResource(id = R.string.welcome),
    style = MaterialTheme.typography.subtitle1)
}

切換分組視圖:


切換分組視圖

(三)運(yùn)行Compose應(yīng)用

如果我們想看看界面UI及一些交互操作在模擬器及真機(jī)上的效果,我們可以通過(guò)以下兩個(gè)方式:

  • 部署Composable函數(shù)
  • 運(yùn)行App

1.部署Composable函數(shù)

我們?cè)陬A(yù)覽面板的某個(gè)預(yù)覽函數(shù)的預(yù)覽界面的右上角,有一個(gè)預(yù)覽按鈕,點(diǎn)擊即可部署到真機(jī)或模擬器上,這種方法比較適合調(diào)試單個(gè)Composable函數(shù)的時(shí)候。

部署函數(shù)

這種方法會(huì)為我們自動(dòng)創(chuàng)建運(yùn)行配置,我們可以在Run/Debug Comfigurations里面進(jìn)行修改。

2.在Activity上使用Composable函數(shù)

普通情況下,我們新建的工程,在AndroidManifest.xml項(xiàng)目里面就將MainActivity設(shè)置為啟動(dòng)界面。并在MainActivity里設(shè)置它對(duì)應(yīng)的布局。同樣的,使用Compose時(shí),我們也需要在Activity里面綁定Compose表示的界面。

MainActivity.kt

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Welcome()
        }
    }
}

我們通過(guò)setContent{ }就可以簡(jiǎn)潔明了地設(shè)置對(duì)應(yīng)的布局。與之前的setContentView()相比,使用Jetpack Compose,不需要維護(hù)對(duì)UI組件樹(shù)或其單個(gè)元素的引用。這點(diǎn)我們會(huì)在后面詳細(xì)介紹。

3.項(xiàng)目的配置

Jetpack Compose依賴Kotlin編寫(xiě),這意味著我們的應(yīng)用程序項(xiàng)目必須配置為Kotlin工程,但這并不意味著我們完全不能使用Java。事實(shí)上,只要我們的可組合函數(shù)是用Kotlin編寫(xiě)的,就可以在項(xiàng)目中輕松地混合Kotlin和Java,同時(shí)也可以混合使用傳統(tǒng)視圖和可組合視圖。關(guān)于這個(gè)互操作性API主題我們將在后面詳細(xì)介紹。
在創(chuàng)建項(xiàng)目的時(shí)候,我們只需要選擇Empty Compose Activity,AndroidStudio就會(huì)為我們做好一個(gè)Compose項(xiàng)目的所有配置。

新建Compose項(xiàng)目

這包括在項(xiàng)目級(jí)別的build.gradle里面對(duì)Kotlin的引用和配置,API版本不低于21,及在應(yīng)用級(jí)別的build.gradle里面引入compose相關(guān)的依賴庫(kù)。

4.點(diǎn)擊運(yùn)行按鈕

運(yùn)行我們的App,首先確定我們運(yùn)行的app(下圖的app處)是否已選擇,并確認(rèn)我們要運(yùn)行的設(shè)備(下圖的Pixel XL API 30處)是否已選擇,然后點(diǎn)擊綠色播放按鈕,即可成功運(yùn)行我們的應(yīng)用。

運(yùn)行程序

至此,我們開(kāi)發(fā)的第一HelloWorld應(yīng)用就順利完成了!

(四)總結(jié)回顧

1.總結(jié)

這一章,我們學(xué)習(xí)了如何編寫(xiě)我們的第一個(gè)Compose程序,并成功運(yùn)行到設(shè)備上。同時(shí),在編寫(xiě)代碼的過(guò)程中,我們了解到了如何使用@Composable注解編寫(xiě)一個(gè)可組合函數(shù),及如何使用@Preview注解在預(yù)覽面板預(yù)覽可組合函數(shù)。另外,對(duì)可組合函數(shù)的狀態(tài)與重組及狀態(tài)提升等概念也有了基本的了解。

2.回顧

關(guān)鍵術(shù)語(yǔ)
@Composable
帶參數(shù)的字符串
三大布局基礎(chǔ)布局(Row/Column/Box)
remember
mutableStateOf
可組合函數(shù)的狀態(tài)與重組
狀態(tài)提升
@Preview
@PreviewParameter
項(xiàng)目的部署與運(yùn)行

?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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