Android-Jetpack-Compose-最全上手指南

在今年的Google/IO大會上,亮相了一個全新的 Android 原生 UI 開發(fā)框架-Jetpack Compose, 與蘋果的SwiftIUI一樣,Jetpack Compose是一個聲明式的UI框架,隨著了今年安卓和蘋果兩大移動平臺相繼推出自己的UI開發(fā)框架Jetpack Compose 和SwiftIUI,標(biāo)志著移動操作系統(tǒng)正式全面擁抱聲明式 UI 開發(fā)模式。

一、聲明式 UI 的前世今生

其實(shí)聲明式 UI 并不是什么新技術(shù),早在 2006 年,微軟就已經(jīng)發(fā)布了其新一代界面開發(fā)框架 WPF,其采用了 XAML 標(biāo)記語言,支持雙向數(shù)據(jù)綁定、可復(fù)用模板等特性。

2010 年,由諾基亞領(lǐng)導(dǎo)的 Qt 團(tuán)隊(duì)也正式發(fā)布了其下一代界面解決方案 Qt Quick,同樣也是聲明式,甚至 Qt Quick 起初的名字就是 Qt Declarative。QML 語言同樣支持?jǐn)?shù)據(jù)綁定、模塊化等特性,此外還支持內(nèi)置 JavaScript,開發(fā)者只用 QML 就可以開發(fā)出簡單的帶交互的原型應(yīng)用。

聲明式 UI 框架近年來飛速發(fā)展,并且被 Web 開發(fā)帶向高潮。React 更是為聲明式 UI 奠定了堅(jiān)實(shí)基礎(chǔ)并一直引領(lǐng)其未來的發(fā)展。隨后 Flutter 的發(fā)布也將聲明式 UI 的思想成功帶到移動端開發(fā)領(lǐng)域...

聲明式UI的意思就是,描述你想要一個什么樣的UI界面,狀態(tài)變化時,界面按照先前描述的重新“渲染”即可得到狀態(tài)絕對正確的界面,而不用像命令一樣,告訴程序一步一步該干什么,維護(hù)各種狀態(tài)。扯遠(yuǎn)了,這個并不是今天文章的重點(diǎn),稍微了解一下就好,其他的就不在本文延伸。關(guān)于聲明式的更多介紹,我們回到本文的重點(diǎn)Jetpack Compose。

二、Jetpack Compose 介紹

Jetpack Compose 是一個用于構(gòu)建原生Android UI 的現(xiàn)代化工具包,它基于聲明式的編程模型,因此你可以簡單地描述UI的外觀,而Compose則負(fù)責(zé)其余的工作-當(dāng)狀態(tài)發(fā)生改變時,你的UI將自動更新。由于Compose基于Kotlin構(gòu)建,因此可以與Java編程語言完全互操作,并且可以直接訪問所有Android和Jetpack API。它與現(xiàn)有的UI工具包也是完全兼容的,因此你可以混合原來的View和現(xiàn)在新的View,并且從一開始就使用Material和動畫進(jìn)行設(shè)計(jì)。

三、Jetpack Compose 環(huán)境準(zhǔn)備和Hello World

每當(dāng)我們學(xué)習(xí)一門新的語言,我們都是從一個hello world開始,今天我們也從一個hello world來開始Jetpack Compose 吧! 要想獲得Jetpack Compose 的最佳體驗(yàn),我們需要下載最新版本的Android Studio 預(yù)覽版本(即Android Studio 4.0)。因?yàn)锳ndroid Studio 4.0 添加了對Jetpack Compose 的支持,如新的Compose 模版和Compose 及時預(yù)覽。

image

使用Jetpack Compose 來開始你的開發(fā)工作有2種方式:

  • 將Jetpack Compose 添加到現(xiàn)有項(xiàng)目

  • 創(chuàng)建一個支持Jetpack Compose的新應(yīng)用

接下來分別介紹一下這兩種方式。

1. 將Jetpack Compose 添加到現(xiàn)有項(xiàng)目

如果你想在現(xiàn)有的項(xiàng)目中使用Jetpack Compose,你需要配置一些必須的設(shè)置和依賴:

(1)gradle 配置

在app目錄下的build.gradle 中將app支持的最低API 版本設(shè)置為21或更高,同時開啟Jetpack Compose enable開關(guān),代碼如下:

android {
    defaultConfig {
        ...
        minSdkVersion 21
    }

    buildFeatures {
        // Enables Jetpack Compose for this module
        compose true
    }
    ...

    // Set both the Java and Kotlin compilers to target Java 8.

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
}       

