解读Compose的项目中的知识点

本篇文章转自麦客奥德彪的博客,文章主要分享了他如何阅读Compose项目中的知识点,相信会对大家有所帮助!

原文地址:

https://juejin.cn/post/7377439806135795764

/   前言   /

我是一个非常喜欢学习的人,在这行业中摸爬滚打这么多年,靠的就是技术栈从未落后过。然而,不幸的是,公司不允许使用kotlin, 更别提compose了。

这就尴尬了,如果是我刚毕业那会儿,这无所谓,因为我有大把的时间弥补欠缺,之后的工作中补回来就OK了。

更尴尬的是,我30了,最最尴尬的是,这东西学了不用就全忘了,特别是理论的东西,又没有机会写,只能读项目了。

/   读项目吧   /

读之前也要做些了解的,比如:

  1. Compose 开发中的架构问题

  2. 页面应该怎么写

  3. 常用的控件啊 这些无所谓

我选择的项目是官方的nowinandroid, 他的好处在于它是一个教程类项目,教程类项目肯定有很多相关的 “炫技” 在里面。


阅读方式

这没办法扯,就这吧,当然:

  1. 你可以从MainActivity 中一行行读

  2. 也可以跳着读,遇到不懂的技术点时弄明白,直到没有看不懂的技术为止(小递归,如果每次读下来判断条件没有变化,那就不要读了,不要再学习了,转前端吧!转大前端吧!)

所以我记录的是这个项目中我不懂的一个个技术点。

/   知识点 Effect及带出来的问题   /

首页第一个看不明白的就是:

DisposableEffect(darkTheme) {
                //...
            }

DisposableEffect, 位于Effect.kt 文件中,(这里有个小技巧,kotlin 作为可顶级函数编程模式,导致很多功能相似或相同的函数都位于同一个文件中)

查阅发现一共有以下几种:

  1. SideEffect

  2. LaunchedEffect

  3. DisposableEffect

LaunchedEffect 分析

@Composable
@NonRestartableComposable
@OptIn(InternalComposeApi::class)
fun LaunchedEffect(
    key1: Any?,
    block: suspend CoroutineScope.() -> Unit
) {
    val applyContext = currentComposer.applyCoroutineContext
    remember(key1) { LaunchedEffectImpl(applyContext, block) }
}

remember 是什么?

remember 是一个函数,用于在 Composable 函数中记住(缓存)某个值,并在 UI 重新组合时保持该值不变

@Composable
inline fun <T> remember(
    key1: Any?,
    crossinline calculation: @DisallowComposableCalls () -> T
): T {
    return currentComposer.cache(currentComposer.changed(key1), calculation)
}

@ComposeCompilerApi
inline fun <T> Composer.cache(invalid: Boolean, block: @DisallowComposableCalls () -> T): T {
    @Suppress("UNCHECKED_CAST")
    return rememberedValue().let {
        if (invalid || it === Composer.Empty) {
            val value = block()
            updateRememberedValue(value)
            value
        } else it
    } as T
}

当currentComposer.changed(key1) 的值发生变化时,会更新存储并且执行calculation。

在LaunchedEffect中的rember 值变化时执行的LaunchedEffectImpl 是什么?

internal class LaunchedEffectImpl(
    parentCoroutineContext: CoroutineContext,
    private val task: suspend CoroutineScope.() -> Unit
) : RememberObserver {
    private val scope = CoroutineScope(parentCoroutineContext)
    private var job: Job? = null

    override fun onRemembered() {
        // This should never happen but is left here for safety
        job?.cancel("Old job was still running!")
        job = scope.launch(block = task)
    }

    override fun onForgotten() {
        job?.cancel(LeftCompositionCancellationException())
        job = null
    }

    override fun onAbandoned() {
        job?.cancel(LeftCompositionCancellationException())
        job = null
    }
}

实现了RememberObserver。


RememberObserver

RememberObserver 是一个接口,用于管理 remember 状态对象的生命周期。通过实现这个接口,可以在状态对象的创建、进入和离开 Composition 的时候执行特定的逻辑。

RememberObserver 详解

RememberObserver 接口包含三个主要的生命周期回调:

  1. onRemembered:当状态对象被创建并进入 Composition 时调用。

  2. onForgotten:当状态对象离开 Composition 时调用。

  3. onAbandoned:当状态对象由于 Composition 中止或重启而被遗弃时调用。

这些回调使你能够在特定的生命周期阶段执行特定的逻辑,例如启动和清理资源。


接口定义
interface RememberObserver {
    fun onRemembered()
    fun onForgotten()
    fun onAbandoned()
}

