Android | Compose 初上手

語言: CN / TW / HK

theme: channing-cyan

簡介

Jetpack Compose 是用於構建原生 Andorid 界面的新工具包,Compose 使用了更少的代碼,強大的工具和直觀的 Kotlin Api 簡化並且加快了 Android 上界面的開發。

在 Compose 中,在構建界面的時候,無需在像之前那麼構建 XML 佈局,只需要調用 Jetpack Compose 函數來聲明你想要的的元素,Compose 編譯器就會自動幫你完成後面的工作。

在開始使用 Compose 之前,你需要重新搭建環境,可參考官方文檔

註解

  • @Compose

所有的組合函數都必須添加 @Compose 註解才可以。

@Compose 註解的方法只能被同類型的方法調用。

  • @Preview

使用該註解的方法可以不在運行 App 的情況下就可以查看佈局。@Preview 中常用的參數如下:

  1. name: String: 為該Preview命名,該名字會在佈局預覽中顯示。

  2. showBackground: Boolean: 是否顯示背景,true為顯示。

  3. backgroundColor: Long: 設置背景的顏色。

  4. showDecoration: Boolean: 是否顯示Statusbar和Toolbar,true為顯示。

  5. group: String: 為該Preview設置group名字,可以在UI中以group為單位顯示。

  6. fontScale: Float: 可以在預覽中對字體放大,範圍是從0.01。

  7. widthDp: Int: 在Compose中渲染的最大寬度,單位為dp。

  8. heightDp: Int: 在Compose中渲染的最大高度,單位為dp。

Compose 編程思想

Jetpack COmpose 是一個適用於 android 的新式聲明性界面工具包。Compose 提供了聲明性 API ,可以在不以命令的方式改變前端視圖的情況下呈現應用界面,從而使得編寫和維護界面變得更加容易。

申明性編程範式

長期以來,android 的視圖結構一直可以表示為界面微件數。由於應用的狀態會因用户交互等因素而發生變化,因此界面層次結構需要進行更新以顯示當前的數據,最常見的就是 findviewById 等函數遍歷樹,並調用設置數據的方法等改變節點,這些方法會改變微件的內部狀態

再過去的幾年中,整個行業已經轉向聲明性界面模型,該模型大大的簡化了構建和更新界面管理的工程設計,改技術的工作原理是在改建上重頭生成整個屏幕,然後執行必要的更改。此方法可以避免手動更新有狀態視圖結構的複雜性。Compose 是一個聲明性的界面框架。

重新生成整個屏幕所面臨的一個難題是,在時間,計算力和電量方面可能成本高昂,為了減輕這一成本,Compose 會智能的選擇在任何時間需要重新繪製界面的那些部分。這回對設計界面的組件有一定影響。

組合函數

Jetpack Compose 是圍繞可組合函數構建的,這些函數就是要顯示在界面上的元素,在函數中只需要描述應用界面形狀和數據依賴關係,而不用去關係界面的構建過程,

如果需要創建組合函數,只需要將 @Composeable 註解添加到對於的函數上即可,需要注意的是組合函數的名稱一般都是以大寫字母開頭的,如下:

```kotlin class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { PrimaryTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { Greeting("Android") } } } } }

@Composable fun Greeting(name: String) { Text(text = "Hello $name!", fontSize = 18.sp, color = Color.Red) }

@Preview(showBackground = true) @Composable fun DefaultPreview() { PrimaryTheme { Greeting("Android") } } ```

setContent 塊 定義了 Activity 的佈局,我們不需要去定義 XML 的佈局內容,只需要在其中調用組合函數即可。

上面的 一個簡單的示例Greeting 微件,它接收 String 而發出的一個顯示問候消息的 Text 微件。此函數不會返回任何內容,因為他們描述所需的屏幕狀態,而不是構造界面微件。

其中 Greeting 就是一個非常簡單的可組合函數,裏面定義了一個 Text,顧名思義,就是用來顯示一段文本

並且,我們可以在 Test 函數上添加 @PreView 註釋,這樣就可以非常方便的進行預覽。

聲明式範式轉變

