# Android compose [官方教學...](https://developer.android.com/develop?hl=zh-tw)其實也是挺強的 主要使用 Kotlin 來撰寫 UI [kotlin筆記](/aziUKMMTT3qzMkJG5lzrXg)有一些些些些kotliin基本語法 [TOC] ## 開啟 1. 開啟 Android Studio 1. 建立新專案 → 選擇 Empty Compose Activity 1. 設定最小 API Level(建議 API 26 以上) 1. 點擊 Finish,專案會自動配置 Compose 環境 ## 圖片位置 在main新增一個資料夾放照片 ![image](https://hackmd.io/_uploads/B1gJXvJCgl.png) 可以右鍵Open In...開啟資料夾存取位置,直接將圖片存入其中 ## 程式開始 在這一開始的地方寫上你要連結的函式即可 💡💡BiggerTheme為專案名稱 ![image](https://hackmd.io/_uploads/rkOiC0LsJe.png=50%) ## UI撰寫地 * setContent {} 是 Compose 的 入口點 * @Composable 標記的函式負責繪製 UI * Text(text = "Hello, Compose!") 顯示文字 ```kotlin= @Composable fun MyApp() { // 這裡是 UI 程式碼 MyScreen() } @Composable fun MyScreen() { Text(text = "Hello, Compose!") } ``` ## UI範例 Jetpack Compose 使用 Composable 函式 來建立 UI,以下是一個按鈕的範例: ### 按鈕 + 文字 ```kotlin= @Composable fun MyScreen() { Column( modifier = Modifier .fillMaxSize() .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { var count by remember { mutableStateOf(0) } Text(text = "按鈕點擊次數: $count", fontSize = 24.sp) Spacer(modifier = Modifier.height(16.dp)) Button(onClick = { count++ }) { Text(text = "點我增加計數") } } } ``` * remember { mutableStateOf(0) } 用來記錄狀態(點擊次數) * Button(onClick = { count++ }) 按下按鈕時執行 count++ * Text(text = "按鈕點擊次數: $count") 動態顯示點擊次數 ### UI 互動(點擊事件) 當 按下按鈕 時,text 變數會改變,UI 自動更新 ```kotlin= @Composable fun ClickableTextExample() { var text by remember { mutableStateOf("點擊按鈕變更文字") } Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Text(text = text, fontSize = 20.sp) Button(onClick = { text = "文字已改變!" }) { Text("點擊更改文字") } } } ``` ### Button onClick 觸發不同事件 Jetpack Compose 支援多種點擊事件,例如: * onClick(一般按鈕點擊) * Modifier.clickable {}(任何組件點擊) * onLongClick(長按) 長按事件: ```kotlin= @Composable fun LongClickExample() { var message by remember { mutableStateOf("長按按鈕改變文字") } Button( onClick = { message = "短按" }, modifier = Modifier.combinedClickable( onClick = { message = "短按" }, onLongClick = { message = "長按" } ) ) { Text("點擊或長按") } Text(text = message, fontSize = 20.sp) } ``` * onClick 短按時執行 * onLongClick 長按時執行 ### LaunchedEffect 執行事件 如果你想在 畫面載入時執行某些動作,可以用 LaunchedEffect: ```kotlin= @Composable fun LoadDataExample() { var message by remember { mutableStateOf("載入中...") } LaunchedEffect(Unit) { delay(2000) // 模擬載入 2 秒 message = "載入完成!" } Text(text = message, fontSize = 24.sp) } ``` * LaunchedEffect(Unit):只在畫面載入時執行一次 * delay(2000):延遲 2 秒後更新 UI ## UI 範例2 設計一個畫面(包含標題、輸入框、按鈕) ```kotlin= @Composable fun LoginScreen() { var username by remember { mutableStateOf("") } var password by remember { mutableStateOf("") } Column( modifier = Modifier .fillMaxSize() .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Text(text = "登入", fontSize = 30.sp, fontWeight = FontWeight.Bold) Spacer(modifier = Modifier.height(16.dp)) TextField( value = username, onValueChange = { username = it }, label = { Text("使用者名稱") } ) Spacer(modifier = Modifier.height(8.dp)) TextField( value = password, onValueChange = { password = it }, label = { Text("密碼") }, visualTransformation = PasswordVisualTransformation() ) Spacer(modifier = Modifier.height(16.dp)) Button(onClick = { Log.d("LoginScreen", "登入按鈕被點擊") }) { Text("登入") } } } ``` 🔹 TextField → 用來輸入帳號密碼 🔹 PasswordVisualTransformation() → 讓密碼隱藏 🔹 Spacer(modifier = Modifier.height(16.dp)) → 增加間距 🔹 Button → 登入按鈕 ### 顯示 UI 💡在 MainActivity.kt ```kotlin= class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MyTestTheme { LoginScreen() // 設定要顯示的 UI } } } } ``` LoginScreen() 就會顯示在 MainActivity 內! ## UI Text ```kotlin= @Composable fun TextExample() { Text( text = "Hello, Compose!", fontSize = 24.sp, fontWeight = FontWeight.Bold, color = Color.Blue ) } ``` 🔹 fontSize = 24.sp → 設定文字大小 🔹 fontWeight = FontWeight.Bold → 設定字體加粗 🔹 color = Color.Blue → 設定文字顏色 ## UI Button ```kotlin= @Composable fun ButtonExample() { Button( onClick = { Log.d("ButtonExample", "按鈕被點擊!") } ) { Text(text = "點我!") } } ``` 🔹 onClick = {} → 設定按鈕點擊事件 * onClick 短按時執行 * onLongClick 長按時執行 🔹 Text(text = "...") → 按鈕內的文字 ## UI Image ```kotlin= @Composable fun ImageExample() { Image( painter = painterResource(id = R.drawable.sample_image), contentDescription = "示例圖片", modifier = Modifier.size(200.dp) ) } ``` 🔹 painterResource(id = R.drawable.sample_image) → 加載 res/drawable 內的圖片 🔹 modifier = Modifier.size(200.dp) → 設定圖片大小 ## UI TextField(文字輸入框) ```kotlin= @Composable fun TextFieldExample() { var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, label = { Text("輸入文字") } ) } ``` 🔹 remember { mutableStateOf("") } → 記錄輸入的值 🔹 onValueChange = { text = it } → 更新輸入的內容 ## UI Switch(切換開關) ```kotlin= @Composable fun SwitchExample() { var isChecked by remember { mutableStateOf(false) } Switch( checked = isChecked, onCheckedChange = { isChecked = it } ) } ``` 🔹 checked = isChecked → 綁定開關狀態 🔹 onCheckedChange = {} → 設定開關事件 ## Column 垂直排列 Column 是一種 Composable 函數,用來 垂直排列 其內部的子項目 ```kotlin= @Composable fun ColumnExample() { Column( modifier = Modifier .fillMaxSize() //讓 Column 填滿整個螢幕 .padding(16.dp), //設定與邊界的距離 verticalArrangement = Arrangement.spacedBy(8.dp), // 控制子項的垂直間距 horizontalAlignment = Alignment.CenterHorizontally // 控制子項的水平對齊方式 ) { Text(text = "第一個項目") Text(text = "第二個項目") Button(onClick = { /* 點擊事件 */ }) { Text(text = "點我") } } } ``` * modifier:用來設定 Column 的大小、間距、顏色等。 [modifier基本設定](https://hackmd.io/ACwuFxU-RMGrzvzTix4qeA?view#Modifier) * verticalArrangement:設定子項在垂直方向上的排列方式,例如: Arrangement.Top(預設):從上到下排列 Arrangement.Center:子項置中排列 Arrangement.Bottom:從下到上排列 Arrangement.SpaceBetween:首項和尾項貼齊,其他均勻分布 Arrangement.SpaceEvenly:所有子項均勻分布(含首尾) Arrangement.SpaceAround:每個子項之間間距相等,首尾的間距為子項間距的一半 * horizontalAlignment:設定子項在水平方向的對齊方式,例如: Alignment.Start:靠左對齊 Alignment.CenterHorizontally:置中對齊 Alignment.End:靠右對齊 Arrangement.SpaceAround:元素之間有相等的間距 ### 背景設定 ```kotlin= Box( modifier = Modifier .fillMaxSize() .padding(12.dp) // 設定內部間距 .background(Color.Cyan) // 設定背景顏色 .size(200.dp) .border(4.dp, Color.Red, RoundedCornerShape(16.dp)) // 設定邊框 ) ``` ### 補充 如果需要 水平排列 子項,可以使用 Row,如果需要 重疊 子項,可以使用 Box。 ### 垂直的物件中也有水平的寫法 ```csharp= Box( ... ) { Text(...) //這裡用Row,用設定來控制物件水平擺放!!! Row(Modifier.align(Alignment.Center), verticalAlignment = Alignment.CenterVertically) { OutlinedTextField(...) Icon(...) OutlinedTextField(...) } Button(...) { Text("HAHAHA") } } ``` ## UI 基本設定 在UI物件後的()內加入一些設定即可 ### Text ```kotlin= @Composable fun StyledText() { Text( text = "Hello Compose!", fontSize = 24.sp, // 文字大小 fontWeight = FontWeight.Bold, // 字體加粗 color = Color.Blue, // 文字顏色 textAlign = TextAlign.Center, // 文字對齊 fontFamily = FontFamily.Serif, // 設定字型 modifier = Modifier.fillMaxWidth() // 讓文字撐滿整行 modifier = Modifier .clip(RoundedCornerShape(12.dp)) // 設定圓角 .background(Color.Green) // 設定背景顏色 .padding(12.dp) // 增加內距,讓背景有空間 ) } ``` ### Button ```kotlin= @Composable fun StyledButton() { Button( onClick = { /* 按鈕點擊事件 */ }, colors = ButtonDefaults.buttonColors( containerColor = Color.Green, // 按鈕背景顏色 contentColor = Color.White // 按鈕內文字顏色 ), modifier = Modifier .padding(16.dp) // 設定按鈕與周圍的間距 .size(width = 150.dp, height = 50.dp) // 設定按鈕大小 ) { Text("點我!") } } ``` ### Image ```kotlin= @Composable fun ImageExample() { Image( painter = painterResource(id = R.drawable.sample_image), contentDescription = "範例圖片", modifier = Modifier .size(150.dp) // 設定圖片大小 .clip(RoundedCornerShape(12.dp)) // 設定圓角 ) } ``` 🔹 size(150.dp) → 設定圖片大小 🔹 clip(RoundedCornerShape(12.dp)) → 設定圖片圓角 ### TextField(文字輸入框) ```kotlin= @Composable fun TextFieldExample() { var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, label = { Text("輸入文字") } ) } ``` 🔹 remember { mutableStateOf("") } → 記錄輸入的值 🔹 onValueChange = { text = it } → 更新輸入的內容 ### Switch(切換開關) ```kotlin= @Composable fun SwitchExample() { var isChecked by remember { mutableStateOf(false) } Switch( checked = isChecked, onCheckedChange = { isChecked = it } ) } ``` 🔹 checked = isChecked → 綁定開關狀態 🔹 onCheckedChange = {} → 設定開關事件 ### Spacer(間距) ```kotlin= @Composable fun SpacerExample() { Column { Text("第一行") Spacer(modifier = Modifier.height(16.dp)) // 設定垂直間距 Text("第二行") } } ``` 🔹 Spacer(modifier = Modifier.height(16.dp)) → 設定 16dp 的垂直間距 🔹 Spacer(modifier = Modifier.width(16.dp)) → 設定 16dp 的水平間距 ### 總結 Jetpack Compose UI 基本設定 功能 | 語法範例 -------------|------------------------------------------------------ 文字大小 | fontSize = 24.sp 文字顏色 | color = Color.Red 按鈕顏色 | ButtonDefaults.buttonColors(containerColor = Color.Green) 填滿畫面 | fillMaxSize() 間距 | padding(16.dp) 背景顏色 | background(Color.Yellow) 邊框 | border(2.dp, Color.Black, RoundedCornerShape(8.dp)) 圖片圓角 | clip(RoundedCornerShape(12.dp)) 間距元件 | Spacer(modifier = Modifier.height(16.dp)) ## Modifier 調整大小、間距、顏色、點擊事件 等等。 ```kotlin= @Composable fun ModifierExample() { Text( text = "Hello Compose!", modifier = Modifier .padding(16.dp) // 設定內邊距 .background(Color.Cyan) // 設定背景顏色 .border(2.dp, Color.Black) // 設定邊框 .padding(8.dp) // 內部再加一層 padding ) } ``` | 功能 | 語法 | 作用 | |----------------|---------------------------------------|----------------------------| | 內邊距 | `padding(16.dp)` | 設定內部間距 | | 外邊距 | `padding(start = 8.dp, top = 16.dp)`| 設定個別方向的間距 | | 填滿寬度 | `fillMaxWidth()` | 讓元件填滿父容器的寬度 | | 填滿高度 | `fillMaxHeight()` | 讓元件填滿父容器的高度 | | 填滿整個區域 | `fillMaxSize()` | 讓元件填滿父容器的寬度 + 高度 | | 設定大小 | `size(100.dp, 50.dp)` | 設定元件的固定大小 | | 設定背景顏色 | `background(Color.Red)` | 設定背景顏色 | | 設定圓角背景 | `clip(RoundedCornerShape(8.dp))` | 設定圓角效果 | | 設定邊框 | `border(2.dp, Color.Black)` | 設定邊框粗細、顏色 | | 設定陰影 | `shadow(8.dp, RoundedCornerShape(12.dp))` | 設定陰影效果 | | 設定點擊事件 | `clickable { /* 點擊時執行 */ }` | 設定點擊行為 | ## 變數 [kotlin筆記](/aziUKMMTT3qzMkJG5lzrXg),這裡有說到一些相關知識 ```kotlin= var random by remember { mutableStateOf(0) } //int var guess by remember { mutableStateOf("") } //string ``` ---- ```kotlin= val name: String = "Alice" // 不可變 var age: Int = 25 // 可變 ``` 但在 Jetpack Compose 的 Composable 函式中,這種變數 不會觸發 UI 更新。 ### remember、random remember 的作用是讓變數在組合時保持不變,避免每次組合時變數重新初始化: ```kotlin= val randomValue = (1..100).random() // 這個值每次重組時都會變 val rememberedValue = remember { (1..100).random() } // 這個值只會初始化一次 ``` ### rememberSaveable 渲染問題 因為螢幕旋轉、App 進入背景等情況被銷毀,remember 會 失效 ```kotlin= @Composable fun SaveableExample() { var text by rememberSaveable { mutableStateOf("") } // 當畫面旋轉時,值不會丟失 Column { TextField( value = text, onValueChange = { text = it } ) Text(text = "輸入: $text") } } ----------------------------------------------------------- var tall by rememberSaveable { 0 } // 正確 ✅ ``` 🔹 rememberSaveable 會 自動保存變數狀態,即使畫面旋轉,輸入的文字 仍然會被保留。 ### mutableStateListOf列表 如果要讓列表具有 狀態管理,可以用 mutableStateListOf(): ```kotlin= @Composable fun TodoList() { val tasks = remember { mutableStateListOf("買牛奶", "運動", "寫程式") } Column { tasks.forEach { task -> Text(text = task) } Button(onClick = { tasks.add("新任務") }) { Text("新增任務") } } } ``` 🔹 mutableStateListOf() 讓列表能夠響應 add()、remove() 操作,自動更新 UI。 ### mutableStateMapOf() (可變 Map) ```kotlin= @Composable fun UserMap() { val users = remember { mutableStateMapOf("Alice" to 25, "Bob" to 30) } Column { users.forEach { (name, age) -> Text(text = "$name, 年齡: $age") } Button(onClick = { users["Charlie"] = 28 }) { Text("新增 Charlie") } } } ``` 🔹 mutableStateMapOf() 讓 Map 變數可以響應 UI 變化。 ### 總結 | 方法 | 用途 | UI 會自動更新? | 旋轉螢幕後會保留值? | |-----------------------------------|--------------------|----------------|---------------------| | `val / var` | 一般變數 | ❌ | ❌ | | `remember { mutableStateOf(...) }`| UI 變數 | ✅ | ❌ | | `rememberSaveable { mutableStateOf(...) }` | 可保存的 UI 變數 | ✅ | ✅ | | `mutableStateListOf()` | 可變列表 | ✅ | ❌ | | `mutableStateMapOf()` | 可變 Map | ✅ | ❌ | ## LaunchedEffect(Unit) 啟動時運行 LaunchedEffect 會在 Composable 函式啟動時運行一次,當 key 變更時重新運行。 LaunchedEffect 用來執行 副作用 (side effect),例如: * 網路請求 * 資料庫查詢 * 延遲操作 (delay()) * 只執行一次的初始化邏輯 ### 基本語法 ```kotlin= LaunchedEffect(Unit) { // 這裡的程式碼只會執行一次 Log.d("LaunchedEffect", "這段程式碼執行了!") } ``` Unit 代表 不依賴任何變數,所以這段程式碼 只會執行一次。 ### LaunchedEffect 只執行一次 ```kotlin= @Composable fun ExampleScreen() { LaunchedEffect(Unit) { Log.d("LaunchedEffect", "這段程式碼只會執行一次!") } Text(text = "Hello Compose!") } ``` LaunchedEffect(Unit) 只會執行一次,不管 ExampleScreen() 重新組合多少次。 ### LaunchedEffect + delay() 可以用 LaunchedEffect 來做 倒數計時: ```kotlin= @Composable fun CountdownTimer() { var timeLeft by remember { mutableStateOf(5) } LaunchedEffect(Unit) { while (timeLeft > 0) { delay(1000L) // 每秒減 1 timeLeft-- } } Text(text = "倒數計時: $timeLeft 秒") } ``` 🔹 delay(1000L) → 每秒更新一次 timeLeft 🔹 當 timeLeft == 0,while 迴圈結束 ### key變更時重新執行 如果 LaunchedEffect 傳入一個 可變 Key,當 Key 變更時,LaunchedEffect 會重新執行。 ```kotlin= @Composable fun LaunchedEffectWithKey(userId: String) { LaunchedEffect(userId) { Log.d("LaunchedEffect", "載入用戶資料: $userId") } Text(text = "用戶 ID: $userId") } ``` 🔹 LaunchedEffect(userId) * 當 userId 改變時,LaunchedEffect 會重新執行 * 但如果 userId 沒變,LaunchedEffect 不會重新執行 ## Shared Preferences 儲存 這個儲存就是不管你關閉再開啟程式 還是 關閉模擬機再開啟,你的資料都會在 [elias chen](https://hackmd.io/@eliaschen-dev) 裡整理的好棒棒!!默默學習做筆記... ```kotlin= val context = LocalContext.current //宣告 val sharedPref = context.getSharedPreferences("test", Context.MODE_PRIVATE) //存取random的值。如果沒有就存0 var random by remember { mutableStateOf(sharedPref.getInt("random", 0) ?: 0) } LaunchedEffect(random) { sharedPref.edit().putInt("random", random).apply() } ``` ## let + 函數 用來避免 null 錯誤。 ### let 的語法,判斷是否為null ```kotlin= 變數?.let { 非空時執行的程式碼 } ``` ### 等價寫法 (使用 if 判斷): ```kotlin= if (bmi != null) { Text("你的 BMI: ${String.format("%.2f", bmi)}") } ``` 使用 ?.let 更簡潔,不需要手動檢查 null。 ## 套件安裝 在資料裡點選build.gradle.kts ![image](https://hackmd.io/_uploads/HyNyQpGSel.png) 即可加入要安裝的套件,再回到主程式碼重新更新