在 A2UI 架構中,每個介面都是由元件目錄驅動。 目錄會向 AI 代理程式宣告可用的元件、屬性結構定義和功能,而不是讓 AI 代理程式自行發明 UI 基本元素或產生任意程式碼。然後,代理程式會使用這些元件建構使用者介面。
為應用程式的設計系統建構自訂目錄時,您會實作元件,將這些目錄定義對應至具體的 Jetpack Compose UI 元素。每個 A2UI 元件 (A2uiComponent) 都會定義自己的屬性結構定義合約、在動態資料抵達時評估就緒狀態、繫結資料模型中的反應式屬性、發出 Compose UI,以及將使用者互動動作傳回給代理程式。
Compose UI 算繪器 (androidx.a2ui.compose:compose-ui) 提供實作自訂元件所需的介面和接收器範圍,這些元件會遵循應用程式的設計系統。
宣告靜態型別元件屬性
在算繪之前,請先宣告元件預期從代理程式取得的屬性。執行階段層提供靜態型別的 A2uiProperty API,用於產生 JSON 結構定義,以及在執行階段擷取值:
// Define static properties, dynamic bindings, and component references
val textProp = A2uiProperty.dynamicString("text", required = true)
val variantProp = A2uiProperty.stringEnum("variant", enumValues = listOf("body", "title"))
val childProp = A2uiProperty.componentId("child", required = true)
val actionProp = A2uiProperty.action("action", required = true)
實作 A2uiComponent 介面
導入 A2uiComponent 介面,定義元件的結構定義,並將從代理收到的屬性對應至 Compose UI:
object CustomTextComponent : A2uiComponent {
private val textProp = A2uiProperty.dynamicString("text", required = true)
private val variantProp = A2uiProperty.stringEnum(
"variant",
enumValues = listOf("body", "title"),
)
override val name = "Text"
override val description = "Displays dynamic text."
override val properties = listOf(textProp, variantProp)
@Composable
override fun A2uiComponentScope.isReady(properties: A2uiComponentProperties): Boolean {
// The component does not become ready until dynamic text data arrives
return properties.bind(textProp) != null
}
@Composable
override fun A2uiComponentScope.Content(
properties: A2uiComponentProperties,
modifier: Modifier,
) {
// Reactively resolve dynamic data binding and subscribe to updates
val text = properties.bind(textProp) ?: ""
// Read the static configuration property
val variant = properties[variantProp] ?: "body"
val textStyle = if (variant == "title") {
MaterialTheme.typography.titleLarge
} else {
MaterialTheme.typography.bodyLarge
}
Text(
text = text,
style = textStyle,
modifier = modifier,
)
}
}
解決一般和雙向資料模型繫結
元件實作項目會使用 A2uiComponentScope 解析動態繫結的屬性。如果是規則動態屬性,bind 會傳回目前的值,並自動訂閱資料模型更新。
如果是互動式輸入元件,bindUpdater 會傳回穩定的更新程式 Lambda。
如果代理程式提供的是字串常值,而非可寫入的資料路徑,更新程式 Lambda 會是 null,表示該欄位為唯讀:
val labelProp = A2uiProperty.dynamicString("label", required = true)
val valueProp = A2uiProperty.dynamicBoolean("value")
@Composable
fun A2uiComponentScope.CustomCheckbox(properties: A2uiComponentProperties) {
// Read a dynamic property from the data model subscribing to updates
val label = properties.bind(labelProp) ?: ""
// Bind a property value and its updater to handle two-way data binding
val checked = properties.bind(valueProp) ?: false
val onCheckedChange = properties.bindUpdater(valueProp)
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(
checked = checked,
onCheckedChange = onCheckedChange,
enabled = (onCheckedChange != null), // Read-only if no writable path was bound
)
Text(text = label)
}
}
將使用者動作傳送給代理程式
互動式元件會使用 A2uiComponentScope.dispatchAction 將使用者事件傳送回代理程式:
object CustomButtonComponent : A2uiComponent {
private val childProp = A2uiProperty.componentId("child", required = true)
private val actionProp = A2uiProperty.action("action", required = true)
override val name = "Button"
override val description = "A clickable button."
override val properties = listOf(childProp, actionProp)
@Composable
override fun A2uiComponentScope.Content(
properties: A2uiComponentProperties,
modifier: Modifier,
) {
val actionDefinition = properties[actionProp]
val childId = properties[childProp] ?: return
val currentAction by rememberUpdatedState(actionDefinition)
val onClick: () -> Unit = remember {
{ currentAction?.let { dispatchAction(it) } }
}
Button(onClick = onClick, modifier = modifier) {
val childState = observeA2uiComponentState(id = childId)
when (childState) {
is A2uiComponentState.Loading -> CircularProgressIndicator()
is A2uiComponentState.Error -> Text("Error")
is A2uiComponentState.Success -> A2uiComponent(childState.component)
}
}
}
}
處理子項元件和漸進式算繪
支援巢狀子項的元件會使用 observeA2uiComponentState(id) 觀察子項狀態。這樣一來,父項容器就能在子項元件獨立載入時,逐步算繪外殼:
val headerChildProp = A2uiProperty.componentId("headerId", required = true)
@Composable
fun A2uiComponentScope.CustomCompositeContent(
properties: A2uiComponentProperties,
) {
val headerId = properties[headerChildProp] ?: return
val headerState = observeA2uiComponentState(id = headerId)
when (headerState) {
is A2uiComponentState.Loading -> {
// Render a localized loading placeholder
LinearProgressIndicator()
}
is A2uiComponentState.Error -> {
// Render a localized error fallback
Text("Failed to load header")
}
is A2uiComponentState.Success -> {
// Forward the resolved child component to the visual UI router
A2uiComponent(headerState.component)
}
}
}
如要處理子項集合或清單 (例如欄、列或清單中的項目),請使用 A2uiProperty.childList 宣告屬性,並使用 bindChildReferences 解析子項:
val childrenProp = A2uiProperty.childList("children", required = true)
@Composable
fun A2uiComponentScope.CustomColumn(
properties: A2uiComponentProperties,
modifier: Modifier = Modifier,
) {
// Resolve child references (supports both static ID arrays and dynamic data templates)
val childReferences = properties.bindChildReferences(childrenProp) ?: return
Column(modifier = modifier) {
childReferences.forEach { reference ->
key(reference.id, reference.baseDataPath) {
val childState = observeA2uiComponentState(reference)
when (childState) {
is A2uiComponentState.Loading -> CircularProgressIndicator()
is A2uiComponentState.Error -> Text("Failed to load child")
is A2uiComponentState.Success -> A2uiComponent(childState.component)
}
}
}
}
}
在 Basic Catalog 中整合原生媒體算繪功能
使用提供的 Basic Catalog 實作 (androidx.compose.material3:material3-a2ui) 時,您可以將偏好的媒體程式庫 (例如圖片的 Coil 或影片的 ExoPlayer) 插入 Basic Catalog 的媒體元件:
// Configure an Image component for the Basic Catalog using Coil
val coilImage = MaterialA2uiBasicCatalogV1Defaults.image { url, desc, scale, modifier, onError ->
AsyncImage(
model = url,
contentDescription = desc,
contentScale = scale,
modifier = modifier,
onError = { state -> onError(state.result.throwable) },
)
}
實作詳細資料
以下各節說明遞迴 UI 傳輸、動態屬性評估和錯誤報告。
元件實作使用者歷程會介紹下列重要 API:
A2uiComponent:定義元件中繼資料、屬性結構定義、準備檢查 (isReady) 和算繪發射 (Content) 的介面。A2uiProperty:用於產生 JSON 結構定義和解析執行階段值的靜態型別屬性宣告。A2uiComponentScope:接收器範圍,可為元件實作項目提供情境功能 (例如資料繫結、動作調度,以及子項狀態觀察)。A2uiComponentProperties:元件屬性的容器,可從提供型別安全屬性存取的代理程式接收屬性。A2uiComponentState:代表元件的反應式載入、成功或錯誤解決狀態。
遞迴 UI 傳送和動態路徑
呼叫端提升的根狀態 (或在父項中解析的子項元件狀態) 會透過 A2uiComponent 可組合函式啟動遞迴元件算繪。這個函式會做為動態路由器,而非將已解析的狀態緊密連結至特定 UI 實作項目。