Compose 的聲明方法中,微件相對無狀態,並且不提供 get,set 方法。實際上,微件微件不會以對象的形式提供。你可以通過調用帶有不同參數的統一可組合函數來更新界面。這使得架構模式,如 ViewModel 變得很容易。

引用邏輯為頂級可組合函數提供數據。該函數通過調用其他可組合函數來使用這些數據來描述界面。將適當的數據傳遞給這些可組合函數,並沿層次結構向下傳遞數據。

image-20220525170811867

當用户與界面交互時,界面發起 onClick事件。這些事件會通知應用邏輯,應用邏輯可以改變應用狀態。當狀態發生變化時,系統就會重新調用可組合函數。這回導致重新繪製界面描述,此過程稱為重組

image-20220525170848197

動態內容

由於可組合函是 kotlin 編寫的,因此他們可以像任何 kotlin 代碼一樣動態,例如,假設你想要的構建一個界面,如下:

kotlin @Composable fun Greeting(names: List<String>) { for (name in names) { Text("Hello $name") } }

此函數接受一個列表,每位每個列表元素生成一個 Text。可組合函數可能性非常複雜,你可以使用 if 語句來確定是否需要顯示特定的界面元素。例如循環,輔助函數等。你擁有地城語言的靈活性,這種強大的功能和靈活性是 JetpackCompose 的主要優勢之一。

重組

Compose 中,你可以用新數據再次調用某個可組合函數,這回導致組合函數重新進行重組。系統會根據需要使用新數據重新繪製發出的微件。Compose 框架可以只能的重組已經更改的組件。

例如,下面這個可組合函數,用於顯示一個按鈕:

kotlin @Composable fun ClickCounter(clicks: Int, onClick: () -> Unit) { Button(onClick = onClick) { Text("I've been clicked $clicks times") } }

每次點擊按鈕,就會更新 clicks 的值,Compose 會再次調用 lambda 與 Text 函數以顯示新值,此過程稱為 重組。不依賴該值的其他元素不會重組。

重組是指在輸入更改的時候再次調用可組合函數的過程。當函數更改時,會發生這種情況。當 Compose 根據新輸入重組時,它僅調用可能已經更改的函數或 lambad,而跳過其餘函數或 lambda。通過跳過豈會為更改參數的函數或者 lambda ,Compose 可以高效的重組。

切勿依賴於執行可組合函數所產生的附帶效應,因為可能會跳過函數的重組,如果這樣做,用户可能在應用中遇到奇怪且不可預測的行為。例如:

  • 寫入共享對象的屬性
  • 更新 viewmodel 中的可觀察項
  • 更新共享偏好設置

可組合函數可能會每一幀一樣的頻繁執行,例如呈現動畫的時候。所以可組合函數需要快速執行,所以避免在組合函數中出現卡頓,如果你需要執行高昂的操作,請在狗太協程中執行,並將結果作為參數傳遞給可組合函數。

例如下面代碼,應該將 sp 讀取的操作放在 viewmode 中,然後在回調中觸發更新:

kotlin @Composable fun SharedPrefsToggle( text: String, value: Boolean, onValueChanged: (Boolean) -> Unit ) { Row { Text(text) Checkbox(checked = value, onCheckedChange = onValueChanged) } }

可組合函數可以按照任何順序執行

如果你看到了可組合函數的代碼,可能會認為他們按照順序運行。但實際上未必是這樣。如果某個可組合函數包含對其他組合代碼的調用,這些函數可以按照順序執行。

Compose 可以選擇識別出某些界面元素的優先級高於其他界面元素,因此首先繪製這些元素。

假設你有如下代碼:

kotlin @Composable fun ButtonRow() { MyFancyNavigation { StartScreen() MiddleScreen() EndScreen() } }

對於這三個的調用可以按照任何順序進行。這意味着你不能讓某個函數設置一個全局變量(附帶效應),並讓別的函數利用這個全局變量而發生更改。所以每個函數都應該獨立。

可組合函數可以並行運行

Compose 可以通過並行運行可組合函數來優化重組。這樣依賴,Compose 就可以利用多個核心,並按照較低的優先級運行可組合函數(不在屏幕上)

這種優化方方式意味着可組合函數可能會在後台的線程池中執行,如果某個可組合函數對 viewModel 調用一個函數,則 Compose 可能會同時從多個線程調動該函數。

