Kotlin特别记录
Kotlin 学习
| 天 | 学什么 |
|---|
| Day 1 | Kotlin 基础 + 空安全 |
| Day 2 | Lambda / data class / 扩展 |
| Day 3 | when / sealed class |
| Day 4 | 协程 Coroutine |
| Day 5 | Compose 基础 |
| Day 6 | Compose + ViewModel |
| Day 7 | 写一个完整小 App |
推荐技术栈总表
| 分类 | 官方推荐 |
|---|
| 语言 | Kotlin |
| UI | Jetpack Compose |
| 架构 | MVVM |
| 状态 | StateFlow |
| 异步 | Coroutine |
| 数据库 | Room |
| 本地存储 | DataStore |
| 网络 | Retrofit + OkHttp |
| DI | Hilt |
| 导航 | Navigation Compose |
Modifier 理解
1 2 3 4
| Modifier .width(100.dp) .height(50.dp) .fillMaxWidth()
|
- 外观(Draw)
1 2 3
| Modifier .background(Color.Blue) .clip(RoundedCornerShape(8.dp))
|
- 行为(Interaction)
1 2
| Modifier .clickable { }
|
- 手势 & 输入
1 2 3
| .pointerInput(Unit) { detectTapGestures { } }
|
- 坑
- Modifier 顺序 = 生命线,顺序不同,效果完全不同
- 示例1
1 2 3 4 5 6 7 8
| Modifier .background(Color.Red) .padding(16.dp)
// 情况 B Modifier .padding(16.dp) .background(Color.Red)
|
区别 - A:背景包含 padding - B:背景不包含 padding
- 示例2 clickable 放哪?
1 2 3 4 5 6 7 8
| // 情况 A Modifier .padding(16.dp) .clickable { } //情况B Modifier .clickable { } .padding(16.dp)
|
区别 - A:点击区域包含 padding - 点击区域不包含 padding(很坑)
1 2
| .fillMaxWidth() .padding(horizontal = 16.dp)
|
- 卡片样式
1 2 3 4
| .fillMaxWidth() .padding(8.dp) .clip(RoundedCornerShape(8.dp)) .background(Color.White)
|
- 点击 + Ripple
1 2 3 4 5
| Modifier .clickable( indication = rememberRipple(), interactionSource = remember { MutableInteractionSource() } ) { }
|
LocalContext理解 val context = LocalContext.current
- LocalContext:是一个 CompositionLocal 对象,用于在 Compose 树中隐式传递 Android Context
- .current:获取当前 Composable 函数所在位置的 Context 值
- 在 Compose 中,你不能直接使用 Activity.this 或 applicationContext,因为:Compose 函数不是传统的 View 或 Activity,需要一种在组件树中安全传递 Context 的机制
- 用法:
- 获取资源
1 2 3 4 5 6 7 8 9 10
| val context = LocalContext.current
// 获取字符串 val appName = context.getString(R.string.app_name)
// 获取颜色 val primaryColor = context.getColor(R.color.primary)
// 获取尺寸 val spacing = context.resources.getDimension(R.dimen.spacing_small)
|
2. 启动 Activity:context.startActivity(Intent(context, DetailActivity::class.java))
3. 显示 Toast :Toast.makeText(context, "消息", Toast.LENGTH_SHORT).show()
4. 访问系统服务:val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
rememberCoroutineScope 解析,val scope = rememberCoroutineScope()
1. 概念:rememberCoroutineScope 是 Jetpack Compose 中获取与当前 Composable 生命周期绑定的协程作用域的关键函数。val scope = rememberCoroutineScope()
2. 核心特性
1. 生命周期感知
- 自动取消:当 Composable 离开组合(退出屏幕/销毁)时,作用域会自动取消
- 避免泄漏:防止协程在 Composable 销毁后继续运行
2. 响应重组
- 记住状态:在重组期间保持同一个协程作用域实例
- 不会重复创建:避免不必要的资源开销
3. 基本用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Composable fun ExampleScreen() { val scope = rememberCoroutineScope() Button( onClick = { scope.launch { // 执行异步操作 delay(1000) // 更新 UI 状态(需在协程内) } } ) { Text("执行任务") } }
|
- 处理点击事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @Composable fun SearchButton() { val scope = rememberCoroutineScope() var results by remember { mutableStateOf<List<String>>(emptyList()) } Column { Button( onClick = { scope.launch { // 发起网络请求 results = performSearch() } } ) { Text("搜索") } // 显示结果 results.forEach { Text(it) } } }
suspend fun performSearch(): List<String> { delay(2000) // 模拟网络请求 return listOf("结果1", "结果2") }
|
- 启动动画
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Composable fun AnimatedBox() { val scope = rememberCoroutineScope() var animated by remember { mutableStateOf(false) } Box( modifier = Modifier .size(if (animated) 200.dp else 100.dp) .background(Color.Blue) .clickable { scope.launch { animated = !animated delay(1000) // 动画延时 animated = !animated } } ) }
|
- 收集 Flow
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Composable fun FlowCollector() { val scope = rememberCoroutineScope() var value by remember { mutableStateOf(0) } // 在副作用中启动协程 LaunchedEffect(Unit) { scope.launch { flow { repeat(10) { emit(it) delay(1000) } }.collect { value = it } } } Text("当前值: $value") }
|
LaunchedEffect 解析
1. 概念:LaunchedEffect 是 Compose 中用于在进入组合时自动执行挂起函数的副作用 API。
1 2 3
| LaunchedEffect(key1, key2, ...) { // 进入组合时自动执行的协程代码块 }
|
- 核心特性
- 自动启动
- 无需手动触发:进入组合时自动执行
- 一次性执行:默认只在首次组合时执行一次
- 生命周期绑定
- 进入组合时启动:当 Composable 显示时启动协程
- 退出组合时取消:当 Composable 离开组合时自动取消协程
- key 变化时重启:当依赖的 key 变化时,会重启协程
- 使用场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Composable fun UserProfileScreen(userId: String) { var userData by remember { mutableStateOf<User?>(null) } var isLoading by remember { mutableStateOf(true) } // 当 userId 变化时重新加载数据 LaunchedEffect(userId) { isLoading = true userData = userRepository.getUser(userId) isLoading = false } if (isLoading) { LoadingIndicator() } else { UserProfile(user = userData) } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Composable fun TimerScreen() { var currentTime by remember { mutableStateOf(0L) } // 监听时间 Flow LaunchedEffect(Unit) { // Unit 表示只执行一次 timerFlow .onEach { time -> currentTime = time } .collect() } Text("时间: $currentTime") }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Composable fun FadeInContent() { var alpha by remember { mutableStateOf(0f) } LaunchedEffect(Unit) { // 进入时执行渐入动画 animate(0f, 1f) { value, _ -> alpha = value delay(16) // 约 60fps } } Box( modifier = Modifier .fillMaxSize() .graphicsLayer { this.alpha = alpha } ) { Text("渐入内容") } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Composable fun WebSocketConnection() { LaunchedEffect(Unit) { val socket = openWebSocket() try { socket.collect { message -> // 处理消息 } } finally { // ✅ 确保连接关闭 socket.close() } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Composable fun UserDetailScreen(userId: String) { var user by remember { mutableStateOf<User?>(null) } // 初始加载 LaunchedEffect(userId) { user = loadUser(userId) } // UI 包含刷新按钮 UserDetail( user = user, onRefresh = { // 使用 rememberCoroutineScope 手动刷新 } ) }
|
1 2 3 4 5 6 7 8 9 10 11
| @Composable fun LiveDataScreen() { var liveData by remember { mutableStateOf<Data?>(null) } LaunchedEffect(Unit) { while (true) { liveData = fetchLiveData() delay(5000) // 每 5 秒轮询一次 } } }
|