数据绑定
在接触运行时数据绑定 API 之前,重要的是先熟悉概述中介绍的核心概念。
视图模型
视图模型描述一组属性,但本身不能用于获取或设置值——这是视图模型实例的职责。
首先,我们需要获取对特定视图模型的引用。这可以通过索引、名称或给定画板的默认方式来完成,从 Rive 文件中获取。默认选项指的是编辑器中画板上通过下拉菜单分配的视图模型。
新版运行时(推荐)
import { useRiveFile } from '@rive-app/react-native';
const { riveFile } = useRiveFile(require('./my_file.riv'));
// 按名称获取引用
const namedVM = await riveFile?.viewModelByNameAsync('My View Model');
// 获取所有视图模型名称
const vmNames = await riveFile?.getViewModelNamesAsync(); // ['My View Model', ...]
// 获取默认画板视图模型的引用
const defaultVM = await riveFile?.defaultArtboardViewModelAsync();
旧版运行时
创建视图模型对象仅在新版 React Native 运行时中受支持。
视图模型实例
获取视图模型的引用后,可以使用它来创建实例。创建实例时有四种选择:
- 创建空白实例 — 使用 以下默认值填充所创建实例的属性:
| 类型 | 值 |
|---|---|
| Number | 0 |
| String | 空字符串 |
| Boolean | False |
| Color | 0xFF000000 |
| Trigger | 未触发 |
| Enum | 第一个值 |
| Image | 无图像 |
| Artboard | 无画板 |
| List | 空列表 |
| Nested view model | Null |
- 创建默认实例 — 使用编辑器中标记为 "Default" 的实例。通常这是设计师期望在运行时使用的主要实例。
- 按索引创建 — 使用遍历所有可用实例时的顺序。在通过迭代创建多个实例时很有用。
- 按名称创建 — 使用编辑器中的实例名称。在创建特定实例时很有用。
在一些示例中,由于 "view model instance" 过于冗长,我们使用缩写 "VMI",以及用 "VM" 表示 "view model"。
新版运行时(推荐)
使用 useViewModelInstance hook 创建视图模型实例。您可以传入 RiveFile、ViewModel 或 RiveViewRef 作为来源。
import { useRiveFile, useViewModelInstance, RiveView } from '@rive-app/react-native';
const { riveFile } = useRiveFile(require('./my_file.riv'));
// 从 RiveFile — 默认画板的 ViewModel,默认实例
const { instance } = useViewModelInstance(riveFile);
// 指定画板或 ViewModel 名称(互斥)
const { instance } = useViewModelInstance(riveFile, { artboardName: 'MainArtboard' });
const { instance } = useViewModelInstance(riveFile, { viewModelName: 'Settings' });
// instanceName 可与上述任意选项组合以选择特定实例
const { instance } = useViewModelInstance(riveFile, { instanceName: 'PersonInstance' });
const { instance } = useViewModelInstance(riveFile, { viewModelName: 'Settings', instanceName: 'UserSettings' });
// 从 ViewModel 对象
const viewModel = riveFile?.viewModelByName('My View Model');
const { instance: namedInstance } = useViewModelInstance(viewModel, { name: 'My Instance' });
const { instance: newInstance } = useViewModelInstance(viewModel, { useNew: true });
// 使用 required: true(如果为 null 则抛出异常,需配合 Error Boundary 使用)
const { instance } = useViewModelInstance(riveFile, { required: true });
// 使用 onInit 同步设置初始值
const { instance } = useViewModelInstance(riveFile, {
onInit: (vmi) => {
vmi.numberProperty('health')?.set(100);
},
});
return (
<RiveView file={riveFile} dataBind={instance} />
);
您还可以从 RiveViewRef 获 取自动绑定的实例:
import { useRive, useViewModelInstance } from '@rive-app/react-native';
const { riveViewRef, setHybridRef } = useRive();
const { instance } = useViewModelInstance(riveViewRef);
旧版运行时
您可以通过向 Rive 组件传入 dataBinding 属性来将视图模型实例绑定到 Rive 组件。
dataBinding 属性接受 DataBindBy 类型,可以是以下之一:
export type DataBindBy =
| { type: 'autobind'; value: boolean }
| { type: 'index'; value: number }
| { type: 'name'; value: string }
| { type: 'empty' };
export const AutoBind = (value: boolean): DataBindBy => ({
type: 'autobind',
value,
});
export const BindByIndex = (value: number): DataBindBy => ({
type: 'index',
value,
});
export const BindByName = (value: string): DataBindBy => ({
type: 'name',
value,
});
export const BindEmpty = (): DataBindBy => ({ type: 'empty' });
示例用法:
const [setRiveRef, riveRef] = useRive();
return (
<Rive
ref={setRiveRef}
autoplay={true}
dataBinding={AutoBind(true)} // 默认: `AutoBind(false)`
// dataBinding={BindByIndex(0)}
// dataBinding={BindByName('SomeName')}
// dataBinding={BindEmpty()}
stateMachineName={'State Machine 1'}
resourceName={'rewards'}
/>
);
您可以通过向 Rive 组件传入 onError={(riveError: RNRiveError) 属性来监听错误。
riveError 对象包含错误类型和消息,您可以过滤出 RNRiveErrorType.DataBindingError:
onError={(riveError: RNRiveError) => {
switch (riveError.type) {
case RNRiveErrorType.DataBindingError: {
console.error(`${riveError.message}`);
return;
}
default:
console.error('Unhandled error');
return;
}
}}
绑定
创建的实例可以分配给状态机或画板。这会建立在编辑时设置的绑定关系。
建议分配给状态机,因为这会自动将实例也应用到画板上。仅在不使用状态机时(即文件是静态的或使用线性动画时)才分配给画板。
实例的初始值在状态机或画板推进之前不会应用到其绑定的元素上。
新版运行时(推荐)
对于 React Native,不需要额外的步骤来将视图模型实例绑定到 Rive 组件。将实例传递给 RiveView 上的 dataBind 属性:
import { RiveView, useRiveFile, useViewModelInstance } from '@rive-app/react-native';
const { riveFile } = useRiveFile(require('./my_file.riv'));
const { instance } = useViewModelInstance(riveFile);
return (
<RiveView
file={riveFile}
dataBind={instance}
/>
);
旧版运行时
对于 React Native,不需要额外的步骤来将视图模型实例绑定到 Rive 组件。dataBinding 属性会自动处理。
自动绑定
或者,您可能更喜欢使用自动绑定。这将使用默认实例自动将画板的默认视图模型绑定到状态机和画板。默认视图模型是编辑器中画板下拉菜单选择的那个。默认实例是编辑器中标记为 "Default" 的那个。
新版运行时(推荐)
自动绑定通过 DataBindMode 枚举可用。您可以将 DataBindMode.Auto 传递给 dataBind 属性:
import { RiveView, useRiveFile, DataBindMode } from '@rive-app/react-native';
const { riveFile } = useRiveFile(require('./my_file.riv'));
return (
<RiveView
file={riveFile}
dataBind={DataBindMode.Auto}
autoPlay={true}
/>
);
您也可以按名称绑定:
<RiveView
file={riveFile}
dataBind={{ byName: 'My Instance' }}
autoPlay={true}
/>
或绑定特定实例:
const { instance } = useViewModelInstance(riveFile);
<RiveView
file={riveFile}
dataBind={instance}
autoPlay={true}
/>
旧版运行时
dataBinding 属性的默认值是 AutoBind(false),这意味着自动绑定默认禁用。
要启用自动绑定,将 dataBinding 属性设置为 AutoBind(true):
const [setRiveRef, riveRef] = useRive();
return (
<Rive
ref={setRiveRef}
autoplay={true}
dataBinding={AutoBind(true)} // 默认: `AutoBind(false)`
stateMachineName={'State Machine 1'}
resourceName={'rewards'}
/>
);
属性
属性是可以在视图模型实例上读取、设置或观察的值。属性可以是以下类型:
| 类型 | 支持 |
|---|---|
| 浮点数 | ✅ |
| 布尔值 | ✅ |
| 触发器 | ✅ |
| 字符串 | ✅ |
| 枚举 | ✅ |
| 颜色 | ✅ |
| 嵌套视图模型 | ✅ |
| 列表 | ✅ |
| 图像 | ✅ |
| 画板 | ✅ |
有关版本兼容性的更多信息,请参见功能支持页面。
列出属性
可以在视图模型上检查属性描述符,以在运行时发现哪些可用。但这些不是可变属性本身——那些再次是在实例上的。这些描述符具有类型和名称。
新版运行时(推荐)
即将推出
旧版运行时
⚠️ 属性 API 在旧版运行时上不受支持。
读取和写入属性
可以通过名称或路径检索对这些属性 的引用。
某些属性是可变的,具有 getter、setter 和 observer 操作用于其值。获取或观察值将获取自上次状态机或画板推进以来设置在该属性绑定上的最新值。设置值将更新该值及其所有绑定的元素。
设置属性值后,更改在状态机或画板推进之前不会应用到其绑定的元素上。
新版运行时(推荐)
使用针对每种属性类型的特定 hooks 来获取和设置属性值:
useRiveBoolean:读/写布尔属性useRiveString:读/写字符串属性useRiveNumber:读/写数字属性useRiveColor:读/写颜色属性,支持十六进制字符串或 RGBAuseRiveEnum:读/写枚举属性useRiveTrigger:触发触发器事件,支持可选回调
这些 hooks 返回当前 value、一个 setter 函数(setValue 或 trigger),以及如果属性未找到则返回 error。
setValue函数允许您传入一个接收前一个值的函数,类似于 React 的setState模式。这在需要基于当前状态更新值时很有用:setValue((v) => (v ?? 0) + 5)
import {
useRiveFile,
useViewModelInstance,
useRiveBoolean,
useRiveString,
useRiveNumber,
useRiveColor,
useRiveEnum,
useRiveTrigger,
RiveView
} from '@rive-app/react-native';
const { riveFile } = useRiveFile(require('./my_file.riv'));
const { instance } = useViewModelInstance(riveFile);
// 布尔值
const { value: isActive, setValue: setIsActive, error: boolError } = useRiveBoolean(
'isToggleOn',
instance
);
// 设置: setIsActive(true);
// 字符串
const { value: userName, setValue: setUserName, error: stringError } = useRiveString(
'user/name',
instance
);
// 设置: setUserName('Rive');
// 数字
const { value: score, setValue: setScore, error: numberError } = useRiveNumber(
'levelScore',
instance
);
// 设置: setScore(100);
// 颜色(接受十六进制字符串或 RiveColor 对象)
const { value: themeColor, setValue: setThemeColor, error: colorError } = useRiveColor(
'ui/themeColor',
instance
);
// 设置: setThemeColor('#FF0000FF'); // 十六进制字符串
// 或: setThemeColor({ r: 255, g: 0, b: 0, a: 255 }); // RGBA 对象
// 枚举
const { value: status, setValue: setStatus, error: enumError } = useRiveEnum(
'appStatus',
instance
);
// 设置: setStatus('loading');
// 触发器(无值,仅触发函数)
const { trigger: playEffect, error: triggerError } = useRiveTrigger(
'playButtonEffect',
instance,
{
// 可选回调,在触发器被触发时调用
onTrigger: () => {
console.log('Trigger Fired!');
}
}
);
// 触发: playEffect();
return (
<RiveView
file={riveFile}
dataBind={instance}
autoPlay={true}
/>
);
每个 hook 返回的 value 会在 Rive 图形中属性变化时自动更新。
旧版运行时
以下数据绑定方法暴露在 RiveRef 对象上:
setBoolean: (path: string, value: boolean) => void;
setString: (path: string, value: string) => void;
setNumber: (path: string, value: number) => void;
setColor: (path: string, color: RiveRGBA | string) => void;
setEnum: (path: string, value: string) => void;
trigger: (path: string) => void;
颜色属性可以使用
RiveRGBA对象或十六进制字符串设置。十六进制字符串格式应为#RRGGBBAA,其中RR、GG、BB和AA是两位十六进制值,分别表示红、绿、蓝和 alpha 通道。type RiveRGBA = { r: number; g: number; b: number; a: number };
示例用法:
const [setRiveRef, riveRef] = useRive();
const setBoolean = () => {
if (riveRef) {
riveRef.setBoolean('My Boolean Property', true);
}
};
const setString = () => {
if (riveRef) {
riveRef.current.setString('My String Property', 'Hello, Rive');
}
};
const setNumber = () => {
if (riveRef) {
riveRef.current.setNumber('My Number Property', 10);
}
};
const setColor = () => {
if (riveRef) {
riveRef.setColor('My Color Property', { r: 255, g: 0, b: 0, a: 1 });
// 或
riveRef.setColor('My Color Property', '#00FF00FF');
}
};
const setEnum = () => {
if (riveRef) {
riveRef.setEnum('My Enum Property', 'Option 1');
}
};
const trigger = () => {
if (riveRef) {
riveRef.trigger('My Trigger Property');
}
};
嵌套属性路径
视图模型可以具有类型为视图模型的属性,允许任意嵌套。您可以在每个实例上链式调用属性,从根开始直到到达目标属性。或者,您可以通过路径参数来做到这一点,路径参数类似于 URI,是由正斜杠分隔的属性名称列表,以目标属性名称结尾。
新版运行时(推荐)
通过将完整路径(以 / 分隔)作为属性 hooks 的第一个参数来访问嵌套属性。
import { useRiveString, useRiveNumber, useRiveFile, useViewModelInstance } from '@rive-app/react-native';
const { riveFile } = useRiveFile(require('./my_file.riv'));
const { instance } = useViewModelInstance(riveFile);
// 访问 'settings/theme/name'(字符串)
const { value: themeName, setValue: setThemeName } = useRiveString(
'settings/theme/name',
instance
);
// 访问 'settings/volume'(数字)
const { value: volume, setValue: setVolume } = useRiveNumber(
'settings/volume',
instance
);
console.log('Current theme:', themeName);
// setThemeName('Dark Mode');
// setVolume(80);
Rive React Native 运行时尚未支持直接访问视图模型属性以使用链式表示法。
旧版运行时
⚠️ Rive React Native 运行时不支持使用链式表示法访问嵌套属性。但您可以使用路径表示法访问嵌套属性。
const [setRiveRef, riveRef] = useRive();
const nestedNumberByPath = useRiveNumber(riveRef, 'My Nested View Model/My Second Nested VM/My Nested Number');
useEffect(() => {
if (nestedNumberByPath) {
nestedNumberByPath.setValue(10);
}
}, [nestedNumberByPath]);
可观察性
您可以通过使用监听器或平台等效方法来观察属性值随时间的变化。一旦观察,当状态机推进导致属性变化时您会收到通知——无论是显式设置的新值,还是绑定导致的值更新。
新版运行时(推荐)
值通过 hooks 自动观察。当属性值在 Rive 实例中发生变化时(无论是因为您通过 hook 设置了它,还是由于内部绑定),相应 hook 返回的 value 会更新。此状态更改会触发 React 组件重新渲染,允许您响应新值。
对于触发器,您可以向 useRiveTrigger hook 直接提供 onTrigger 回调,该回调在 Rive 实例中激活 触发器时触发。
import {
useRiveFile,
useViewModelInstance,
useRiveBoolean,
useRiveString,
useRiveNumber,
useRiveColor,
useRiveEnum,
useRiveTrigger,
useEffect
} from '@rive-app/react-native';
const { riveFile } = useRiveFile(require('./my_file.riv'));
const { instance } = useViewModelInstance(riveFile);
const { value: boolValue, setValue: setBoolValue } = useRiveBoolean('My Boolean Property', instance);
const { value: stringValue, setValue: setStringValue } = useRiveString('My String Property', instance);
const { value: numberValue, setValue: setNumberValue } = useRiveNumber('My Number Property', instance);
const { value: colorValue, setValue: setColorValue } = useRiveColor('My Color Property', instance);
const { value: enumValue, setValue: setEnumValue } = useRiveEnum('My Enum Property', instance);
const { trigger: triggerButton } = useRiveTrigger('My Trigger Property', instance, {
onTrigger: () => {
console.log('Trigger fired');
}
});
useEffect(() => {
if (numberValue !== undefined) {
console.log('numberValue changed:', numberValue);
}
}, [numberValue]);
const handleButtonPress = () => {
triggerButton();
};
useRiveTriggerhook 返回一个trigger函数,调用该函数可以触发触发器。此 hook 在其第三个参数中接受一个可选的onTrigger回调,该回调将在触发器被触发时执行。
旧版运行时
值通过 hooks 观察:
const [setRiveRef, riveRef] = useRive();
const [boolValue, setBoolValue] = useRiveBoolean(riveRef, 'My Boolean Property');
const [stringValue, setStringValue] = useRiveString(riveRef, 'My String Property');
const [numberValue, setNumberValue] = useRiveNumber(riveRef, 'My Number Property');
const [colorValue, setColorValue] = useRiveColor(riveRef, 'My Color Property');
const [enumValue, setEnumValue] = useRiveEnum(riveRef, 'My Enum Property');
const triggerButton = useRiveTrigger(riveRef, 'My Trigger Property', () => {
console.log('Trigger fired');
});
useEffect(() => {
if (numberValue !== undefined) {
console.log('numberValue changed:', numberValue);
}
}, [numberValue]);
const handleButtonPress = () => {
if (triggerButton) {
triggerButton();
}
};
useRiveTriggerhook 不返回值,而是接受一个回调函数作为其第三个参数。此回调将在触发器被触发时执行。
图像
图像属性允许您在运行时设置和替换光栅图像,每个图像实例独立管理。例如,您可以构建一个头像创建器并动态更新特征——比如更换帽子——通过设置视图模型的图像属性。
新版运行时(推荐)
可以使用 ViewModelInstance 上的 imageProperty 方法和用于加载图像的 RiveImages 工具来设置图像属性。
import {
useRive,
useRiveFile,
useViewModelInstance,
RiveView,
RiveImages,
type RiveViewRef
} from '@rive-app/react-native';
import { useRef } from 'react';
const { riveViewRef, setHybridRef } = useRive();
const { riveFile } = useRiveFile(require('./my_file.riv'));
const { instance } = useViewModelInstance(riveFile);
const riveViewRef = useRef<RiveViewRef>(undefined);
const handleLoadImage = async () => {
if (!riveViewRef) return;
const vmi = riveViewRef.getViewModelInstance();
if (!vmi) return;
const imgProp = vmi.imageProperty('imageValue');
if (!imgProp) return;
// 从 URL 加载图像
const riveImage = await RiveImages.loadFromURLAsync(
'https://picsum.photos/id/372/500/500'
);
imgProp.set(riveImage);
riveViewRef.playIfNeeded();
};
return (
<RiveView
hybridRef={setHybridRef}
file={riveFile}
dataBind={instance}
autoPlay={true}
/>
);
您还可以向图像属性添加监听器:
const imgProp = vmi.imageProperty('imageValue');
if (imgProp) {
imgProp.addListener(() => {
console.log('Image property changed!');
});
}
RiveImages 上的其他图像加载选项:
/**
* 从捆绑资源加载图像
* @param resource 资源名称(例如 "image.png")
* @returns 解析为已加载 RiveImage 的 Promise
*/
loadFromResourceAsync(resource: string): Promise<RiveImage>;
/**
* 从原始字节加载图像
* @param bytes 作为 ArrayBuffer 的图像数据
* @returns 解析为已加载 RiveImage 的 Promise
*/
loadFromBytesAsync(bytes: ArrayBuffer): Promise<RiveImage>;
旧版运行时
⚠️ 图像数据绑定在旧版运行时上不受支持。
列表
列表属性允许您在运行时管理一组动态的视图模型实例。例如,您可以构建一个 TODO 应用,用户可以在可滚动的布局中添加和删除任务。
参见编辑器章节了解创建数据绑定列表。
单个列表属性可以包含不同的视图模型类型,每个视图模型绑定到其自己的组件,使得可以轻松地用各种组件实例填充列表。
使用列表属性,您可以:
- 添加新的视图模型实例(可选择在指定索引处)
- 删除现有的视图模型实例(可选择按索引)
- 交换两个视图模型实例(按索引)
- 获取列表的大小
有关列表属性的更多信息,请参见数据绑定列表属性编辑器文档。
新版运行时(推荐)
使用 useRiveList hook 管理视图模型实例上的列表属性。
import {
useRiveFile,
useViewModelInstance,
useRiveList,
RiveView
} from '@rive-app/react-native';
const { riveFile } = useRiveFile(require('./my_file.riv'));
const { instance } = useViewModelInstance(riveFile);
// 获取列表属性及操作函数
const {
length,
getInstanceAt,
addInstance,
addInstanceAt,
removeInstance,
removeInstanceAt,
swap,
error
} = useRiveList('todos', instance);
// 添加新的待办事项
const handleAddItem = async () => {
const todoItemViewModel = await riveFile?.viewModelByNameAsync('TodoItem');
if (todoItemViewModel) {
const newTodoItem = await todoItemViewModel.createBlankInstanceAsync();
if (newTodoItem) {
// 设置一些初始值
newTodoItem.stringProperty('description')?.set('Buy groceries');
addInstance(newTodoItem);
}
}
};
// 在指定索引处插入项
const handleInsertItem = async () => {
const todoItemViewModel = await riveFile?.viewModelByNameAsync('TodoItem');
if (todoItemViewModel) {
const newTodoItem = await todoItemViewModel.createBlankInstanceAsync();
if (newTodoItem) {
addInstanceAt(newTodoItem, 0); // 在开头插入
}
}
};
// 按实例删除第一项
const handleRemoveFirst = () => {
const firstInstance = getInstanceAt(0);
if (firstInstance) {
removeInstance(firstInstance);
}
};
// 按索引删除项
const handleRemoveAt = () => {
if (length > 0) {
removeInstanceAt(0);
}
};
// 交换两项
const handleSwap = () => {
if (length >= 2) {
swap(0, 1);
}
};
console.log(`List has ${length} items`);
return (
<RiveView
file={riveFile}
dataBind={instance}
autoPlay={true}
/>
);
旧版运行时
⚠️ 列表数据绑定在旧版运行时上不受支持。
画板
画板属性允许您在运行时交换整个组件。这对于创建可在不同设计或应用间复用的模块化组件非常有用,例如:
- 创建支持大量变体的换肤系统,例如角色创建器,您可以交换不同的身体部位、服装和配饰。
- 创建由来自不同 Rive 文件的多个画板组成的复杂场景(绘制到单个画布/纹理/控件上)。
- 通过将单个 Rive 文件拆分为可按需加载并按需交换的较小组件,减小文件大小(复杂度)。