為了確保應用可以正常運行,所有的組合都不應該有附帶效應,而應該通過始終在界面線程上執行的 onClick 等回調觸發附帶效應。

調用某個可組合函數時,調用可能發生在與調用方不同的線程上。這意味着,應避免修改可組合函數 lambda 中的變量代碼,基因為此類代碼並非線程安全代碼,又因為他是可組合 lambda 不允許的附帶效應。

下面展示了一個可組合函數,他顯示了一個列表已經數量。

kotlin @Composable fun ListComposable(myList: List<String>) { Row(horizontalArrangement = Arrangement.SpaceBetween) { Column { for (item in myList) { Text("Item: $item") } } Text("Count: ${myList.size}") } }

此函數沒有附帶效應,他會將輸出列表轉為界面。才代碼非常適合展示小列表。不過此函數寫入局部變量,則這並不是非線程安全或者正確的代碼:

```kotlin @Composable @Deprecated("Example with bug") fun ListWithBug(myList: List) { var items = 0

Row(horizontalArrangement = Arrangement.SpaceBetween) {
    Column {
        for (item in myList) {
            Text("Item: $item")
            items++ // Avoid! Side-effect of the column recomposing.
        }
    }
    Text("Count: $items")
}

} ```

在上面例子中,每次重組都會修改 items。這可以在動畫的第一幀,或者在列表更新的時候。但不管怎麼樣,界面都會顯示出錯誤的數量。因此 Compose 不支持這樣的寫入操作。通過靜止此類操作,我們允許框架更改線程以執行可組合 lambda。

重組跳過儘可能多的內容

如果界面某些部分無需,Compose 會盡力只重組需要更新的部分。這意味着,他可以跳過某些內容以重新運行單個按鈕的可組合項,而不執行樹中其上面或下面的任何可組合項。

每個可組合函數和 lambda 都可以自行重組。以下演示了在呈現列表時重組如何跳過某些元素:

```kotlin /* * Display a list of names the user can click with a header / @Composable fun NamePicker( header: String, names: List, onNameClicked: (String) -> Unit ) { Column { // this will recompose when [header] changes, but not when [names] changes Text(header, style = MaterialTheme.typography.h5) Divider()

    // LazyColumn is the Compose version of a RecyclerView.
    // The lambda passed to items() is similar to a RecyclerView.ViewHolder.
    LazyColumn {
        items(names) { name ->
            // When an item's [name] updates, the adapter for that item
            // will recompose. This will not recompose when [header] changes
            NamePickerItem(name, onNameClicked)
        }
    }
}

}

/* * Display a single name the user can click. / @Composable private fun NamePickerItem(name: String, onClicked: (String) -> Unit) { Text(name, Modifier.clickable(onClick = { onClicked(name) })) } ```

這些作用域中的每一個都可能是在重組期間執行唯一一個作用域。當 header 發生更改時,Compose 可能會跳至 Column lambda 。二部執行他的任何父項。此外,執行 Colum 時,如果 names 未更改,Compose 可能會旋轉跳過 LazyColum 的項。

同樣,執行所有組合函數或者 lambda 都應該沒有附帶效應。當需要執行附帶效應時,應該通過回調觸發。

重組是樂觀操作

只要 Compose 任務某個可組合函數可能已經更改,就會開始重組。重組是樂觀操作,也就是説 Compose 預計會在參數再次更改之前完成重組。如果某個參數在重組完成之間發生改變,Compose 可能會取消重組,並使用新的參數重新開始。

取消重組後,Compose 會從重組中捨棄界面樹。如有附帶效應依賴於顯示的界面,即使取消了組成操作,也會應用該附帶效應。這可能導致應用狀態不一致。

確保每個可組合函數和 lambda 都冪等,且沒有附帶效應,以處理樂觀的重組

可組合函數可能會非常頻繁的運行

在某些情況下,可能針對界面每一幀運行一個可組合函數,如果該函數成本高昂,可能會導致界面卡頓。

例如,你的微件重試讀取設備配置,或者讀取 sp,他可能會在一秒鐘內讀取這些數據上百次,這回對性能造成災難性的影響。

