誤入Android開發-Day3: JetPack Compose

影山小麥機
13 min readJul 29, 2024

聊聊JetPack Compose在做些什麼

JetPack Compose

前言

最近簡單自學了Kotlin之後,有了語法,接下來就要自學一些UI框架,雖然本人也想學Android古老的xml,但近期比較值得投資的大概就是Jetbrain出的JetPack Compose了。

所以我們就來JetPack Compose吧,我在初步體驗後,覺得JetPack Compose的寫法基本上就跟Flutter很像..還有跟SwiftUI很像..

我已經搞不清楚到底誰抄誰的了XDD
反正就是個Mobile Domain建構UI的趨勢。
不過由於我選擇開的專案是Kotlin Multiplateform APP,就多少玩一下整個專案可以怎麼做,有機會順勢做做SwiftUI。

正文

聊聊Jetpack Compose的特點和替代概念:

1. 無生命週期方法:

Jetpack Compose 中的組件(Composables)是純函數,它們不擁有或管理自己的生命週期。相反,它們根據其輸入數據(屬性)生成 UI。當屬性發生變化時,Compose 會重新計算並更新 UI,這種方式使得 UI 的狀態和邏輯更易於理解和測試,Well,說這麼多反正就是Flutter在做Widget的方式。想像有 @composable 的標示就是一個Flutter的Widget。

2. 狀態管理:

在 Jetpack Compose 中,狀態管理通常使用 remember、mutableStateOf、viewModel 等來達到。這些工具幫助 Composables 記住它們的狀態,並在需要時重新計算和重新渲染。

3. 生命週期感知:

即使 Jetpack Compose 沒有傳統意義上的生命週期,但它仍然需要處理 UI 的可見性變化、應用程序的前後台切換等場景。這通常通過 Android 系統提供的 API 來實現,例如 CompositionLocalProvider、DisposableEffect 等,以確保 Composables 在適當的時間進行初始化和清理。

4. 效能和重新計算:

Jetpack Compose 通過基於數據變化的重新計算來實現效能優化,這與傳統的 View Hierarchy 有所不同。它可以智能地決定何時重新計算和重新渲染部分 UI,從而避免了全局刷新的開銷。

最近要開始寫JetPack Compose製作的UI,以下可能會稍微地講解一下哪些東西代表什麼。

在介紹物件之前,大抵上會先小小的認識一下這段簡單的程式碼:

import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp

@Composable
fun MyScreen() {
var counter by remember { mutableStateOf(0) }

Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center
) {
Text(
text = "Button clicked $counter times",
style = MaterialTheme.typography.h5,
modifier = Modifier.padding(bottom = 16.dp)
)
Button(onClick = { counter++ }) {
Text("Click Me")
}
}
}

@Preview
@Composable
fun PreviewMyScreen() {
MaterialTheme {
Surface {
MyScreen()
}
}
}

以上是所謂的JetPack Compose的整個簡單的UI佈局,大概可以猜到某些程式碼可能代表著某些物件,比如Column、Text、Button之類的,不過這部分會在下一個章節比較多的著墨。

屬性裝飾器

看了看UI佈局的方式,個人的感覺是跟Flutter蠻像的,也都是聲明了某個物件,然後就從這裡面拉這個物件來用,不過大概會先從屬性裝飾器來詮釋這個UI操控的做法:

@Composable

@Composable 是 Jetpack Compose 中的核心概念,它標識了一個可以參與 UI 組合過程的函數,允許我們使用聲明式方式構建和管理 UI。通過這些組合函數,我們可以創建可重用的 UI 組件,並輕鬆管理狀態和行為。

@Preview

@Preview 標註用於在 Android Studio 中預覽組合函數的效果。這對於在設計階段快速查看 UI 的外觀非常有用。

@ComposableContract

這個標註用於指定組合函數的契約,主要用於高級性能優化。它告訴編譯器這個函數的某些屬性,例如是否會重組或需要記住狀態。

@Stable

這個標註用於標識類或函數,表示它們在組合過程中是穩定的,不會引起不必要的重組。

@Immutable

這個標註表示標註的類或函數是不可變的。這意味著其狀態在創建後不會改變,從而可以優化組合過程。

@OptIn

@OptIn 標註用於試驗性或內部 API,表示你願意接受使用這些 API 的風險。

@Model

@Model 標註在 Compose 1.0 之前用於標識狀態對象,現在已被 mutableStateOf 和 remember 等狀態 API 取代。

@Remember

@Remember 是一個函數,用於保存組合函數中的狀態。在組合過程中,狀態將被記住,不會在重組時丟失。

