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. 獲取 nameText(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 提供給了 LocalColorsCompositionLocalProvider(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@ReadOnlyComposableget() = LocalColors.currentval typography: Typography@Composable@ReadOnlyComposableget() = LocalTypography.currentval shapes: Shapes@Composable@ReadOnlyComposableget() = 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 }