如果您的可組合函數需要數據,它應為相應的數據定義參數。然後,您可以阿靜成本高昂的工作移到其他線程,並使用 mutableStateOf 或者 LiveData 將相應的數據傳遞給 Compose。

主題

```kotlin //深色 val DarkColorScheme = darkColors( primary = Purple80, onPrimary = Color(0xFFFFFFFF), secondary = PurpleGrey80, )

//亮色 val LightColorScheme = lightColors( primary = Purple40, onPrimary = Color(0xFF333333), secondary = PurpleGrey40, )

@Composable fun PrimaryTheme( darkTheme: Boolean = isSystemInDarkTheme(), // Dynamic color is available on Android 12+ dynamicColor: Boolean = true, content: @Composable () -> Unit ) {

MaterialTheme(
    colors = LightColorScheme,
    typography = Typography,
    content = content
)

} ```

默認的主題定義如上所示,最終會調用 MaterialTheme

Material 主題主要包含三個屬性,分別是 顏色,排版,和內容,Api 如下:

kotlin @Composable fun MaterialTheme(    colors: Colors = MaterialTheme.colors, // 顏色集合    typography: Typography = MaterialTheme.typography, // 排版集合    shapes: Shapes = MaterialTheme.shapes, // 形狀集合    content: @Composable () -> Unit // 要展示的內容 )

顏色

kotlin class Colors(    primary: Color, // 主顏色,屏幕和元素都用這個顏色    primaryVariant: Color, // 用於區分主顏色,比如app bar和system bar    secondary: Color, // 強調色,懸浮按鈕,單選/複選按鈕,高亮選中的文本,鏈接和標題    secondaryVariant: Color, // 用於區分強調色    background: Color, // 背景色,在可滾動項下面展示    surface: Color, // 表層色,展示在組件表層,比如卡片,清單和菜單(CardView,SheetLayout,Menu)等    error: Color, // 錯誤色,展示錯誤信息,比如TextField的提示信息    onPrimary: Color, // 在主顏色primary之上的文本和圖標的顏色    onSecondary: Color, // 在強調色secondary之上的文本和圖標的顏色    onBackground: Color, // 在背景色background之上的文本和圖標的顏色    onSurface: Color, // 在表層色surface之上的文本和圖標的顏色    onError: Color, // 在錯誤色error之上的文本和圖標的顏色    isLight: Boolean // 是否是淺色模式 )

更多的可以查看 lightColorScheme 函數。

排版

kotlin @Immutable class Typography internal constructor( val h1: TextStyle, val h2: TextStyle, val h3: TextStyle, val h4: TextStyle, val h5: TextStyle, val h6: TextStyle, val subtitle1: TextStyle, val subtitle2: TextStyle, val body1: TextStyle, val body2: TextStyle, val button: TextStyle, val caption: TextStyle, val overline: TextStyle )

形狀

```kotlin class Shapes( // 小組件使用的形狀,比如: Button,SnackBar,懸浮按鈕等    val small: CornerBasedShape = RoundedCornerShape(4.dp),

// 中組件使用的形狀,比如Card(就是CardView),AlertDialog等

val medium: CornerBasedShape = RoundedCornerShape(4.dp),

// 大組件使用的形狀,比如ModalDrawer或者ModalBottomSheetLayout(就是抽屜佈局和清單佈局)

val large: CornerBasedShape = RoundedCornerShape(0.dp), ) ```

使用

kotlin setContent { PrimaryTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.color.background ) { Greeting("Android") } } }

kotlin @Composable fun PrimaryTheme( themeType: ThemeType = themeTypeState.value, content: @Composable () -> Unit ) { val shapes = Shapes( small = RoundedCornerShape(4.dp), medium = RoundedCornerShape(8.dp), large = RoundedCornerShape(12.dp), ) MaterialTheme( colors = getThemeForTheme(themeType), typography = Typography, shapes = shapes, content = content ) }

另外,我寫了一個可動態切換的主題,有興趣的可以看一下

UI

SetContent

kotlin setContent { PrimaryTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { Greeting("Android") } } }

同 Android 中的 SetContentView。

Theme

創建項目之後,就會生成一個 項目名稱+Theme@Compose 方法,我們可以通過更改其中的顏色來完成對主題的修改。具體如上面的主題所示.

Modifier

