聊聊 Jetpack Compose 原理 -- 穿透刺客 CompositionLocal

Compose 官方说明一直很简洁:CompositionLocal 是通过组合隐式向下传递数据的工具。


我们先来看一段代码:

class MainActivity : ComponentActivity() {
   
    override fun onCreate(savedInstanceState: Bundle?) {
   
        super.onCreate(savedInstanceState)

        setContent {
   
            ComposeBlogTheme {
   
                val name = "Hi, Compose!"    // name 是局部变量
                ShowMessage(name)
            }
        }
    }
}

@Composable
fun ShowMessage(message: String) {
   
    Text(message)
}

这段代码中局部变量 name 被暴露出来了,这就是我们常见的 State Hoisting(状态提升)。

🤔 🤔 现在思考两个问题:

class MainActivity : ComponentActivity() {
   
    override fun onCreate(savedInstanceState: Bundle?) {
   
        super.onCreate(savedInstanceState)

        setContent {
   
            ComposeBlogTheme {
   
                val name = "Hi, Compose!"
                ShowMessage(name)
            }
         👉🏻 name =     // 思考 1: 这边能不能获取到 name?
        }
    }
}

@Composable
fun ShowMessage(message: String) {
   
 👉🏻 name =             // 思考 2: 这边能不能获取到 name?
    Text(message)
}

答案是:肯定不行!因为 name 这个局部变量的 作用域 是限定在 ComposeBlogTheme {} 范围内的。

那么你可能会有疑惑,为什么要思考这么常识的问题?我现在把代码改一下:

class MainActivity : ComponentActivity() {
   
    override fun onCreate(savedInstanceState: Bundle?) {
   
        super.onCreate(savedInstanceState)

        setContent {
   
            ComposeBlogTheme {
   
                val name = "Hi, Compose!"
                // ShowMessage(name)    // 这段代码我不要了
            }
        }
    }
}

@Composable
fun ShowMessage() {
       // 参数我也去掉,不需要外面传参进来
    Text(name)         // 我就直接把外面的 name 写到这里,直接用
}

这样写行吗?肯定是不行的!但我既然这么写了,肯定是有目的的。如果我们的代码这样写,还希望它可以运行,并且结果正确!那么也就意味着 name 这个局部变量拥有了可以穿出它的 作用域,并且穿透进 showMessage 函数的能力!

其实我们可以实现这样的效果,主角登场:CompositionLocal,它就提供了这种 「穿透能力」


创建 CompositionLocal 实例

首先创建一个具有 「穿透能力」 的变量:

class MainActivity : ComponentActivity() {
   
    override fun onCreate(savedInstanceState: Bundle?) {
   
        super.onCreate(savedInstanceState)

        setContent {
   
            ComposeBlogTheme {
   
                val name = "Hi, Compose!"
            }
        }
    }
}

@Composable
fun ShowMessage() {
   
    Text(name)
}

// 1. 通过 compostionLocalOf 创建 CompositionLocal
val LocalName = compositionLocalOf<String> {
    "Compose" }

Compose 中,一般定义这种具有「穿透能力」CompostionLocal,它的名称有个约定俗成的写法:LocalXXX


为 CompositionLocal 提供值

CompostionLocal 实例定义好了,我们要通过 provides 给它绑定一个值。

class MainActivity : ComponentActivity() {
   
    override fun onCreate(savedInstanceState: Bundle?) {
   
        super.onCreate(savedInstanceState)

        setContent {
   
            ComposeBlogTheme {
   
                val name = "Hi, Compose!"
                // 2. CompositionLocalProvider 可组合项可将值绑定到给定层次结构的 CompositionLocal 实例
                CompositionLocalProvider(LocalName provides name) {
   
                    ShowMessage()
                }
            }
        }
    }
}

@Composable
fun ShowMessage() {
   
    Text(name)
}