(2) 使用試驗(yàn)版Kotlin-Gradle 插件

Jetpack Compose 需要試驗(yàn)版的Kotlin-Gradle插件,在根目錄下的build.gradle添加如下代碼:

buildscript {
    repositories {
        google()
        jcenter()
        // To download the required version of the Kotlin-Gradle plugin,
        // add the following repository.
        maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
    ...
    dependencies {
        classpath 'com.android.tools.build:gradle:4.0.0-alpha01'
        classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.60-eap-25'
    }
}

allprojects {
    repositories {
        google()
        jcenter()
        maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
    }
}

(3) 添加Jetpack Compose工具包依賴項(xiàng)

在app目錄下的build.gradle添加Jetpack Compose 工具包依賴項(xiàng),代碼如下:

dependencies {
    // You also need to include the following Compose toolkit dependencies.
    implementation 'androidx.ui:ui-tooling:0.1.0-dev02'
    implementation 'androidx.ui:ui-layout:0.1.0-dev02'
    implementation 'androidx.ui:ui-material:0.1.0-dev02'
    ...
}

ok,到這兒準(zhǔn)備工作就完畢,就可以開始寫代碼了,但是前面說了,還有一種方式接入Jetpack Compose ,我們來一起看看。

2. 創(chuàng)建一個支持Jetpack Compose的新應(yīng)用

比起在現(xiàn)有應(yīng)用中接入Jetpack Compose ,創(chuàng)建一個支持Jetpack Compose 的新項(xiàng)目則簡單了許多,因?yàn)锳ndroid Studio 4.0 提供了一個新的Compose 模版,只要選擇這個模版創(chuàng)建應(yīng)用,則所有上面的那些配置項(xiàng)都自動幫我們完成了。

創(chuàng)建一個支持Jetpack Compose 的應(yīng)用,如下幾個步驟就可以了:

    1. 如果你在Android Studio的歡迎窗口,點(diǎn)擊Start a new Android Studio project,如果你已經(jīng)打開了Android Studio 項(xiàng)目,則在頂部菜單欄選擇File > New > New Project
    1. Select a Project Template 窗口,選擇Empty Compose Activity并且點(diǎn)擊下一步
    1. Configure your project 窗口,做如下幾步:
    • a. 設(shè)置項(xiàng)目名稱, 包名保存位置

    • b. 注意,在語言下來菜單中,Kotlin 是唯一一個可選項(xiàng),因?yàn)镴etpack Compose 只能用Kotlin來寫的才能運(yùn)行。

    • c. Minimum API level 下拉菜單中,選擇21或者更高

    1. 點(diǎn)擊Finish

現(xiàn)在,你就可以使用Jetpack Compose 來編寫你的應(yīng)用了。

3. Hello wold

MainActivity.kt中添加如下代碼:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
             Text("Hello, Android技術(shù)雜貨鋪")
        }
    }
}
image

Jetpack Compose是圍繞composable函數(shù)來構(gòu)建的。這些函數(shù)使你可以通過描述應(yīng)用程序的形狀和數(shù)據(jù)依賴,以編程方式定義應(yīng)用程序的UI,而不是著眼于UI的構(gòu)建過程。要創(chuàng)建composable函數(shù),只需要在函數(shù)名前面加上一個@composable注解即可, 上面的Text就是一個composable函數(shù)。

4. 定義一個composable函數(shù)

一個composable函數(shù)只能在另一個composable函數(shù)的作用域里北調(diào)用,要使一個函數(shù)變?yōu)?code>composable函數(shù),只需在函數(shù)名前加上@composable注解,我們把上面的代碼中,setContent中的部分移到外面,抽取到一個composable函數(shù)中,然后傳遞一個參數(shù)nametext元素。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Greeting("Android技術(shù)雜貨鋪")
        }
    }
}

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}
image

四、布局

UI元素是分層級的,元素包含在其他元素中。在Jetpack Compose中,你可以通過從其他composable函數(shù)中調(diào)composable函數(shù)來構(gòu)建UI層次結(jié)構(gòu)。

image

在Android的xml布局中,如果要顯示一個垂直結(jié)構(gòu)的布局,最長用的就是LinearLayout, 設(shè)置android:orientation 值為vertical, 子元素就會垂直排列,那么,在Jetpack Compose 中,如何來實(shí)現(xiàn)垂直布局呢?先添加幾個Text來看一下。