示例

以下是一个实现 RememberObserver 的简单示例,用于在组件的生命周期内记录日志:

import androidx.compose.runtime.*

class LoggingRememberObserver(private val tag: String) : RememberObserver {
    override fun onRemembered() {
        println("$tag: Remembered")
    }

    override fun onForgotten() {
        println("$tag: Forgotten")
    }

    override fun onAbandoned() {
        println("$tag: Abandoned")
    }
}

@Composable
fun RememberObserverExample() {
    val observer = remember { LoggingRememberObserver("MyObserver") }
    DisposableEffect(observer) {
        onDispose { /* Optionally perform cleanup here */ }
    }

    // Your UI content
    Text("RememberObserver Example")
}

@Preview
@Composable
fun PreviewRememberObserverExample() {
    RememberObserverExample()
}
解释

LoggingRememberObserver

  • 实现 RememberObserver 接口,在每个生命周期回调中记录日志。
  • onRemembered、onForgotten、onAbandoned 分别在不同的生命周期阶段调用。

RememberObserverExample

  • 使用 remember 创建 LoggingRememberObserver 实例,并将其与 Composition 关联。
  • DisposableEffect 可以用于处理在 Composition 离开时的清理操作。

UI 内容

显示一个简单的文本,表示示例的 UI 内容。

RememberObserver 的实际应用

RememberObserver 主要用于需要在状态对象的生命周期内执行特定操作的场景。例如:

  • 资源管理:打开和关闭数据库连接、注册和取消注册监听器等。

  • 副作用管理:启动和停止定时任务、管理网络请求的生命周期等。(而Effect 正是利用这个完成他们的功能的)

  • 性能优化:在适当的时候初始化和销毁昂贵的资源。


Composition 是什么

Composition 是一个核心概念,它描述了如何将 UI 的状态与其界面元素关联起来。具体来说,Composition 是一个将可组合函数(Composables)及其状态在运行时结合在一起的过程。

关键点

声明式 UI

在 Jetpack Compose 中,UI 是声明式的,即 UI 是当前状态的函数。UI 会根据状态的变化自动重组(recompose)。

Composable 函数

@Composable 注解的函数可以定义 UI 元素和布局。这些函数可以组合在一起,形成复杂的 UI 层次结构。

Recomposition

当与 UI 相关的状态发生变化时,Compose 会重新运行相应的可组合函数,以更新 UI。这种重新运行称为重组(Recomposition)。

Composition 的生命周期

Composition 的生命周期与 UI 的生命周期密切相关。当某个 Composable 函数第一次执行时,它进入 Composition。当状态变化导致 UI 需要更新时,该函数会重新组合。

示例

以下是一个简单的 Jetpack Compose 示例,展示了 Composition 的基本概念:
import androidx.compose.runtime.*
import androidx.compose.material.*
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.layout.*

@Composable
fun Greeting(name: String) {
    Text(text = "Hello, $name!")
}

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }

    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center,
        modifier = Modifier.fillMaxSize()
    ) {
        Text(text = "Count: $count", style = MaterialTheme.typography.h4)
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = { count++ }) {
            Text("Increment")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    Column {
        Greeting(name = "Compose")
        Counter()
    }
}
解释

Greeting Composable 函数

定义了一个简单的文本显示函数。Greeting(name: String) 会显示一个带有 name 参数值的文本。

Counter Composable 函数

定义了一个计数器组件,显示当前计数并有一个按钮来增加计数。var count by remember { mutableStateOf(0) } 声明并记住状态 count,当 count 变化时,UI 会自动重组。Button(onClick = { count++ }):按钮点击事件会增加 count。

DefaultPreview Composable 函数

用于预览,展示 Greeting 和 Counter 组件的组合。

Composition 是 Jetpack Compose 中将 UI 与其状态结合在一起的过程。可组合函数是构建 UI 的基本单元,通过组合这些函数,可以构建复杂的 UI。重组是当状态变化时重新运行可组合函数以更新 UI 的过程。

解释Effect问题

结合上述的知识点,这个组件的作用就是根据组件的Composition状态,做一些Compose之外的工作。

在 Jetpack Compose 中,Effects 是用于处理副作用(side effects)的工具。副作用是指在 Compose 之外的环境中执行的操作,比如网络请求、数据库访问、订阅和取消订阅等。Compose 提供了一组用于处理这些操作的 API,确保这些操作在 UI 状态变化时能正确执行。

Compose 中的主要 Effects API:
  1. SideEffect

  2. LaunchedEffect

  3. DisposableEffect


SideEffect