@Composable Lambda

組合函數也可以作為 lambda 表達式使用,這在一些情況下非常有用。

@ReadOnlyComposable

這個標註用於標識那些不應該引起重組的組合函數。

所以,上面大概就是屬性裝飾器主要在控制的事情,不過其實建構UI也不會用到這麼多屬性裝飾器,大宗看起來還是@Composable

然後接著要簡單的進到UI的建構,上面的解釋稍微閱讀一下後,再回到另一份程式碼:

Scaffold

跟Flutter的Scaffold一樣,這邊也有提供類似的API給JetPack Compose使用,總之就是想像它是APP目前使用者習慣介面的鷹架,也有點像是在iOS中包含NavigationBar、TabBar的設計:

import androidx.compose.foundation.layout.padding
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp

@Composable
fun MyCompleteApp() {
var selectedIndex by remember { mutableStateOf(0) }

Scaffold(
topBar = {
TopAppBar(
title = { Text("My Complete App") },
actions = {
IconButton(onClick = { /*TODO*/ }) {
Icon(Icons.Filled.Search, contentDescription = "Search")
}
IconButton(onClick = { /*TODO*/ }) {
Icon(Icons.Filled.MoreVert, contentDescription = "More")
}
},
backgroundColor = MaterialTheme.colors.primary
)
},
bottomBar = {
BottomNavigation {
BottomNavigationItem(
icon = { Icon(Icons.Filled.Home, contentDescription = "Home") },
label = { Text("Home") },
selected = selectedIndex == 0,
onClick = { selectedIndex = 0 }
)
BottomNavigationItem(
icon = { Icon(Icons.Filled.Favorite, contentDescription = "Favorites") },
label = { Text("Favorites") },
selected = selectedIndex == 1,
onClick = { selectedIndex = 1 }
)
BottomNavigationItem(
icon = { Icon(Icons.Filled.Person, contentDescription = "Profile") },
label = { Text("Profile") },
selected = selectedIndex == 2,
onClick = { selectedIndex = 2 }
)
}
},
floatingActionButton = {
FloatingActionButton(onClick = { /*TODO*/ }) {
Icon(Icons.Filled.Add, contentDescription = "Add")
}
},
content = { innerPadding ->
// 這裡是主要的內容部分
MainContent(Modifier.padding(innerPadding))
}
)
}

@Composable
fun MainContent(modifier: Modifier = Modifier) {
// 這裡放置主要內容
Text(text = "Hello, Scaffold!", modifier = modifier.padding(16.dp))
}

@Preview(showBackground = true)
@Composable
fun PreviewMyCompleteApp() {
MyCompleteApp()
}

所以大概就可以知道有Scaffold的鷹架的狀態大概是這個樣子,那稍微實作一個ViewModel接近去UI裡面:

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

class MyViewModel : ViewModel() {

private val _counter = MutableStateFlow(0)
val counter: StateFlow<Int> get() = _counter

fun incrementCounter() {
viewModelScope.launch {
_counter.value += 1
}
}

fun decrementCounter() {
viewModelScope.launch {
_counter.value -= 1
}
}
}

以上是要置入UI的ViewModel。
以下是結合的ViewModel的UI。

import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun MyApp(myViewModel: MyViewModel = viewModel()) {
val counter by myViewModel.counter.collectAsState()

Scaffold(
topBar = {
TopAppBar(title = { Text("My App with ViewModel") })
},
content = { innerPadding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Counter: $counter", style = MaterialTheme.typography.h4)
Spacer(modifier = Modifier.height(16.dp))
Row {
Button(onClick = { myViewModel.incrementCounter() }) {
Text("Increment")
}
Spacer(modifier = Modifier.width(8.dp))
Button(onClick = { myViewModel.decrementCounter() }) {
Text("Decrement")
}
}
}
}
)
}

@Preview(showBackground = true)
@Composable
fun PreviewMyApp() {
MyApp()
}

小結

看起來聲明式UI真的未來的趨勢欸,不管SwiftUI是這樣用,Flutter的Widget也是,JetPack Compose也是這樣做。
不過我想這部分的UI建構也許就是一個簡單的入門,大略式的建構程式碼,知道UI大概是這樣使用。下一個章節會比較多一點放在UI的元件上,然後可能稍微示範一下一個基本的登入頁怎麼實作。

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

影山小麥機
影山小麥機

Written by 影山小麥機

本職為Mobile工程師,熱愛分享視野,也樂意站在ChatGPT的肩膀上。訂閱小麥機,收割技術、職涯、人生的難題。

No responses yet

Write a response