1. 添加多個Text

在上面的例子中,我們添加了一個Text顯示文本,現(xiàn)在我們添加三個文本,代碼如下:

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

@Composable
fun NewsStory() {
    Text("我超??JetPack Compose的!")
    Text("Android技術(shù)雜貨鋪")
    Text("依然范特西")
}
image

從上圖可以看到,我們添加了3個文本,但是,由于我們還沒有提供有關(guān)如何排列它們的任何信息,因此三個文本元素相互重疊繪制,使得文本不可讀。

2. 使用Column

要使重疊繪制的Text文本能夠垂直排列,我們就需要使用到Column函數(shù),寫過flutter的同學(xué)看起來是不是很眼熟?是的,跟flutter里面的Column Widget 名字和功能完全一樣,甚至連他們的屬性都一摸一樣。

@Composable
fun NewsStory() {
   Column { //  添加Column,使布局垂直排列
       Text("我超??JetPack Compose的!")
       Text("Android技術(shù)雜貨鋪")
       Text("依然范特西")
   }
}

效果如下:

image

可以看到,前面重疊的布局,現(xiàn)在已經(jīng)垂直排列了,但是,默認(rèn)情況下,從左上角開始,一個接一個的排列,沒有任何間距。接下來,我們給Column 設(shè)置一些樣式。

3. 給Column添加樣式

在調(diào)用Column()時,可以傳遞參數(shù)給Column()來配置Column的大小、位置以及設(shè)置子元素的排列方式。

@Composable
fun NewsStory() {
    Column (
        crossAxisSize = LayoutSize.Expand,
        modifier = Spacing(16.dp)
    ){ //  添加Column,使布局垂直排列
        Text("我超??JetPack Compose的!")
        Text("Android技術(shù)雜貨鋪")
        Text("依然范特西")
    }
}
image

如上圖所示,我們填充了padding,其他效果幾乎一摸一樣, 上面代碼中的設(shè)置屬性解釋如下:

  • crossAxisSize: 指定Column組件(注:Compose中,所有的組件都是composable函數(shù),文中的組件都是指代composable函數(shù))在水平方向的大小,設(shè)置?crossAxisSizeLayoutSize.Expand即表示Column寬度應(yīng)為其父組件允許的最大寬度,相當(dāng)于傳統(tǒng)布局中的match_parant ,還有一個值為LayoutSize.Wrap,看名字就知道,包裹內(nèi)容,相當(dāng)于傳統(tǒng)布局中的wrap_content。

  • modifier:使你可以進(jìn)行其他格式更改。在這種情況下,我們將應(yīng)用一個Spacing修改器,該設(shè)置將Cloumn與周圍的視圖產(chǎn)生間距。

4. 如何顯示一張圖片?

在原來的安卓原生布局中,顯示圖片有相應(yīng)的控件ImageView,設(shè)置本地圖片地址或者Bitmap就能展示,在Jetpack Compose 中該如何顯示圖片呢?

image

我們先下載這張圖片到本地,添加到資源管理器中,命名為header.png, 我們更改一下上面的NewsStory ()方法,先從資源文件夾獲取圖片image,然后通過DrawImage()將圖片繪制出來:

@Composable
fun NewsStory() {
    // 獲取圖片
    val image = +imageResource(R.mipmap.header)
    Column (
        crossAxisSize = LayoutSize.Expand,
        modifier = Spacing(16.dp)
    ){ //  添加Column,使布局垂直排列
        // 顯示圖片
        DrawImage(image)
        
        Text("我超??JetPack Compose的!")
        Text("Android技術(shù)雜貨鋪")
        Text("依然范特西")
    }
}
image

可以看到,圖片不會按正確的比列顯示,接下來,我們來修復(fù)它。

圖片已添加到布局中,但會展開以填充整個視圖,并和文本是拼疊排列,文字顯示在上層。要設(shè)置圖形樣式,請將其放入Container(又一個和flutter中一樣的控件)

  • Container: 一個通用的內(nèi)容對象,用于保存和安排其他UI元素。然后,你可以將大小和位置的設(shè)置應(yīng)用于容器。