SideEffect 是一个简单的效果处理器,用于在每次重组(recomposition)后执行一些操作。它通常用于调试和日志记录。

@Composable
fun SideEffectExample(counter: Int) {
    SideEffect {
        println("The counter value is $counter")
    }
    Text("Counter: $counter")
}
LaunchedEffect

LaunchedEffect 用于启动协程来处理异步操作。它会在 Composable 进入 Composition 时启动,并在离开 Composition 时取消。它的主要特点是可以依赖于传入的键,当键发生变化时重新启动协程。

@Composable
fun LaunchedEffectExample(data: String) {
    var result by remember { mutableStateOf("Loading...") }

    LaunchedEffect(data) {
        // 模拟网络请求
        delay(1000L)
        result = "Result for $data"
    }

    Text(result)
}
DisposableEffect

DisposableEffect 用于处理在 Composition 生命周期内需要清理的副作用。它会在 Composable 进入 Composition 时启动,在离开时执行清理操作。

@Composable
fun DisposableEffectExample(userId: String) {
    DisposableEffect(userId) {
        println("Start observing $userId")

        onDispose {
            println("Stop observing $userId")
        }
    }
    Text("Observing user: $userId")
}
使用示例综合

以下是一个综合示例,展示了如何在实际应用中使用这些 Effects API:

@Composable
fun ComprehensiveExample(userId: String, counter: Int) {
    var userName by remember { mutableStateOf("Loading...") }

    // LaunchedEffect 用于异步加载数据
    LaunchedEffect(userId) {
        // 模拟网络请求
        delay(1000L)
        userName = "User: $userId"
    }

    // DisposableEffect 用于订阅和取消订阅用户状态
    DisposableEffect(userId) {
        println("Start observing $userId")

        onDispose {
            println("Stop observing $userId")
        }
    }



    Column {
        Text(userName)
        Text("Status: $userStatus")

        // SideEffect 用于记录日志
        SideEffect {
            println("Rendering ComprehensiveExample with userId: $userId and counter: $counter")
        }
    }
}

@Preview
@Composable
fun PreviewComprehensiveExample() {
    ComprehensiveExample(userId = "42", counter = 5)
}
总结
  • SideEffect:用于简单的副作用,如日志记录。

  • LaunchedEffect:用于在 Composition 中启动和管理协程,适合异步操作。

  • DisposableEffect:用于在 Composition 生命周期内处理需要清理的副作用。

/   知识点 CompositionLocalProvider   /

CompositionLocalProvider 是 Jetpack Compose 中用于在局部范围内提供数据的工具。它允许你定义和传递局部数据,而不需要通过参数一级一级地传递。这种机制类似于 React 中的 Context API,但在 Jetpack Compose 中使用的是 CompositionLocal。

CompositionLocalProvider 可以在 Compose 的 Composition 树中设置局部数据,这些数据可以在树中的任何子组件中访问。它通常与 CompositionLocal 一起使用。

核心概念

  • CompositionLocal:这是一个可以在 Composition 树中任何地方访问的局部数据的容器。

  • CompositionLocalProvider:这是用于提供 CompositionLocal 数据的 Composable 函数。


定义 CompositionLocal

首先,需要定义一个 CompositionLocal:

import androidx.compose.runtime.compositionLocalOf

data class User(val name: String, val age: Int)

val LocalUser = compositionLocalOf<User> { error("No user provided") }

在上面的代码中,我们定义了一个 CompositionLocal,用于存储 User 对象。


使用 CompositionLocalProvider

接下来,可以使用 CompositionLocalProvider 在局部范围内提供 User 数据:

import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.material.Text
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun UserInfo() {
    val user = LocalUser.current
    Text("Name: ${user.name}, Age: ${user.age}")
}

@Composable
fun UserScreen() {
    val user = User(name = "John Doe", age = 30)

    CompositionLocalProvider(LocalUser provides user) {
        UserInfo()
    }
}

@Preview
@Composable
fun PreviewUserScreen() {
    UserScreen()
}

解释


定义 LocalUser


compositionLocalOf<User> 定义了一个 CompositionLocal,它存储 User 类型的值。默认情况下,如果没有提供值,会抛出异常。


UserInfo Composable


使用 LocalUser.current 获取当前的 User 对象,并显示其姓名和年龄。


UserScreen Composable


创建一个 User 对象,并使用 CompositionLocalProvider 提供 LocalUser。在 CompositionLocalProvider 的作用范围内,LocalUser 的值将是我们提供的 user 对象。


多个 CompositionLocalProvider

可以在同一个 CompositionLocalProvider 中提供多个 CompositionLocal,如下所示:

import androidx.compose.runtime.staticCompositionLocalOf

val LocalThemeColor = staticCompositionLocalOf { Color.Black }

@Composable
fun ThemedUserInfo() {
    val user = LocalUser.current
    val color = LocalThemeColor.current
    Text("Name: ${user.name}, Age: ${user.age}", color = color)
}

@Composable
fun ThemedUserScreen() {
    val user = User(name = "Jane Doe", age = 28)
    val themeColor = Color.Blue

    CompositionLocalProvider(
        LocalUser provides user,
        LocalThemeColor provides themeColor
    ) {
        ThemedUserInfo()
    }
}

@Preview
@Composable
fun PreviewThemedUserScreen() {
    ThemedUserScreen()
}

解释


定义 LocalThemeColor


staticCompositionLocalOf 定义了一个静态的 CompositionLocal,它存储 Color 类型的值。


ThemedUserInfo Composable


  • 使用 LocalUser.current 获取当前的 User 对象。

  • 使用 LocalThemeColor.current 获取当前的颜色值,并将其应用到文本颜色上。


ThemedUserScreen Composable


使用 CompositionLocalProvider 同时提供 LocalUser 和 LocalThemeColor。

只要是在 CompositionLocalProvider 的 content 块中调用的所有 Composable 函数,都可以访问 CompositionLocal 提供的数据。这是因为 CompositionLocalProvider 会在其 content 块内设置一个作用域,该作用域内所有的 Composable 都可以访问通过 CompositionLocal 提供的数据。

CompositionLocal的方式都有哪些

有两种主要的方式来定义 CompositionLocal,分别是 compositionLocalOf 和 staticCompositionLocalOf。下面是它们的详细介绍:


compositionLocalOf

compositionLocalOf 是一种在运行时动态创建 CompositionLocal 的方式。它允许你使用一个默认值来定义 CompositionLocal,并在需要的时候通过提供不同的值来替换它。

使用方法:
val LocalUser = compositionLocalOf<User> { error("No user provided") }
  • LocalUser:定义的 CompositionLocal 变量。

  • compositionLocalOf<User>:创建一个 CompositionLocal,其值的类型是 User。

  • { error("No user provided") }:提供一个默认值,当没有提供具体值时将抛出错误。


示例:

val LocalUser = compositionLocalOf<User> { error("No user provided") }

@Composable
fun UserInfo() {
    val user = LocalUser.current
    Text("Name: ${user.name}")
}

@Composable
fun UserScreen() {
    val user = User(name = "John Doe", age = 30)

    CompositionLocalProvider(LocalUser provides user) {
        UserInfo()
    }
}

staticCompositionLocalOf

staticCompositionLocalOf 是一种在编译时静态创建 CompositionLocal 的方式。它在声明时就提供了一个默认值,并且该值是不可修改的。

使用方法:

val LocalThemeColor = staticCompositionLocalOf { Color.Black }
  • LocalThemeColor:定义的 CompositionLocal 变量。

  • staticCompositionLocalOf:创建一个静态的 CompositionLocal。

  • { Color.Black }:提供一个默认值,该值在编译时确定,不可更改。


示例:

val LocalThemeColor = staticCompositionLocalOf { Color.Black }

@Composable
fun ThemedUserInfo() {
    val color = LocalThemeColor.current
    Text("User Info", color = color)
}

@Composable
fun ThemedUserScreen() {
    CompositionLocalProvider(LocalThemeColor provides Color.Blue) {
        ThemedUserInfo()
    }
}

区别和适用场景

动态 vs. 静态

  • compositionLocalOf 提供了动态创建 CompositionLocal 的方式,适用于需要在运行时根据条件提供不同值的情况。

  • staticCompositionLocalOf 提供了在编译时确定默认值的方式,适用于值是固定的情况。

默认值的处理

  • compositionLocalOf 允许在提供默认值时执行代码逻辑,例如抛出异常等。

  • staticCompositionLocalOf 只能在编译时确定一个不可修改的默认值。

可变性

无论是动态还是静态创建的 CompositionLocal,其值都是可变的,可以在需要的时候通过 CompositionLocalProvider 提供不同的值。

一般来说,如果需要在运行时根据条件提供不同的默认值,或者需要在提供默认值时执行一些逻辑,那么使用 compositionLocalOf 更为适合。如果你的值是固定的,不会在运行时改变,那么使用 staticCompositionLocalOf 更为合适。

关注我获取更多知识或者投稿

6fc0455d88f91cbb9e251c9d1f3b3db4.jpeg

1811682a993fe13b778bb601289b63c5.jpeg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值