Modifier 本質是一個接口,可以用來修飾各種佈局,例如 寬高,padding 等,常見的如下:

  • padding:有四個重載方法
  • plus:將其他的 Modifer 加入到當前的 Modifer 中。
  • fillMaxHeight,fillMaxWidth,fillmaxSize:類似於 match_parent,填充整個父 Layout
  • with,height,size :設置寬高度
  • rtl,ltr:開始佈局的方向
  • widthIn,heightIn,sizeIn 設置佈局的寬度和高度的最大值和最小值
  • gravity:元素的位置,
  • 等等

需要注意的是 Modifier 系列的方法都支持鏈式調用

Column,Row

類似於 LinearLayoutColumn 是橫向的,Row 是豎向的。有四個參數:

  • Modifer: 具體值如上述所示

  • verticalArrangement:子元素豎向的排列規則

常見的就是,上下左右中,比較特殊的就是

SpaceEvenly 均勻分配,

SpaceBetween 第一個元素前和最後一個元素後沒有空隙,其他的按比例放入。

SpaceAround 把整體中的一半空隙憑據放入第一個和最後一個的開始和結束,剩餘的一半等比放入各個元素。

  • horizontalAlignment:和上面一個,只不過方向不同

  • content:要顯示的內容

栗子:@Composable () -> Unit

dart setContent { PrimaryTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { Column { Row { Button( onClick = { themeTypeState.value = ThemeType.RED_THEME }, modifier = Modifier.width(100.dp), colors = ButtonDefaults.buttonColors(containerColor = Color.Yellow) ) { Greeting(name = "按鈕1") } Button( onClick = { themeTypeState.value = ThemeType.GREEN_THEME }, modifier = Modifier.width(100.dp), colors = ButtonDefaults.elevatedButtonColors() ) { Greeting(name = "按鈕2") } } Greeting(name = "Hello Android") Greeting(name = "Hello 345") } } } }

屏幕截圖 2022-05-17 135439

Text