// 1. 通过 compostionLocalOf 创建 CompositionLocal
val LocalName = compositionLocalOf<String> {
    "Compose" }

获取 CompositionLocal 数据

前面两步其实就已经创建好了一个包含 数据 + 可穿透CompositionLocal 了,最后一步就是获取到其中的数据。

class MainActivity : ComponentActivity() {
   
    override fun onCreate(savedInstanceState: Bundle?) {
   
        super.onCreate(savedInstanceState)

        setContent {
   
            ComposeBlogTheme {
   
                val name = "Hi, Compose!"
                // 2. CompositionLocalProvider 可组合项可将值绑定到给定层次结构的 CompositionLocal 实例
                CompositionLocalProvider(LocalName provides name) {
   
                    ShowMessage()
                }
            }
        }
    }
}

@Composable
fun ShowMessage() {
   
    // 3. 获取 name
    Text(LocalName.current)
}

// 1. 通过 compostionLocalOf 创建 CompositionLocal
val LocalName = compositionLocalOf<String> {
    "Compose" }

👌🏼👌🏼👌🏼,搞定!


CompositionLocal 应用场景

提供上下文

LocalContext.current    // 是不是很熟悉?这其实就相当于 getContext()

MaterialTheme

其实官方的 MaterialTheme 就用到了 CompositionLocal,我们看源码:

@Composable
fun MaterialTheme(
    colors: Colors = MaterialTheme.colors,
    typography: Typography = MaterialTheme.typography,
    shapes: Shapes = MaterialTheme.shapes,
    content: @Composable () -> Unit
) {
   
    val rememberedColors = remember {
    colors.copy() }.apply {
    updateColorsFrom(colors) }
    val rippleIndication = rememberRipple()
    val selectionColors = rememberTextSelectionColors(rememberedColors)
    // 通过 providers 将 rememberedColors 提供给了 LocalColors
    CompositionLocalProvider(
        LocalColors provides rememberedColors,
        LocalContentAlpha provides ContentAlpha.high,
        LocalIndication provides rippleIndication,
        LocalRippleTheme provides MaterialRippleTheme,
        LocalShapes provides shapes,
        LocalTextSelectionColors provides selectionColors,
        LocalTypography provides typography
    ) {
   
        ProvideTextStyle(value = typography.body1) {
   
            PlatformMaterialTheme(content)
        }
    }
}
object MaterialTheme {
   
    val colors: Colors
        @Composable
        @ReadOnlyComposable
        get() = LocalColors.current

    val typography: Typography
        @Composable
        @ReadOnlyComposable
        get() = LocalTypography.current

    val shapes: Shapes
        @Composable
        @ReadOnlyComposable
        get() = LocalShapes.current
}

compositionLocalOf / staticCompositionLocalOf 的区别

前面的例子我们使用 compositionLocalOf 创建 CompositionLocal,另外官方还提供了 staticCompositionLocalOf


📌 compositionLocalOf:在重组期间更改提供的值只会使读取其 current 值的内容无效。


📌 staticCompositionLocalOf:与 compositionLocalOf 不同,Compose 不会跟踪 staticCompositionLocalOf 的读取。更改该值会导致提供 CompositionLocal 的整个 content lambda 被重组,而不仅仅是在组合中读取 current 值的位置。


😵😵😵,很懵???我们来看下面的代码:

compositionLocalOf

class MainActivity : ComponentActivity() {
   

    private val themeBackground by mutableStateOf(Color.Blue)

    override fun onCreate(savedInstanceState: Bundle?) {
   
        super.onCreate(savedInstanceState)
        setContent {
   
            ComposeBlogTheme {
   
                CompositionLocalProvider(LocalBackground provides Color.Red) {
   
                    TextBackground()
                    CompositionLocalProvider(LocalBackground provides themeBackground) {
   
                        /**
                         * 2. themeBackground 变化后,仅此范围重组
                         * TextBackground()
                         */
                        CompositionLocalProvider(LocalBackground provides Color.Red) {
   
                            TextBackground()
                        }
                    }
                    TextBackground()
                }
            }
        }
    }
}

