Data Binding
数据绑定(Data Binding)用于把运行时代码连接到 Rive 编辑器中已经绑定的元素。通过 View Model 和 View Model Instance,你可以在 Android 运行时中读取、写入和观察属性值,并把这些值绑定到状态机、图片、列表、Artboard、枚举等内容。
Rive Android 目前包含两套常见 API:
- Compose API:面向 Jetpack Compose,通常使用
rememberViewModelInstance、ViewModelSource、ViewModelInstanceSource和 KotlinFlow。 - Legacy API:面向
RiveAnimationView,通过view.controller.file、ViewModel、ViewModelInstance以及属性对象进行操作。
概览
数据绑定的核心概念包括:
- View Model:在 Rive 文件中定义的一组可绑定属性结构。
- View Model Instance:View Model 的具体实例,持有实际运行时数据。
- Binding:将 View Model Instance 绑定到 Artboard 或 State Machine,让动画可以响应实例中的数据。
- Properties:View Model Instance 中的具体字段,例如数字、字符串、布尔值、颜色、图片、列表、Artboard、嵌套实例等。
- Observability:在运行时监听属性变化,并在属性更新时同步 UI 或业务逻辑。
View Models
View Model 是在 Rive 编辑器中定义的数据结构。运行时可以根据名称、索引,或某个 Artboard 的默认 View Model 来获取它。
Compose
与其他运行时不同,在 Compose API 中,View Model 不作为单独对象存在。它由 ViewModelSource sealed class 表示,并作为创建 View Model Instance 的构建器模式的一部分。另一部分是创建实例,详见 View Model Instances。
// Named source
val vmSource = ViewModelSource.Named("My View Model")
// Default for artboard source
val vmSource = ViewModelSource.DefaultForArtboard(artboard)
Legacy
// `view` of type RiveAnimationView
view.setRiveResource(R.raw.my_rive_file)
val file = view.controller.file!!
// Get reference by name
val vm = file.getViewModelByName("My View Model")
// Get reference by index
for (i in 0 until file.viewModelCount) {
val indexedVM = file.getViewModelByIndex(i)
}
// Get reference to the default view model
val defaultVM = file.defaultViewModelForArtboard(view.controller.activeArtboard!!)
View Model Instances
View Model Instance 是 View Model 的具体数据实例。你可以创建空白实例、默认实例、按名称创建的实例,或引用嵌套实例。
Compose
请先参考 View Models 获取可用于下面示例的 ViewModelSource。有了它之后,可以使用构建器模式创建 ViewModelInstanceSource。随后将该 source 传给 rememberViewModelInstance,即可在 composition 生命周期内创建并记住该实例。
// From previous section
val vmSource = ViewModelSource.Named("My View Model")
// Blank instance source
val vmiSourceBlank = ViewModelInstanceSource.Blank(vmSource)
// or
val vmiSourceBlank = vmSource.blankInstance()
// Default instance source
val vmiSourceDefault = ViewModelInstanceSource.Default(vmSource)
// or
val vmiSourceDefault = vmSource.defaultInstance()
// Named instance source
val vmiSourceNamed = ViewModelInstanceSource.Named(vmSource, "My Instance")
// or
val vmiSourceNamed = vmSource.namedInstance("My Instance")
// The completed source can now be used along with the Rive file to create and remember the instance
val viewModelInstance = rememberViewModelInstance(riveFile, vmiSourceNamed)
此外,你也可以通过 Reference 变体,从父实例中引用嵌套 View Model Instance。
val myVMI = rememberViewModelInstance(riveFile, mySource)
val referenceSource = ViewModelInstanceSource.Reference(myVMI, "Path/To/Nested VMI")
val nestedVMI = rememberViewModelInstance(riveFile, referenceSource)
Legacy
val vm = view.controller.file?.getViewModelByName("My View Model")!!
// Create blank
val vmiBlank = vm.createBlankInstance()
// Create default
val vmiDefault = vm.createDefaultInstance()
// Create by index
for (i in 0 until vm.instanceCount) {
val vmiIndexed = vm.createInstanceFromIndex(i)
}
// Create by name
val vmiNamed = vm.createInstanceFromName("My Instance")
Binding
创建 View Model Instance 后,需要将它绑定到 State Machine 或 Artboard,动画才能读取其中的数据并对变化作出响应。
Compose
请参考 Compose data binding example。
当把 ViewModelInstance 传给 Rive composable 时,它会自动绑定到状态机。
val vmiSource = ViewModelSource.Named("My View Model").namedInstance("My Instance")
val vmi = rememberViewModelInstance(riveFile, vmiSource)
Rive(
riveFile,
viewModelInstance = vmi
)
Legacy
请参考 Legacy data binding example。
view.setRiveResource(
R.raw.my_rive_file,
artboardName = "My Artboard",
)
val vm = view.controller.file?.getViewModelByName("My View Model")!!
val vmi = vm.createInstanceFromName("My Instance")
// Apply the instance to the state machine (preferred)
view.controller.stateMachines.first().viewModelInstance = vmi
// Alternatively, apply the instance to the artboard
view.controller.activeArtboard?.viewModelInstance = vmi
Auto Binding
Auto-binding 会自动创建并绑定默认 Artboard、默认 View Model 和默认实例。它适合使用 Rive 文件中的默认绑定配置快速启动。
Compose
由于 composable 是函数,在 Compose API 中不存在 Legacy API 那样的 auto-binding。和类相比,从 composable 中取值较困难;如果使用回调,还需要在回调触发前记住一个 null 占位值,这会比直接提供实例产生更多额外开销。
等价做法是创建一个没有 source 的 View Model Instance。它会在内部创建默认 Artboard、该 Artboard 的默认 View Model,以及该 View Model 的默认实例。然后你可以把它传给 Rive composable。
val vmi = rememberViewModelInstance(riveFile)
Rive(
riveFile,
viewModelInstance = vmi,
)
Legacy
view.setRiveResource(
R.raw.my_rive_file,
autoBind = true,
)
Properties
属性是 View Model 中定义的具体数据字段。常见类型包括数字、字符串、布尔值、颜色、枚举、图片、列表、Artboard,以及嵌套 View Model Instance。
运行时可以列出属性、读取属性、写入属性,也可以观察属性变化。
Listing Properties
你可以列 出某个 View Model 中定义的所有属性,用于调试、校验或动态构建 UI。
Compose
获取 View Model 属性是挂起操作,因此需要在协程作用域中调用,例如 LaunchedEffect。
LaunchedEffect(riveFile) {
riveFile.getViewModelProperties("My View Model").forEach { property ->
Log.d("My Tag", "Property Name: ${property.name}, Type: ${property.type}")
}
}
Legacy
val vm = view.controller.file?.getViewModelByName("My View Model")!!
// A list of properties
val properties = vm.properties
assertContains(
properties,
ViewModel.Property(ViewModel.PropertyDataType.NUMBER, "My Number Property")
)
Reading and Writing Properties
读取和写入属性是数据绑定中最常见的操作。Compose API 通常直接通过路径设置属性,并通过 Flow 读取和观察属性;Legacy API 则通常先获取属性对象,然后读取或修改它的 value。
Compose
写入值
Compose API 没有显式的属性对象。属性值会直接通过 ViewModelInstance 上的方法设置,这些方法接收属性路径。
val vmi = rememberViewModelInstance(...)
vmi.setNumberProperty("Path/To/Property", 10f)
读取值
值会通过 Kotlin Flow 读取。每当属性值发生变化时,该 Flow 都会发出最新值。你可以在 LaunchedEffect 中 collect 这个 Flow,也可以使用 collectAsState() 将其转换为 State(或使用 collectAsStateWithLifecycle() 只在特定生命周期状态下 collect)。
如果只想获取一次最新值而不持续观察,可以使用终端操作符 first()。
val vmi = rememberViewModelInstance(...)
// Collect as State
val numberValue by vmi.numberPropertyFlow("Path/To/Property").collectAsState(initial = 0f)
Text(text = "Number value: $numberValue")
// Or collect
LaunchedEffect(vmi) {
vmi.numberPropertyFlow("Path/To/Property").collect { value ->
Log.d("Rive", "Number value changed: $value")
}
// Or get once
val numberValue = vmi.numberPropertyFlow("Path/To/Property").first()
Log.d("Rive", "Current number value: $numberValue")
}
Legacy
val vm = view.controller.file?.getViewModelByName("My View Model")!!
val vmi = vm.createInstanceFromName("My Instance")
val numberProperty = vmi.getNumberProperty("My Number Property")
// Get
val numberValue = numberProperty.value
// Set
numberProperty.value = 10f
Nested Property Paths
属性可以位于嵌套 View Model Instance 中。你可以逐级获取嵌套实例,也可以使用 / 分隔的路径直接访问嵌套属性。
Compose
val parent = rememberViewModelInstance(riveFile, ViewModelSource.Named("Parent VM").namedInstance("Parent"))
// Using references
val child = rememberViewModelInstance(riveFile, ViewModelInstanceSource.Reference(parent, "Child"))
val nestedNumber = child.numberPropertyFlow("My Nested Number").collectAsState(0f)
// Or using paths
val nestedNumber = parent.numberPropertyFlow("Child/My Nested Number").collectAsState(0f)