kotlin fun Text( text: String, //顯示內容 modifier: Modifier = Modifier, //修飾,可修改透明度,邊框,背景等 color: Color = Color.Unspecified, //文字顏色 fontSize: TextUnit = TextUnit.Unspecified,// size fontStyle: FontStyle? = null, //文字樣式,粗體,斜體等 fontWeight: FontWeight? = null,//文字厚度 fontFamily: FontFamily? = null,//字體 letterSpacing: TextUnit = TextUnit.Unspecified, //用於與文本相關的維度值的單位。該組件還在測試中 textDecoration: TextDecoration? = null,//文字裝飾,中劃線,下劃線 textAlign: TextAlign? = null,對齊方式 lineHeight: TextUnit = TextUnit.Unspecified,//行高 overflow: TextOverflow = TextOverflow.Clip,//如何處理溢出,默認裁切 softWrap: Boolean = true,//是否軟換行 maxLines: Int = Int.MAX_VALUE,//最大行數 onTextLayout: (TextLayoutResult) -> Unit = {},//計算佈局時回調 style: TextStyle = LocalTextStyle.current //文本的樣式配置,如顏色、字體、行高等。 )

  • modifier:在此處用來修飾 Text,Modifer 提供了很多擴展,如透明度,背景,邊框等

示例:

kotlin @Composable fun Greeting(name: String) { Text( text = name, fontSize = 18.sp, fontWeight = FontWeight.Medium, color = MaterialTheme.colorScheme.primary, modifier = Modifier.height(30.dp) ) }

Button

kotlin fun Button( onClick: () -> Unit,//點擊時調用 modifier: Modifier = Modifier,//同上 enabled: Boolean = true,//是否啟用 elevation: ButtonElevation? = ButtonDefaults.buttonElevation(),// z軸上的高度 shape: Shape = FilledButtonTokens.ContainerShape.toShape(), border: BorderStroke? = null, colors: ButtonColors = ButtonDefaults.buttonColors(), contentPadding: PaddingValues = ButtonDefaults.ContentPadding, content: @Composable RowScope.() -> Unit )

  • shape

調整 button 的樣式,例如 RoundedCornerShape 是圓角矩形的樣式,CircleShape 是圓形的樣式,CutCornerShape 是切角樣式

  • border

外邊框,默認是 null,Border 有兩種使用方式,1 Border(size: Dp, color: Color),2 Border(size: Dp, brush: Brush)

第二種需要自己創建一個筆刷,去繪製外邊框,例如要實現漸變的外邊框。

  • colors

按鈕的顏色,默認是 ButtonDefaults.buttonColors() 。可選的有:

image-20220517151926468

​ 其中可以設置按鈕的背景色,未啟用的顏色等。

栗子:

kotlin Button( onClick = { themeTypeState.value = ThemeType.GREEN_THEME }, modifier = Modifier.width(100.dp), colors = ButtonDefaults.buttonColors( containerColor = Color.Yellow, contentColor = Color.Red, disabledContainerColor = Color.Black, disabledContentColor = Color.Green ) ) { Greeting(name = "按鈕2") }

OutLinedButton

具有外邊框的按鈕,內部使用的也是 Button。默認會有一個邊框,其參數和 Button 一致,效果如下

image-20220517163336439

TextButton

默認的 button 在有主題的時候,默認背景是主題顏色,而 textButton 背景默認是透明的。TextButton 默認使用的顏色是 ButtonDefaults.textButtonColors()

Image

kotlin @Composable fun Image( painter: Painter, bitmap: ImageBitmap, // contentDescription: String?, modifier: Modifier = Modifier, alignment: Alignment = Alignment.Center, contentScale: ContentScale = ContentScale.Fit, alpha: Float = DefaultAlpha, colorFilter: ColorFilter? = null )

  • painter:圖片資源,使用 PainterResource 來完成。
  • contentDescription:無障礙提示文本信息
  • contentScale :類似於 ImageView 中的 scaleType 屬性。
  • colorFilter:將某種顏色應用到圖片上
  • alpha:不透明度
示例

```dart @Composable @Preview fun Image() { Image( painter = painterResource(id = R.drawable.one), contentDescription = "無障礙提示", contentScale = ContentScale.Crop, modifier = Modifier .width(100.dp) .height(100.dp) ) }

```

image-20220517172753037

像一些圓圖或者邊框啥的就可以在 modifer 中直接設置了,如下:

kotlin @Composable @Preview fun Image() { Image( painter = painterResource(id = R.drawable.one), contentDescription = "無障礙提示", contentScale = ContentScale.Crop, modifier = Modifier .width(100.dp) .height(100.dp) .clip(shape = CircleShape) .border(2.dp, color = Color.Red, shape = CircleShape) ) }

image-20220517173235028

加載網路圖片

加載網路圖片需要藉助第三方庫 coil,使用方式如下:

tex //圖片加載庫 implementation("io.coil-kt:coil:2.0.0") implementation("io.coil-kt:coil-compose:2.0.0")

kotlin @Composable @Preview fun Image() { AsyncImage( model = "https://img0.baidu.com/it/u=3147375221,1813079756&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=836", contentDescription = "無障礙提示", contentScale = ContentScale.Crop, modifier = Modifier .width(100.dp) .height(100.dp) .clip(shape = CircleShape) .border(2.dp, color = Color.Red, shape = CircleShape) ) }

Spacer

和原生的一樣,需要空白區域時可以使用 Spacer ,使用方式如下:

kotlin Spacer(modifier = Modifier.height(100.dp))

Surface

對內容進行裝飾,例如設置背景,shape 等

kotlin fun Surface( modifier: Modifier = Modifier, shape: Shape = Shapes.None, color: Color = MaterialTheme.colorScheme.surface, contentColor: Color = contentColorFor(color), tonalElevation: Dp = 0.dp, shadowElevation: Dp = 0.dp, border: BorderStroke? = null, content: @Composable () -> Unit )

  • color :設置 Surface 的背景色,默認是主題中的 surface 顏色。
  • contentColor:此 Surface 為其子級提供的首選內容顏色。默認為 [color] 的匹配內容顏色,或者如果 [color] 不是來自主題的顏色,這將保持在此 Surface 上方設置的相同值。
  • tonalElevation:當 [color] 為 [ColorScheme.surface] 時,高程越高,淺色主題顏色越深,深色主題顏色越淺。
  • shadowElevation:陰影大小

Scaffold

腳手架的意思,和 Flutter 中的 Scaffold 是一樣的,通過 Scaffold 我看可以快速的對頁面進行佈局,例如設置導航欄,側滑欄,底部導航等等。

kotlin fun Scaffold( modifier: Modifier = Modifier, topBar: @Composable () -> Unit = {}, bottomBar: @Composable () -> Unit = {}, snackbarHost: @Composable () -> Unit = {}, floatingActionButton: @Composable () -> Unit = {}, floatingActionButtonPosition: FabPosition = FabPosition.End, containerColor: Color = MaterialTheme.colorScheme.background, contentColor: Color = contentColorFor(containerColor), content: @Composable (PaddingValues) -> Unit )

  • topBar:Toolbar,常用的有 CenterAlignedTopAppBar,SmallTopAppBarMediumTopAppBar 等。
  • bootomBar:底部導航欄
  • snackbarHost:
  • floatingActionButton:按鈕
  • floatingActionButtonPosition:按鈕位置
  • containerColor:背景顏色
  • contentColor:內容首選顏色
看一個栗子:

kotlin Scaffold( topBar = { //..... }, bottomBar = bottomBar, ) { Box( modifier = Modifier .fillMaxSize() .padding(top = it.calculateTopPadding(), bottom = it.calculateBottomPadding()) ) { content.invoke(it) } }

需要注意的是,如果使用了 toolbar 或者 bootomBar,就會把 content 中的內容擋住,這個時候就需要使用 PaddingValue 設置內邊距了。

還有一點須要注意,如果要使用沉浸式狀態欄,就需要自定義 topBar 了,要不然狀態欄會被 topBar 覆蓋。下面代碼是設置沉浸式狀態欄的。

kotlin ///系統 UI 控制器 implementation "com.google.accompanist:accompanist-systemuicontroller:0.24.8-beta" //正確獲取狀態欄高度 api "com.google.accompanist:accompanist-insets-ui:0.24.8-beta"

```dart override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) WindowCompat.setDecorFitsSystemWindows(window, false) setContent { SetImmersion() PrimaryTheme { SetContent() } } }

@Composable private fun SetImmersion() { if (isImmersion()) { val systemUiController = rememberSystemUiController() SideEffect { systemUiController.run { setSystemBarsColor(color = Color.Transparent, darkIcons = isDark()) setNavigationBarColor(color = Color.Black) } } } } ```

底部導航欄

```kotlin @Composable fun MainCompose(navController: NavHostController, mainBottomState: MutableState) { SetScaffold( bottomBar = { BottomBar(mainBottomState) } ) { when (mainBottomState.value) { 0 -> HomeCompos(navController) 1 -> ProjectCompos() 2 -> FLCompos() else -> UserCompos() } } }

@Composable private fun BottomBar(mainBottomState: MutableState) { BottomNavigation( backgroundColor = MaterialTheme.colors.background, ) {

    navigationItems.forEachIndexed { index, navigationItem ->
        BottomNavigationItem(
            selected = mainBottomState.value == index,
            onClick = {
                mainBottomState.value = index
            },
            icon = {
                Icon(
                    imageVector = navigationItem.icon,
                    contentDescription = navigationItem.name
                )
            },
            label = {
                BottomText(
                    isSelect = mainBottomState.value == index,
                    name = navigationItem.name
                )
            },
            selectedContentColor = Color.White,
            unselectedContentColor = Color.Black
        )
    }
}

}

@Composable fun BottomText(isSelect: Boolean, name: String) { if (isSelect) { Text( text = name, color = MaterialTheme.colors.primary, fontSize = 12.sp ) } else { Text( text = name, color = Color.Black, fontSize = 12.sp ) } } ```

如果看的不是特別清楚,可以直接點這裏看

最後

到這裏,這篇文章也完了。這篇文章主要講了一下 Compose 中最基本的一些 核心思想以及 UI 函數以及主題啥的。這也是我最開始接觸到 Compose 學到的東西,所以這也算是我的學習筆記吧。

參考資料

https://developer.android.google.cn/jetpack/compose/documentation

以及網上的一些文章

如果本文對你有幫助,請點贊支持,謝謝!如果有任何問題,可直接在下方評論,謝謝!