@Composable
fun NewsStory() {
    // 獲取圖片
    val image = +imageResource(R.mipmap.header)
    Column (
        crossAxisSize = LayoutSize.Expand,
        modifier = Spacing(16.dp)
    ){ //  添加Column,使布局垂直排列
        // 放在容器中,設(shè)置大小
        Container(expanded = true, height = 180.dp) {
            // 顯示圖片
            DrawImage(image)
        }
        Text("我超??JetPack Compose的!")
        Text("Android技術(shù)雜貨鋪")
        Text("依然范特西")
    }
}
image
  • expanded : 指定Container的大小,默認(rèn)是false(Container的大小是子組件的大小,相當(dāng)于wrap_content),如果將它設(shè)置為true,就指定Container的大小為父控件所允許的最大size, 相當(dāng)于match_parent。

  • height : 設(shè)置Container容器的高度,height屬性的優(yōu)先級高于expanded,因此會覆蓋expanded,如上面的例子,設(shè)置height180dp,也就是容器寬為父控件寬度,高為180dp

5. 添加間距Spacer

我們看到,圖片和文本之間沒有間距,傳統(tǒng)布局中,我們可以添加Margin屬性,設(shè)置間距,在Jetpack Compose 中,我們可以使用HeightSpacer()WidthSpacer() 來設(shè)置垂直和水平間距

 HeightSpacer(height = 20.dp) //設(shè)置垂直間距20dp
 WidthSpacer(width = 20.dp) // 設(shè)置水平間距20dp

在上面的例子中,我們來為圖片和文本之間添加20dp的間距:

@Composable
fun NewsStory() {
    // 獲取圖片
    val image = +imageResource(R.mipmap.header)
    Column (
        crossAxisSize = LayoutSize.Expand,
        modifier = Spacing(16.dp)
    ){ //  添加Column,使布局垂直排列
        // 放在容器中,設(shè)置大小
        Container(expanded = true, height = 180.dp) {
            // 顯示圖片
            DrawImage(image)
        }

        HeightSpacer(height = 20.dp) // 添加垂直方向間距20dp

        Text("我超??JetPack Compose的!")
        Text("Android技術(shù)雜貨鋪")
        Text("依然范特西")
    }
}
image

五、使用Material design 設(shè)計(jì)

Compose 旨在支持Material Design 設(shè)計(jì)原則,許多組件都實(shí)現(xiàn)了Material Design 設(shè)計(jì),可以開箱即用,在這一節(jié)中,將使用一些Material小組件來對app進(jìn)行樣式設(shè)置

image
1. 添加Shape樣式

Shape是Material Design 系統(tǒng)中的支柱之一,我們來用clip函數(shù)對圖片進(jìn)行圓角裁剪。

@Composable
fun NewsStory() {
    // 獲取圖片
    val image = +imageResource(R.mipmap.header)
    Column (
        crossAxisSize = LayoutSize.Expand,
        modifier = Spacing(16.dp)
    ){ //  添加Column,使布局垂直排列
        // 放在容器中,設(shè)置大小
        Container(expanded = true, height = 180.dp) {
            Clip(shape = RoundedCornerShape(10.dp)) {
                // 顯示圖片
                DrawImage(image)
            }
           
        }

        HeightSpacer(height = 20.dp) // 添加垂直方向間距20dp

        Text("我超??JetPack Compose的!")
        Text("Android技術(shù)雜貨鋪")
        Text("依然范特西")
    }
}
image

形狀是不可見的,但是我們的圖片已經(jīng)被裁剪了成了設(shè)置的形狀樣式,因此如上圖,圖片已經(jīng)有圓角了。

2. 給Text 添加一些樣式

通過Compose,可以輕松利用Material Design原則。將MaterialTheme()應(yīng)用于創(chuàng)建的組件

@Composable
fun NewsStory() {
    // 獲取圖片
    val image = +imageResource(R.mipmap.header)
    // 使用Material Design 設(shè)計(jì)
    MaterialTheme() {
        Column (
            crossAxisSize = LayoutSize.Expand,
            modifier = Spacing(16.dp)
        ){ //  添加Column,使布局垂直排列
            // 放在容器中,設(shè)置大小
            Container(expanded = true, height = 180.dp) {
                Clip(shape = RoundedCornerShape(10.dp)) {
                    // 顯示圖片
                    DrawImage(image)
                }

            }

            HeightSpacer(height = 20.dp) // 添加垂直方向間距20dp

            Text("我超??JetPack Compose的!")
            Text("Android技術(shù)雜貨鋪")
            Text("依然范特西")
        }  
    }
}

如上面的代碼,添加了MaterialTheme后,重新運(yùn)行,效果沒有任何變化,文本現(xiàn)在使用了MaterialTheme的默認(rèn)文本樣式。接下來,我們將特定的段落樣式應(yīng)用于每個文本元素。