@Composable
fun TextBackground() {
   
    Surface(color = LocalBackground.current) {
   
        Text("Hi, Compose")
    }
}

// 1. Compose 会记录跟踪每一个调用 LocalBackground.current 的区域
val LocalBackground = compositionLocalOf {
    Color.Blue }

staticCompositionLocalOf

class MainActivity : ComponentActivity() {
   

    private val themeBackground by mutableStateOf(Color.Blue)

    override fun onCreate(savedInstanceState: Bundle?) {
   
        super.onCreate(savedInstanceState)
        setContent {
   
            ComposeBlogTheme {
   
                CompositionLocalProvider(LocalBackground provides Color.Red) {
   
                    TextBackground()
                    CompositionLocalProvider(LocalBackground provides themeBackground) {
   
                        /**
                         * 2. themeBackground 变化后,内部所有 content 全部重组
                         * TextBackground()
                         *
                         * CompositionLocalProvider(LocalBackground provides Color.Red) {
                         *    TextBackground()
                         * }
                         */
                    }
                    TextBackground()
                }
            }
        }
    }
}

@Composable
fun TextBackground() {
   
    Surface(color = LocalBackground.current) {
   
        Text("Hi, Compose")
    }
}

// Compose 不会记录跟踪每一个调用 LocalBackground.current 的区域
val LocalBackground = staticCompositionLocalOf {
    Color.Blue }

区别搞懂了吧?那么随之而来一个疑问:我们定义 CompositionLocal 该用哪个?

官方说明

如果为 CompositionLocal 提供的值发生更改的可能性微乎其微或永远不会更改,使用 staticCompositionLocalOf 可提高性能。


比如,我们看两个系统里面定义好的 CompositionLocal

// 上下文固定不变,用 staticCompositionLocalOf
val LocalContext = staticCompositionLocalOf<Context> {
   
    noLocalProvidedFor("LocalContext")
}

// 内部内容颜色常变,用 compositionLocalOf
val LocalContentColor = compositionLocalOf {
    Color.Black }

相关推荐

  1. 聊聊 Jetpack Compose 原理 -- 穿透刺客 CompositionLocal

    2023-12-12 05:04:01       45 阅读
  2. [Android]使用CompositionLocal隐式传值

    2023-12-12 05:04:01       28 阅读
  3. 【源码解析】从ReentrantLock角度聊聊AQS原理

    2023-12-12 05:04:01       48 阅读
  4. 蓝桥杯 算法训练 藏匿的刺客

    2023-12-12 05:04:01       29 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2023-12-12 05:04:01       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2023-12-12 05:04:01       106 阅读
  3. 在Django里面运行非项目文件

    2023-12-12 05:04:01       87 阅读
  4. Python语言-面向对象

    2023-12-12 05:04:01       96 阅读

热门阅读

  1. 前端知识(十二)———ES6迭代器

    2023-12-12 05:04:01       56 阅读
  2. 基于鲸鱼算法求解多目标优化问题附 MATLAB 代码

    2023-12-12 05:04:01       57 阅读
  3. windows 安装两个mysql

    2023-12-12 05:04:01       64 阅读
  4. 序列化之text_wiarchive和text_woarchive编程

    2023-12-12 05:04:01       50 阅读
  5. Rust的未来发展趋势和行业应用

    2023-12-12 05:04:01       64 阅读
  6. 国产芯片有哪些优势?

    2023-12-12 05:04:01       68 阅读
  7. 条款25:考虑写出一个不抛出异常的swap函数

    2023-12-12 05:04:01       53 阅读
  8. 转换 pytorch 格式模型为 caffe格式模型 pth2caffemodel

    2023-12-12 05:04:01       56 阅读