聊聊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的元件上,然後可能稍微示範一下一個基本的登入頁怎麼實作。