@Composable
fun NewsStory() {
    // 獲取圖片
    val image = +imageResource(R.mipmap.header)
    // 使用Material Design 設(shè)計(jì)
    MaterialTheme() {
        Column (
            crossAxisSize = LayoutSize.Expand,
            modifier = Spacing(16.dp)
        ){ //  添加Column,使布局垂直排列
            // 放在容器中,設(shè)置大小
            Container(expanded = true, height = 180.dp) {
                Clip(shape = RoundedCornerShape(10.dp)) {
                    // 顯示圖片
                    DrawImage(image)
                }

            }

            HeightSpacer(height = 20.dp) // 添加垂直方向間距20dp

            Text("我超??JetPack Compose的!", style = +themeTextStyle { h5 }) // 注意添加了style
            Text("Android技術(shù)雜貨鋪", style = +themeTextStyle { body1 }) // 注意添加了style
            Text("依然范特西", style = +themeTextStyle { body2 }) // 注意添加了style
        }
    }
}
image

現(xiàn)在看看,我們的文本樣式已經(jīng)有變化了,標(biāo)題有6中樣式 h1-h6,其實(shí)HTML中的樣式很像,內(nèi)容文本有body1body22中樣式。

Material 調(diào)色版使用了一些基本顏色,如果要強(qiáng)調(diào)文本,可以調(diào)整文本的不透明度:

Text("我超??JetPack Compose的!", style = (+themeTextStyle { h5 }).withOpacity(0.87f))
  Text("Android技術(shù)雜貨鋪", style = (+themeTextStyle { body1 }).withOpacity(0.87f))
  Text("依然范特西", style = (+themeTextStyle { body2 }).withOpacity(0.6f))
image

有些時候,標(biāo)題很長,但是我們又不想長標(biāo)題換行從而影響我們的app UI ,因此,我們可以設(shè)置文本的最大顯示行數(shù),超過部分就截斷。

image

如本例所示,我們設(shè)置顯示最大行數(shù)為2,多于的部分截斷處理:

 Text("我超??JetPack Compose的!寫起來簡單,復(fù)用性又強(qiáng),可以抽取很多組件來復(fù)用,不用管理復(fù)雜的狀態(tài)變更!",
                maxLines = 2, overflow = TextOverflow.Ellipsis,
                style = (+themeTextStyle { h5 }).withOpacity(0.87f))
image

可以看到,設(shè)置了maxLinesoverflow 之后,超出部分就截斷處理了,不會影響到整個布局樣式。

六、Compose 布局實(shí)時預(yù)覽

從Android Studio 4.0 開始,提供了在IDE中預(yù)覽composable函數(shù)的功能,不用像以前那樣,要先下載一個模擬器,然后將app狀態(tài)模擬器上,運(yùn)行app才能看到效果。

但是有一個限制,那就是composable函數(shù)不能有參數(shù)

滿足下面兩個條件:

  • 函數(shù)沒有參數(shù)

  • 在函數(shù)前面添加@Preview注解

預(yù)覽效果圖如下:

image

當(dāng)布局改變了之后,頂部會出現(xiàn)一個導(dǎo)航條,顯示預(yù)覽已經(jīng)過期,點(diǎn)擊build&Refresh就可以刷新預(yù)覽

image

這真的是一個非常棒的功能,像其他聲明式布局,如React 、flutter 是沒有這個功能的,布局了之后,要重新運(yùn)行才能看到效果,雖然可以熱啟動,但是還是沒有這個預(yù)覽來得直接。

還有一個非常牛逼的地方是,compose 的預(yù)覽可以同時預(yù)覽多個composable函數(shù)。

效果如下:

image

七、總結(jié)

Jetpack Compse 目前還是試驗(yàn)版,所以肯定還存在很多問題,還不能現(xiàn)在將其用于商業(yè)項(xiàng)目中,但是這并不能妨礙我們學(xué)習(xí)和體驗(yàn)它,聲明式 UI 框架近年來飛速發(fā)展,React 為聲明式 UI 奠定了堅(jiān)實(shí)基礎(chǔ)并。 Flutter 的發(fā)布將聲明式 UI 的思想成功帶到移動端開發(fā)領(lǐng)域,Apple和Google 分別先后發(fā)布了自己的聲明式UI框架SwiftUI 和 Jetpack Compose , 以后,原生UI布局,聲明式可能將會是主流。

以上就是本文的所有內(nèi)容,希望它對你有用!

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

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

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