Jetpack Compose 是 Google 開發的現代 Declarative UI framework,如果有開發過 Swift UI 、React 或是 Flutter 就會發現他們都極為類似。Google 提供了非常豐富的文件與教學,有興趣深入研究的話,非常推薦去看,內容會比我這裡詳細很多。
在這篇文章中,我比較注重的是他與原生 Android View 的不同之處,以及 Jetpack Compose 到底好在哪裡,首先我們來看看 build.gradle
要加入怎麼樣的 dependency 吧!
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.activity:activity-compose:$compose_activity_version'
在 Activity 中的使用方法非常簡單,只要使用 setContent 這個函式就可以了,由於這個函式的最後一個參數是個函式(也就是說setContent 是一個 higher order function),所以結尾可以使用 lambda 來表示要被執行的內容。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyTheme {
HomeScreen()
}
}
}
}
每一個 Jetpack Compose 的函式都有一個特殊的 Annotation - Composable ,有了這個 Annotation ,編譯器就知道如何去自動生成所需要的程式碼,這些自動生成的程式碼會去連結 UI 元件的生命周期事件、狀態管理、Material Theming 等等,如此一來,在我們的日常開發中就不會被這些技術細節給干擾,在接下來的文章中你將會很常看到它。
在原生系統中,與 Composable function 相對應的應該就是 Custom View 了,如果是一個有組合效果的元件,像是一個 Image 配上 Text 好了,在 Custom View 中你可能會 Inflate 一個 xml layout,然後用 findViewById 將他們拿出來使用,或是藉由建構子去創建這些元件,然後使用 addView 這個 function 將他們加進來,這些 View 的大小還要自己用麻煩的 LayoutParam 來去定義,不管哪條路都沒有多容易。如果是使用 Jetpack Compose 就簡單很多了,完全不用去管這些技術實作細節,只要專注在“描述”就行了,像下面這樣子:
@Composable
fun Greeting(name: String) {
Row {
Image(
painter = painterResource(id = R.drawable.ic_baseline_add_24),
contentDescription = "Add"
)
Text(text = "Hello $name!")
}
}
這裡補充一下:不一定所有的 Composable function 都會是用來顯示的 View ,像是動畫相關的 API,他們也是 Composable function,但不是一個獨立的View。
@Preview (showBackground = true)
@Composable
fun HomeScreenPreview() {
MyTheme {
HomeScreen()
}
}
在 Jetpack Compose 之中,除了 Composable 之外,還有另一個常用的 Annotation - Preview,他的作用是可以讓你在 Android Studio 中看到預覽畫面,而不需要執行在手機上。跟之前的 Android View 系統比較起來,雖然 xml 也可以預覽,但是 xml 的缺點就是太靜態了,無法輕易地看到所有狀態(例如選擇狀態)、只能看到一個螢幕畫面,所以在很多時候如果要看到實際效果的話,就只能跑在手機上面。但是 Jetpack Compose 的 Preview 可以讓你同時顯示多個小預覽,也可以像 xml 一樣顯示單個頁面,彈性非常大,但是缺點是效能還有待加強,在目前的 1.0 版本中,從修改程式碼,到 build ,到產生預覽所花的時間會超過 5 秒以上,也有可能要到 30 秒,最理想的情況應該是要不超過一秒。
在 Jetpack Compose 中的排版中,最基本的就是 Box、Row 與 Column 了,Box 跟 FrameLayout 非常類似,可以指定 Children 在排版中的對齊位置,不一樣的是,FrameLayout 使用 Gravity 來表示對齊概念,Box 則是用 Alignment 來表示,下方是各方向對齊的示意圖以及程式碼:
@Composable
fun BoxSample() {
Box(Modifier.fillMaxSize()) {
Text("TS", modifier = Modifier.align(Alignment.TopStart))
Text("TC", modifier = Modifier.align(Alignment.TopCenter))
Text("TE", modifier = Modifier.align(Alignment.TopEnd))
Text("CS", modifier = Modifier.align(Alignment.CenterStart))
Text("C", modifier = Modifier.align(Alignment.Center))
Text("CE", modifier = Modifier.align(Alignment.CenterEnd))
Text("BS", modifier = Modifier.align(Alignment.BottomStart))
Text("BC", modifier = Modifier.align(Alignment.BottomCenter))
Text("BE", modifier = Modifier.align(Alignment.BottomEnd))
}
}
@Preview(showBackground = true)
@Composable
fun BoxSamplePreview() {
Surface(modifier = Modifier.size(100.dp, 100.dp)) {
BoxSample()
}
}
Modifier 是 Jetpack Compose 中非常重要的一個元素,用來對當下的 View 增加行為,或是做排版相關的事項。定位有點像是 xml 中的 attribute,像是 layout_gravity、width、height、wrap_content、padding、margin 等等。與 xml attribute 不同的是,Modifier 在 Jetpack Compose 中有了更明確的分類方式,一些不是廣泛用途的 attribute。像是 text、textColor、font 等等就不會是透過 Modifier 來設定,而是透過 function 的參數來設定。
// 在這例子中,參數就是 name
@Composable
fun Greeting(name: String) {
Row {
Image(
painter = painterResource(id = R.drawable.ic_baseline_add_24),
contentDescription = "Add"
)
Text(text = "Hello $name!")
}
}
然而在 Custom View 要新增自己的參數(Attribute)是一件很麻煩的事,像是要建立一個新的 Styleable,然後要在 Custom View 的 Constructor 中實作你要如何讀這些參數(如下方程式碼所示),還要詳細的閱讀文件才能確保你的實作步驟是沒有錯的。然而這些對於 Android View 來說是有必要的,因為這些新的參數跟 width 、height 這種廣泛用途的參數是同一個進入點: xml 。所以如果要藉由 xml 來做這些比較方便的設定,就得要照 Android framework 所定的規矩來實作才行。相對的, Jetpack Compose 是純 Kotlin 語言所建構出來的,所以“新增參數”這件事變得簡單無比,把它當正常函數來寫就好。
init {
// Custom View 加入自定義參數的方式,繁瑣的技術細節以及實作
context.theme.obtainStyledAttributes(
attrs,
R.styleable.PieChart,
0, 0).apply {
try {
mShowText = getBoolean(R.styleable.PieChart_showText, false)
textPos = getInteger(R.styleable.PieChart_labelPosition, 0)
} finally {
recycle()
}
}
}
Column 跟 Row 對應的就是 LinearLayout,使用起來非常簡單,請看以下範例:
@Composable
fun RowSample() {
Row {
Text(text = "Hello", color = Color.Cyan)
Text(text = "Compose", color = Color.Red)
Text(text = "And", color = Color.Blue)
Text(text = "Kotlin", color = Color.Green)
}
}
@Preview(showBackground = true)
@Composable
fun SamplePreview() {
Surface {
RowSample()
}
}
Row 就是水平的 LinearLayout , Column 則是垂直的 LinearLayout ,這邊就不多示範了。
下一篇將會開始使用 Jetpack Compose 來畫出便利貼。