Compose Multiplatform 實戰:CMP用Compose實作跨平台畫面

前言

Compose Multiplatform (簡稱CMP)

昨天已經建好我們的通用 Material3 Theme
今天我們就可以開始來刻畫跨平台App的畫面了
CMP中使用Compose來刻Android 以及 iOS的畫面
而我們的Compose UI 在CMP中完全都是在commonMain
換句話說UI的部分都能共用

又因為Android現在也是全面推從使用Compose開發原生App
所以對之前已經上手Compose的人就很吃香

刻我們的第一個Compose畫面
  • 我們先來看一下如何在CMP中使用Compose建立一個最基本的Hello World畫面
    (一個萬年不變的例子Hello world XD)

因為Compose 採用 宣告式UI
只需在要實作的function前面 加入@Composable
就可以變成一個Compose的UI元件

在CMP的commonMain中加入以下

// in ~/commonMain/

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name !")
}
  • 當要預覽的時候
    你只要再開一個function並在前面加入@Preview
    就可以在IDE上看到Compose的預覽畫面
// in ~/commonMain/

@Preview
@Composable
fun GreetingPreview() { Greeting("Compose") }

實際可以看到IDE右邊會有@Preview的畫面
Cover

Compose的元件的Modifier

Modifier 是Compose 中用來修飾和配置元件的工具
它提供了多種功能來改變Compose UI元件的行為和外觀

如果你輸入了一個Modifier
然後點開來看就可以看到
他裡面
提供了各種選項讓你去設定UI行為和外觀
像是,backgroundcolor、align、height、width、onClick….等
相當的多 有興趣可以自己再去看看:
Cover

  • 前一天有跟著我建立Theme
    就可以試著使用Material3 theme來設定元件背景顏色
// in ~/commonMain/

@OptIn(KoinExperimentalAPI::class)
@Composable
@Preview
fun App() {
    //透過ElegantAccessComposeTheme設定Material 3 主題
    ElegantAccessComposeTheme {
        Greeting("Compose")
    }
}

然後像是這樣 加入一個Column 在Text外
並透過Modifier.background(color = MaterialTheme.colorScheme.background)

// in ~/commonMain/

@Composable
fun Greeting(name: String) {
    Column(
        modifier = Modifier
            .background(color = MaterialTheme.colorScheme.background)
    ) {
        Text(text = "Hello $name !")
    }
}
建立Compose頂部工具列 topbar
  • 因為在寫App不管是Andoird或iOS的場景上
    很常會需要客製化 toolbar
    Cover

  • 這時候我們可以建立一個可以重用的topbar

    //in ~/commonMain/
    @OptIn(ExperimentalMaterial3Api::class)
    @Composable
    fun MainAppBar(
      modifier: Modifier = Modifier,
      config: MainAppBarConfig,
      elevation: Dp = 4.dp,
      containerColor: Color = MaterialTheme.colorScheme.primaryContainer
    ) {
      CenterAlignedTopAppBar(
          title = config.title,
          colors = TopAppBarDefaults.mediumTopAppBarColors(
              containerColor = containerColor
          ),
          modifier = modifier.shadow(elevation = elevation),
          navigationIcon = {
              config.navigationIcon()
          },
          actions = {
              config.actionIcon?.invoke()
          }
      )
    }
    

此處的核心概念

  1. 使用Compose原生TopAppBar: CenterAlignedTopAppBar
  2. 考慮到不同畫面可能會有不同的topbar內容
    所以另外做了一個data class MainAppBarConfig
    要使用topBar時
    不用重複寫TopAppBar
    只要創建 MainAppBarConfig 的實例即可
  3. 常用的變數有提出來
    讓其可以被設置
    例如:elevation

實作data class MainAppBarConfig

可以自定義一些常用會被調整的東西
像是標題長度數、標題文字、樣式、返回鍵圖示…等等

// in ~/commonMain/

data class MainAppBarConfig(
    val marqueeNum: Int = 0,
    val titleText: @Composable () -> String = { "" },
    val title: @Composable () -> Unit = {
        DefaultTitleText(titleText(), marqueeNum)
    },
    val navigationIcon: @Composable () -> Unit = {},
    val actionIcon: @Composable (() -> Unit)? = null,
)

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun DefaultTitleText(titleText: String, marqueeNum: Int) {
    Text(
        modifier = Modifier.basicMarquee(marqueeNum),
        text = titleText,
        style = MaterialTheme.typography.titleMedium,
        maxLines = 1,
        overflow = TextOverflow.Ellipsis,
        color = ExtendedTheme.colors.onAppBar
    )
}
TopBar實際使用
  • 這裡我們將創建 createSetting 函數。
    就是用前面寫好的MainAppBarConfig
    輸入你要設定的內容
// in ~/commonMain/

private fun createSettingConfig(
    navController: NavController,
) = MainAppBarConfig(
    titleText = { stringResource(Res.string.title_setting) },
    navigationIcon = {
        NavBackIcon(navController = navController)
    },
)

註:如果你想實現返回按鈕的跳轉功能
可能需要將跳轉事件傳入函數
不過,使用 NavController 會更加靈活
NavController 可以管理所有路由
你只需在需要跳轉時指定定義好的字符串即可
關於這部分的詳細說明,會在後面的章節中解釋

  • 實際使用 ```kotlin // in ~/commonMain/

@Composable fun SettingScreen(navController: NavController) { val config = createSettingConfig(navController)

Scaffold(
    topBar = {
        MainAppBar(config = config)
    },
    containerColor = MaterialTheme.colorScheme.surfaceVariant
) {...} } ```
實際例子
  • 利用上面的概念
    我們可以簡單實作一個Setting頁面
// in ~/commonMain/

@Composable
fun SettingScreen(navController: NavController) {
    val config = createSettingConfig(navController)

    Scaffold(
        topBar = {
            MainAppBar(config = config)
        },
        containerColor = MaterialTheme.colorScheme.surfaceVariant
    ) { paddingValues ->
        LazyColumn(
            modifier = Modifier
                .fillMaxSize()
                .padding(paddingValues),
            verticalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            item {
                Text(
                    "Choose Transfer Option",
                    style = MaterialTheme.typography.bodySmall,
                    modifier = Modifier.padding(start = 30.dp, top = 16.dp, end = 30.dp)
                )
            }

            items(SettingOption.values()) { option ->
                SettingOptionCard(
                    option = option,
                    onClick = {
                        navController.navigate(option.route) {
                            navController.graph.startDestinationRoute?.let {
                                popUpTo(it) {
                                    saveState = true
                                }
                            }
                            launchSingleTop = true
                            restoreState = true
                        }
                    }
                )
            }
        }
    }
}

You might also enjoy