CompositionLocal
這項工具
並透過 Composition 隱密傳遞資料。在這個頁面中
進一步瞭解 CompositionLocal
,以及如何建立自己的
CompositionLocal
,並瞭解 CompositionLocal
是否適合
所需用途
隆重推出 CompositionLocal
通常在 Compose 中,資料會向下流動,透過 UI 樹狀結構,做為每個可組合函式的參數。如此一來 明確依附依附元件然而,對處理新資料時 例如顏色或類型樣式請參閱以下範例:
@Composable fun MyApp() { // Theme information tends to be defined near the root of the application val colors = colors() } // Some composable deep in the hierarchy @Composable fun SomeTextLabel(labelText: String) { Text( text = labelText, color = colors.onPrimary // ← need to access colors here ) }
如要支援不需將顏色做為明確參數依附元件傳遞給
大部分的可組合函式,Compose 提供 CompositionLocal
,
建立以樹狀結構為範圍的命名物件,這類物件能做為隱式的方法
資料流經 UI 樹狀結構。
系統通常會在特定節點中提供 CompositionLocal
元素的值
UI 樹狀結構的新增欄位該值可供其可組合函式子系使用,而無需
在可組合函式中將 CompositionLocal
宣告為參數。
CompositionLocal
是 Material 主題在內部使用的方式。
MaterialTheme
是
這個物件提供三個 CompositionLocal
例項:colorScheme
、
typography
和 shapes
,可讓您稍後在任何子系中擷取這些內容
組成部分
具體來說,這些是 LocalColorScheme
、LocalShapes
和
您可以透過 MaterialTheme
存取的 LocalTypography
資源
colorScheme
、shapes
和 typography
屬性。
@Composable fun MyApp() { // Provides a Theme whose values are propagated down its `content` MaterialTheme { // New values for colorScheme, typography, and shapes are available // in MaterialTheme's content lambda. // ... content here ... } } // Some composable deep in the hierarchy of MaterialTheme @Composable fun SomeTextLabel(labelText: String) { Text( text = labelText, // `primary` is obtained from MaterialTheme's // LocalColors CompositionLocal color = MaterialTheme.colorScheme.primary ) }
CompositionLocal
執行個體的範圍限定為 Composition 的一部分,因此
可以在樹狀結構的不同層級提供不同的值。current
值
CompositionLocal
會對應到
祖系。
如要為 CompositionLocal
提供新的值,請使用
CompositionLocalProvider
圖示
和其provides
修正函式,該函式可將 CompositionLocal
金鑰與 value
建立關聯。
CompositionLocalProvider
的 content
lambda 會取得提供的
值 (用於存取 CompositionLocal
的 current
屬性) 時。如果
Compose 提供新值後,Compose 會重新編寫讀取的 Composition 部分
CompositionLocal
。
舉例來說,LocalContentColor
CompositionLocal
包含偏好的文字顏色,用於文字和
確保其與目前的背景顏色形成對比。在
以下範例:CompositionLocalProvider
是用來提供不同的
組合中不同部分的值
@Composable fun CompositionLocalExample() { MaterialTheme { // Surface provides contentColorFor(MaterialTheme.colorScheme.surface) by default // This is to automatically make text and other content contrast to the background // correctly. Surface { Column { Text("Uses Surface's provided content color") CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.primary) { Text("Primary color provided by LocalContentColor") Text("This Text also uses primary as textColor") CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.error) { DescendantExample() } } } } } } @Composable fun DescendantExample() { // CompositionLocalProviders also work across composable functions Text("This Text uses the error color now") }
圖 1. CompositionLocalExample
可組合項的預覽畫面。
在最後一個範例中,CompositionLocal
執行個體是在內部使用
透過 Material Design 可組合項如要存取 CompositionLocal
目前的值,
使用 current
資源。在以下範例中,LocalContext
目前的 Context
值
Android 應用程式常用的 CompositionLocal
用於設定格式
例如:
@Composable fun FruitText(fruitSize: Int) { // Get `resources` from the current value of LocalContext val resources = LocalContext.current.resources val fruitText = remember(resources, fruitSize) { resources.getQuantityString(R.plurals.fruit_title, fruitSize) } Text(text = fruitText) }
建立自己的 CompositionLocal
CompositionLocal
是可透過組合向下傳遞資料的工具
隱含。
使用 CompositionLocal
的另一個關鍵信號是
跨切割和中階實作層不應瞭解
存在,因為讓這些中間層感知會限制
可組合項的公用程式。例如,若要查詢 Android 權限,
當時由 CompositionLocal
提供。媒體選擇器可組合項
可以在 Google Cloud 控制台中新增功能
而無需變更其 API,且需要媒體選擇器的呼叫端才能
但請注意環境中使用的額外情境
不過,CompositionLocal
不一定是最合適的解決方案。三
不建議過度使用 CompositionLocal
,因為其有一些缺點:
CompositionLocal
會導致可組合項的行為難以理解。阿斯
會建立隱含的依附元件,也就是使用這些依附元件的可組合項呼叫端
可確保滿足每個 CompositionLocal
的值。
此外,這個依附元件可能沒有明確的可靠資料來源
可以變更組合的任何部分。因此,對應用程式偵錯
這可能比較困難
組合用於查看提供 current
值的位置。提供工具,例如 Finder
IDE 或 Compose 版面配置檢查器的用法提供了充分資訊,
有助於解決這個問題
決定是否要使用「CompositionLocal
」
在某些情況下,「CompositionLocal
」非常適合用來解決問題
適合所需用途
CompositionLocal
應該有正確的預設值。如果沒有預設值
因此必須確保開發人員
會發生在沒有提供 CompositionLocal
值的情況下。
如未提供預設值,可能會在建立時引發問題和困擾
以測試或預覽使用該 CompositionLocal
的可組合函式
這需要明確提供
避免使用 CompositionLocal
表示不屬於樹狀結構範圍或
以子階層為範圍。CompositionLocal
視情況而定
可能只用於任何子系,而非只有少數。
如果您的用途不符合這些規定,請參閱
請先參考替代方案一節,再建立
CompositionLocal
。
建立 CompositionLocal
來保留
特定畫面的 ViewModel
,讓該畫面中的所有可組合項
取得 ViewModel
的參照以執行部分邏輯。此為不當做法
因為不是特定 UI 樹狀結構下的所有可組合項都需要瞭解
ViewModel
。建議您只將資訊傳送至可組合項
需要遵循狀態向下流及事件上移的模式。這個方法會讓可組合函式變得更加
可重複使用且更容易測試
建立 CompositionLocal
有兩個 API 可用來建立CompositionLocal
:
compositionLocalOf
: 變更在重組期間提供的值,只會失效 也就是讀取其current
值。staticCompositionLocalOf
: 與compositionLocalOf
不同,staticCompositionLocalOf
的讀取並非 此功能。變更此值會導致整個content
lambda ,提供CompositionLocal
進行重組,而不是 而是在組成中讀取current
值的位置。
如果提供給 CompositionLocal
的值不太可能變更,
一律保持不變,使用 staticCompositionLocalOf
來獲取效能優勢。
舉例來說,應用程式的設計系統可能是以可組合項做為主軸
透過 UI 元件的陰影提升。由於
應用程式的高度應在整個 UI 樹狀結構中傳播,因此我們使用
CompositionLocal
。由於 CompositionLocal
值有條件衍生
根據系統主題,我們使用 compositionLocalOf
API:
// LocalElevations.kt file data class Elevations(val card: Dp = 0.dp, val default: Dp = 0.dp) // Define a CompositionLocal global object with a default // This instance can be accessed by all composables in the app val LocalElevations = compositionLocalOf { Elevations() }
為 CompositionLocal
提供值
CompositionLocalProvider
可組合元件會根據指定的目標,將值繫結至 CompositionLocal
執行個體
階層如要為 CompositionLocal
提供新的值,請使用
provides
修正可將 CompositionLocal
金鑰與 value
建立關聯的函式,如下所示:
// MyActivity.kt file class MyActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { // Calculate elevations based on the system theme val elevations = if (isSystemInDarkTheme()) { Elevations(card = 1.dp, default = 1.dp) } else { Elevations(card = 0.dp, default = 0.dp) } // Bind elevation as the value for LocalElevations CompositionLocalProvider(LocalElevations provides elevations) { // ... Content goes here ... // This part of Composition will see the `elevations` instance // when accessing LocalElevations.current } } } }
使用 CompositionLocal
CompositionLocal.current
會傳回最接近的 CompositionLocalProvider
所提供的值 (為該 CompositionLocal
提供值):
@Composable fun SomeComposable() { // Access the globally defined LocalElevations variable to get the // current Elevations in this part of the Composition MyCard(elevation = LocalElevations.current.card) { // Content } }
可以考慮改用的替代方案
對某些用例而言,CompositionLocal
是極端的解決方案。如果您的
廣告的用途不符合判斷是否要使用
CompositionLocal 區段,另一個解決方案可能更適合
視用途而定
傳送明確的參數
明確表明可組合項的依附元件是個好習慣。建議做法 您「只」對可組合項傳遞所需項目。為了促進分離 和重複使用可組合項目,每個可組合項應盡可能減少 資訊
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... MyDescendant(myViewModel.data) } // Don't pass the whole object! Just what the descendant needs. // Also, don't pass the ViewModel as an implicit dependency using // a CompositionLocal. @Composable fun MyDescendant(myViewModel: MyViewModel) { /* ... */ } // Pass only what the descendant needs @Composable fun MyDescendant(data: DataToDisplay) { // Display data }
控制反轉
避免將不必要的依附元件傳遞至可組合項的另一個方法 控制反轉。而非子系將依附元件 會改為執行某些邏輯
請參閱下例,子系必須觸發要求, 載入一些資料:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... MyDescendant(myViewModel) } @Composable fun MyDescendant(myViewModel: MyViewModel) { Button(onClick = { myViewModel.loadData() }) { Text("Load data") } }
視情況而定,MyDescendant
的職責可能不只一個。另外,
將 MyViewModel
做為依附元件傳遞會使 MyDescendant
更容易重複使用,因為
這些元素現在已結合建議您改用不會傳遞
對子系的依賴並使用反轉的控制原則
讓祖系負責執行邏輯:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusableLoadDataButton( onLoadClick = { myViewModel.loadData() } ) } @Composable fun ReusableLoadDataButton(onLoadClick: () -> Unit) { Button(onClick = onLoadClick) { Text("Load data") } }
這個方法更適合某些用途,因為將 來自其直接祖系的子項。祖係可組合項通常會變得更加 以便處理較低層級的彈性可組合函式
同樣地,您可以透過相同方式使用 @Composable
內容 lambda
相同的優點:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusablePartOfTheScreen( content = { Button( onClick = { myViewModel.loadData() } ) { Text("Confirm") } } ) } @Composable fun ReusablePartOfTheScreen(content: @Composable () -> Unit) { Column { // ... content() } }
為您推薦
- 注意:系統會在 JavaScript 關閉時顯示連結文字
- Compose 中的主題剖析
- 在 Compose 中使用 View
- 適用於 Jetpack Compose 的 Kotlin