本文记录把 react-native-animatable(声明式动画库)适配到 HarmonyOS 的完整过程。

这是一个动画库:给任意组件套一层 <Animatable.View animation="fadeIn" duration={600}> 就会出现动画;内置 62 个动画(fadeIn / bounce / zoomIn / slideInDown…),也支持自己注册。它还有一套命令式实例方法(ref.animate() / ref.transitionTo()),并且把这 62 个动画名批量挂成了实例方法。

它有两个特别值得写下来的点:

  1. ★★ 同一个库里存在三类性质完全不同的"会报错" —— 而它们的捕获方式完全不同:

    #出错位置性质怎么捕获本轮实测
    ①createAnimation(definition)(已导出,直接调用)同步 throwtry / catch✅ 抓到 Animation definitions must have at least two values.
    ②ref.animate('不存在的名字')throw 发生在 new Promise 的执行器内 ⇒ Promise reject.catch()✅ 抓到 No animation registred by the name of __no_such_animation__
    ③render() 里同时给 animation + transition渲染期 throw都抓不到,会崩整棵树⚠️ 故意不触发
  2. ★★ 用"末态"做像素判据,再给它配一个对照组。 fadeIn 是 opacity 0→1,而源码里
    getAnimationTarget(iteration, direction) 对 reverse 返回 0、对 normal 返回 1
    ⇒ 同一个动画、只改 direction,末态就完全相反。实测(标记色 #E91E63):

    实例配置末态标记色像素结论
    P1fadeIn · normal57.8%(两万多像素)末态不透明 ✓
    P2fadeIn · reverse0(一个都没有)末态透明 ✓

    再用两帧差分验"是不是真的在动",并配一个静止对照:

    实例配置两帧不同像素结论
    P3iterationCount="infinite" + alternate26,556 / 44,220 = 60.05%(最大通道差 280)持续在动 ✓
    P4动画已结束(delay 过期后跑完)0 / 44,220完全静止 ✓(对照组)

设备侧断言 44 / 44 全部通过,一次构建就过。

在这里插入图片描述


一、先说结论

项结果
上游最新版1.4.0;license: MIT;上游 star 9.9k
库类型纯 JS 组件库(声明式动画 + 命令式实例方法):View / Text / Image 三个包装组件
是否需要原生适配不需要(无 harmony/、无 NativeModules;import 图只有相对路径 + prop-types / react / react-native)
运行时依赖⚠️ prop-types@^15.8.1 ⇒ 目录/file: 接入不会自动装(宿主已有 15.8.1 ⇒ 本轮未被咬到)
prepare 家族✅ 没有(只有 jest / lint / test / format)
mainindex.js(源码直发,没有编译产物目录)
导出形态7 个:View / Text / Image / createAnimatableComponent / createAnimation / registerAnimation / initializeRegistryWithDefinitions
内置动画数62 个;easing 名 29 个
需要 HAR / 权限 / ohpm❌ 都不需要
设备侧断言✅ 44 / 44 全部通过(A 30 + B 10 + C 4),一轮构建就过
★★ 三类"会报错"✅ ① 同步 throw(try/catch)② Promise reject(.catch())③ 渲染期 throw(故意不触发)
★ 末态像素判据✅ normal ⇒ 57.8% 不透明、reverse ⇒ 0 透明
★ 两帧差分 + 对照✅ 在动 60.05% vs 静止 0

⚠️ 一个值得注意的交付状态:本库交付包的 spec.json 里
"validation": "build-pass-device-pending"
—— 意思是交付方只做了构建验证、设备侧验证尚未完成。本轮把这一步补上了(44/44 断言 + 像素判据 + 两帧差分)。


二、判定与接入

步做法结果
①package.json 里有没有 harmony.autolinking没有
②仓库里有没有 harmony/没有
③代码里有没有 NativeModules / requireNativeComponent全无
④import 图体检只有相对路径 + prop-types / react / react-native ✓
⑤scripts 扫 prepare 家族✅ 没有
npm install "git+https://atomgit.com/oh-react-native/react-native-animatable.git#1.4.0-ohos-1.0.0" --ignore-scripts

# ⚠️ 它带运行时依赖 prop-types,目录/软链接入不会自动装
npm install prop-types --ignore-scripts
// metro.config.js
watchFolders: [ path.resolve(__dirname, '../react-native-animatable') ],
import AnimatableTestApp from './AnimatableTestApp';
AppRegistry.registerComponent('AnimatableTestApp', () => AnimatableTestApp);
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey AnimatableTestApp

不需要 HAR、权限、ohpm、link-harmony。


三、读源码取到的真值

3.1 入口(index.js)

import createComponent from './createAnimatableComponent';
import { initializeRegistryWithDefinitions } from './registry';
import * as ANIMATION_DEFINITIONS from './definitions';

initializeRegistryWithDefinitions(ANIMATION_DEFINITIONS);   // ← 模块加载时就把 62 个动画注册好

export const createAnimatableComponent = createComponent;
export const View  = createComponent(CoreView);
export const Text  = createComponent(CoreText);
export const Image = createComponent(CoreImage);
export { default as createAnimation } from './createAnimation';
export { registerAnimation, initializeRegistryWithDefinitions } from './registry';

实测:命名空间正好 7 个导出 ✓;View.displayName === 'withAnimatable(View)' ✓

3.2 ★ 包装组件的契约(createAnimatableComponent.js,607 行)

return class AnimatableComponent extends Component {
  static displayName = `withAnimatable(${wrappedComponentName})`;
  static propTypes = { … 15 键 … };      // 实测 15
  static defaultProps = {                // 实测 15
    animation: undefined, delay: 0, direction: 'normal', duration: undefined,
    easing: undefined, iterationCount: 1, iterationDelay: 0,
    onAnimationBegin() {}, onAnimationEnd() {},
    onTransitionBegin() {}, onTransitionEnd() {},
    style: undefined, transition: undefined,
    useNativeDriver: false, isInteraction: undefined,
  };

★ iterationCount 带一个自定义校验器(可以直接调用,不需要渲染):

iterationCount(props, propName) {
  const val = props[propName];
  if (val !== 'infinite' && !(typeof val === 'number' && val >= 1)) {
    return new Error('iterationCount must be a positive number or "infinite"');
  }
  return null;
}

实测:1 → null、'infinite' → null、0 → Error、'x' → Error ✓

3.3 ★★ 62 个动画被批量挂成"实例方法"

// 构造器里
getAnimationNames().forEach((animationName) => {
  if (!(animationName in this)) {
    this[animationName] = this.animate.bind(this, animationName);
  }
});

实测:注册动画 62 个,实例上的别名方法也是 62 个 ✓ 完全一致
⇒ ref.current.fadeIn() / ref.current.bounce() … 都能直接调 ✓

实例方法实测:

animate:function  stopAnimation:function  transition:function  transitionTo:function
setAnimation:function  startAnimation:function  setNativeProps:function

3.4 ★★★ 三类"会报错"(性质完全不同)

① 同步 throw —— createAnimation(已导出)
export default function createAnimation(definition) {
  const positions = Object.keys(definition).map(parsePosition).filter(notNull);
  positions.sort(compareNumbers);
  if (positions.length < 2) {
    throw new Error('Animation definitions must have at least two values.');   // ← 同步
  }
  …
  if (!keyframe) {
    throw new Error('Missing animation keyframe, this should not happen');     // ← 同步
  }

实测:createAnimation({from:{opacity:0}}) ⇒ try/catch 抓到
Animation definitions must have at least two values. ✓

它还有模块级 cache(键 = JSON.stringify(definition)):

const cache = {};
const cacheKey = JSON.stringify(definition);
if (cache[cacheKey]) { return cache[cacheKey]; }

实测:同一 definition 两次调用返回同一个对象(=== 为 true)✓

② Promise reject —— 未注册的动画名
function getCompiledAnimation(animation) {
  if (typeof animation === 'string') {
    const compiledAnimation = getAnimationByName(animation);
    if (!compiledAnimation) {
      throw new Error(`No animation registred by the name of ${animation}`);   // ← 在下面这个 Promise 执行器里
    }
    …
  }
  return createAnimation(animation);
}

// 而 animate 是这样包的:
animate(animation, duration, iterationDelay) {
  return new Promise((resolve) => {
    this.setAnimation(animation, () => { this.startAnimation(duration, 0, iterationDelay, resolve); });
  });                                  // ↑ throw 发生在这里 ⇒ 变成 Promise reject
}

实测(用 ref.current.animate('__no_such_animation__') + .catch()):
No animation registred by the name of __no_such_animation__ ✓ 页面没有崩 ✓

③ 渲染期 throw —— 同时给 animation 和 transition
render() {
  const { style, animation, transition } = this.props;
  if (animation && transition) {
    throw new Error('You cannot combine animation and transition props');   // ← 渲染期
  }

⚠️ 本轮的测试页【故意不触发它】 —— 因为渲染期的异常既不会被调用点的 try/catch 抓到,也不会被 .catch() 抓到,它会变成一次 React 渲染错误、把整棵树搞崩。
⇒ 只作源码级记录,并在文章里明确标注"运行时未触发"。

3.5 ★ 末态为什么相反(像素判据的根据)

function getAnimationTarget(iteration, direction) {
  switch (direction) {
    case 'reverse':            return 0;
    case 'alternate':          return iteration % 2 ? 0 : 1;
    case 'alternate-reverse':  return iteration % 2 ? 1 : 0;
    case 'normal':
    default:                   return 1;
  }
}
function getAnimationOrigin(iteration, direction) {
  return getAnimationTarget(iteration, direction) ? 0 : 1;
}

而 fadeIn 是 from {opacity: 0} → to {opacity: 1}(createAnimation 把 from→位置 0、to→位置 1)
⇒ normal 结束时 animatedValue = 1(不透明);reverse 结束时 = 0(透明) ✓

3.6 其它

  • 默认时长:duration || this.props.duration || 1000(1000ms);
  • isInteraction 默认:iterationCount <= 1;
  • useNativeDriver 默认 false(JS 驱动);
  • 可插值的样式属性表 INTERPOLATION_STYLE_PROPERTIES(rotate* / skew* / 各种 *Color / tintColor 等);
  • width / height 属 ZERO_CLAMPED_STYLE_PROPERTIES(带 clamp 外推)。

四、验证设计

判据内容
① 末态像素同一动画只改 direction:normal 末态不透明、reverse 末态透明 ⇒ 数标记色像素
② 两帧差分 + 对照iterationCount="infinite" 应持续在动;已结束的实例应完全相同
③ 三类报错各自的可捕获性① try/catch ② .catch() ③ 不触发

五、实测结果

5.1 断言 44 / 44

组内容结果
A 契约7 个导出、displayName、defaultProps/propTypes 各 15 键、iterationCount 校验器直调 4 例、① 同步 throw、cache 同一性、62 个动画、29 个 easing、prop-types 可加载30/30
B 渲染与动画4 个动画实例渲染未抛错、标题不与状态栏重合、实例 7 个方法 + 62 个别名方法、animate() 返回 Promise10/10
C 三类报错① try/catch 有效、② .catch() 接管 reject、③ 识别并故意不触发、事件记录格式4/4
合计44/44

关键观测值(日志):

命名空间导出 = Image, Text, View, createAnimatableComponent, createAnimation,
               initializeRegistryWithDefinitions, registerAnimation
View.displayName = withAnimatable(View)
defaultProps 键数 = 15        propTypes 键数 = 15
已注册动画名数量 = 62          easing 名数量 = 29
★ 实例上的动画别名方法数 = 62
★ 实例方法 = animate:function stopAnimation:function transition:function
             transitionTo:function setAnimation:function startAnimation:function setNativeProps:function
事件 = ANIM P1 onAnimationBegin(fadeIn) | ANIM P2 onAnimationBegin(fadeIn/reverse)
     | ANIM P1 onAnimationEnd(fadeIn) finished=true | ANIM P2 onAnimationEnd(fadeIn/reverse) finished=true

5.2 ★★ 末态像素判据

标记块 = 300×60 vp = 900×180 px,标记色 #E91E63。

实例配置区域命中率结论
P1fadeIn · normal57.8%(22,899 点)末态不透明 ✓
P2fadeIn · reverse0%(0 点)末态透明 ✓

⇒ 同屏内"一个有色块、一个什么都没有",getAnimationTarget 的 reverse ⇒ 0 语义被像素坐实 ✓

在这里插入图片描述

5.3 ★★ 两帧差分(含静止对照组)

实例配置两帧不同像素最大通道差结论
P3fadeIn · infinite + alternate26,556 / 44,220 = 60.05%280持续在动 ✓
P4delay=1200 后跑完的实例0 / 44,2200完全静止 ✓

⇒ "在动"这个结论有对照组支撑(同一测法、同一区域尺寸、同样的阈值 24),不是只看一个数字 ✓

在这里插入图片描述

5.4 ★ 三类报错的实测日志

① createAnimation({from}) 的结果 = Animation definitions must have at least two values.
② 未注册名 ⇒ Promise reject:No animation registred by the name of __no_such_animation__
③ 渲染期 throw(故意不触发)—— 仅源码级记录

5.5 UI:标题不与状态栏重合

TITLE measureInWindow y=420   (状态栏高 117)   ⇒ 420 > 117,不重合 ✓

在这里插入图片描述


六、本轮的方法要点

6.1 ★ "会报错"要分成三类看

如果只把"会报错"当成一种情况,很容易写错用例。这个库把三种情况同时摆在了面前:

类别判断方法捕获方式
同步 throwthrow 就在被调函数体里,调用栈同步返回try / catch
Promise 内 throwthrow 在 new Promise(executor) 的 executor 里 ⇒ 变成 reject.catch()(否则就是 unhandled rejection)
渲染期 throwthrow 在 render() / 函数组件体 / 子组件渲染中都抓不到 —— 需要 error boundary,或别触发

⇒ 写"预期报错"的用例前,先把它归到这三类中的哪一类。

6.2 ★ 用"末态"而不是"过程"做判据,最省事也最稳

动画的中间帧依赖时序(截图早一点晚一点都不一样),但末态是稳定的。
fadeIn 的末态只由 direction 决定 ⇒ 两个实例同屏对照,一个有色块一个没有 ⇒ 判据二值、无阈值调参、可肉眼复核。

6.3 ★ 差分法必须自带对照组

"两帧不同像素 60%“这个数字本身不能证明"在动”——阈值、压缩噪声、采样密度都会影响它。
所以我同时测了一个已知动画已结束的实例(P4)⇒ 0 / 44,220。两个数字一起看,"在动/没动"才是可信的。


七、已知限制与本次验证边界

7.1 用这个库要知道的

事项说明
有运行时依赖 prop-types目录/file: 接入要手动补装
animation 与 transition 不能同时给同时给会在渲染期抛错、整棵树崩
62 个动画名被挂成实例方法别用这些名字当自己的方法名,否则可能撞名
动画名写错不会同步报错从 ref.animate('错名') 调用时是 Promise reject(不 catch 就是 unhandled rejection);而作为 animation prop 给错名,是在构造/渲染期抛错
默认时长 1000msduration 不传时按 1000ms
useNativeDriver 默认 false想走原生驱动要显式开
createAnimation 有全局 cache同一 definition 返回同一对象(注意别就地改它)

7.2 本次验证的边界

  1. transition / transitionTo / onTransitionBegin / onTransitionEnd 未验证(只确认方法存在);
  2. iterationCount 的真实循环次数未逐次数验证(只验了校验器 + infinite 持续在动);
  3. direction: 'alternate' / 'alternate-reverse' 的末态未逐轮验证;
  4. easing 29 个函数只验了"存在",未逐个比对曲线;
  5. onAnimationEnd 的 endState 只看了 finished,未验 cancelled 等分支;
  6. stopAnimation() 未验证(以及"动画中途把 animation 置空会停止"这条源码行为);
  7. delay / iterationDelay 只从源码确认语义,未做严格计时;
  8. transition 相关的 INTERPOLATION_STYLE_PROPERTIES / ZERO_CLAMPED_STYLE_PROPERTIES 分支未验证;
  9. Text / Image 两个包装组件未单独验证(只测了 View);
  10. createAnimatableComponent(自定义组件) 未验证;
  11. ③ 渲染期 throw 未运行时复现(故意不触发);
  12. 只在一台模拟器上验证(Pura X View,density 3,1320×2232),没有真机;未做跨平台对照。

未改动库代码。


八、常见问题

Q1:animation 名字写错了会怎样?

分两种情况(这是本库最容易踩的点):

// ① 作为 prop 给错名 ⇒ 在【构造/渲染期】抛错 ⇒ try/catch 抓不到,会崩
<Animatable.View animation="fadeInnn" />        // ❌

// ② 用实例方法调用 ⇒ 变成 Promise reject ⇒ 能 .catch()
ref.current.animate('fadeInnn').catch(e => console.log(e.message));
// No animation registred by the name of fadeInnn

Q2:为什么我的动画"方向反了"?

看 direction:
normal → 结束在 to;reverse → 结束在 from;alternate / alternate-reverse → 交替。
例如 fadeIn(opacity 0→1)配 direction="reverse" 的末态是透明(实测标记色像素 0)。

Q3:能不能同时用 animation 和 transition?

不能。 源码 render() 里明确:

if (animation && transition) { throw new Error('You cannot combine animation and transition props'); }

⚠️ 这是渲染期抛错 ⇒ 会崩整棵树(不是某一行报错)。

Q4:怎么让动画一直循环?

<Animatable.View animation="fadeIn" iterationCount="infinite" direction="alternate" duration={800} />

iterationCount 只接受正整数或 "infinite"(源码里的自定义校验器:iterationCount must be a positive number or "infinite")。

Q5:能不能不写 JSX,直接用代码触发动画?

能,用实例方法(62 个动画名都是方法):

const ref = useRef(null);
<Animatable.View ref={ref} />
ref.current.fadeIn(600);        // 等价于 animate('fadeIn', 600)
ref.current.animate('bounce', 800).then(() => console.log('done'));
ref.current.transitionTo({opacity: 0.5}, 300);
ref.current.stopAnimation();

Q6:npm install 之后报找不到 prop-types?

它带运行时依赖 prop-types,而目录/file: 接入不会安装目标包自己的 dependencies:

npm install prop-types --ignore-scripts

Q7:为什么改同一个动画定义,行为没变化?

createAnimation 有全局 cache(键是 JSON.stringify(definition))⇒ 同样的定义返回同一个对象。要新定义请改内容(让 JSON.stringify 不同),不要就地修改返回的对象。

Q8:这个库需要鸿蒙侧做什么特殊处理吗?

不需要。 纯 JS、无原生模块、不需要 HAR / 权限 / ohpm。注意上面几条(prop-types、animation+transition 冲突、动画名写错的两种报错形态)即可。


小结

react-native-animatable 是纯 JS 声明式动画库,零原生接线、源码直发。设备侧 44 / 44 断言通过,一次构建就过。

最值得记下来的是这几条:

  1. ★★ "会报错"要分成三类,捕获方式完全不同。 这个库同时具备:
    ① 同步 throw(createAnimation,try/catch 抓到)、
    ② Promise 执行器内 throw(animate('错名') ⇒ reject,.catch() 抓到)、
    ③ 渲染期 throw(animation+transition 同时给 ⇒ 都抓不到,崩整棵树)。
    ⇒ 由此可以把"预期报错"的用例先归类再写:三类各有各的捕获方式。

  2. ★ 用"末态"做判据比用"过程"稳。 fadeIn + normal ⇒ 末态不透明(57.8% 标记色)、fadeIn + reverse ⇒ 末态透明(0)。同屏二值对照、无阈值调参、可肉眼复核。

  3. ★ 差分法要自带对照组。 我不仅测了在动的实例(60.05%),还测了一个已知动画已结束的实例:0 / 44,220 ⇒ 两个数字一起看才可信。

  4. ★ 顺手把"批量生成的东西"数量对齐验证。 源码在构造器里把每个注册动画挂成实例方法 ⇒ 我同时断言 注册动画数 = 62 与 实例别名方法数 = 62,两者一致才说明那段循环真的生效(比只断言"存在"强)。

  5. 交付包状态也值得记录:本库 spec.json 是 "build-pass-device-pending"(构建通过、设备侧待验证)⇒ 本轮的 44/44 + 像素判据 + 两帧差分正好把设备侧那一格补上。


本篇用到的库

项内容
三方库react-native-animatable(上游 1.4.0 的鸿蒙适配版)
适配仓库https://atomgit.com/oh-react-native/react-native-animatable
适配 TAG1.4.0-ohos-1.0.0
需要 HAR / 权限 / ohpm都不需要(纯 JS)
库类型纯 JS 组件库:声明式动画(View / Text / Image 三个包装组件)
运行时依赖⚠️ prop-types@^15.8.1 ⇒ 目录/file: 接入须手动补装
prepare / prepublish都没有
内置动画 / easing62 个动画、29 个 easing 名
许可证MIT
上游仓库https://github.com/oblador/react-native-animatable(★9.9k)
宿主工程RNOH084Demo(测试页 rnAppKey = AnimatableTestApp)
# 从适配仓库安装
npm install "git+https://atomgit.com/oh-react-native/react-native-animatable.git#1.4.0-ohos-1.0.0" --ignore-scripts

# 本地目录接入(开发常用):必须补 prop-types
npm install ../react-native-animatable --ignore-scripts
npm install prop-types --ignore-scripts
// metro.config.js
watchFolders: [ path.resolve(__dirname, '../react-native-animatable') ],
import * as Animatable from 'react-native-animatable';
import { createAnimation, registerAnimation } from 'react-native-animatable';

// ① 声明式:套一层就有动画
<Animatable.View animation="fadeIn" duration={600} direction="normal"
                 onAnimationBegin={() => {}} onAnimationEnd={(e) => console.log(e.finished)} />

// ② 一直循环(末态交替)
<Animatable.View animation="fadeIn" iterationCount="infinite"
                 direction="alternate" duration={800} />

// ③ 延迟 + 缓动
<Animatable.Text animation="bounceIn" delay={500} duration={1000} easing="ease-out"
                 style={{fontSize: 20}}>你好</Animatable.Text>

// ④ 命令式(62 个动画名都是实例方法)
const ref = useRef<any>(null);
<Animatable.View ref={ref} style={{width: 300, height: 60, backgroundColor: '#E91E63'}} />
// 之后:
ref.current.fadeIn(600);
ref.current.animate('bounce', 800).then(() => console.log('done')).catch(e => console.log(e.message));
ref.current.transitionTo({opacity: 0.5}, 300);
ref.current.stopAnimation();

// ⑤ 自定义动画(注册后即可用 animation="myFade")
registerAnimation('myFade', createAnimation({ from: {opacity: 0}, to: {opacity: 1} }));
// ⚠️ createAnimation 至少要有两个关键帧,否则【同步抛错】
// ⚠️ 不要同时给 animation 和 transition(渲染期抛错、崩整棵树)
# 换页启动测试页(force-stop 不能省,换页参数只在冷启动生效)
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey AnimatableTestApp

三类报错的复现方式

// ① 同步 throw ⇒ try/catch 有效
try { createAnimation({from: {opacity: 0}}); }
catch (e) { console.log(e.message); }        // Animation definitions must have at least two values.

// ② Promise reject ⇒ .catch() 有效
ref.current.animate('__no_such_animation__')
  .then(() => {})
  .catch(e => console.log(e.message));       // No animation registred by the name of __no_such_animation__

// ③ 渲染期 throw ⇒ 都不要试(会崩整棵树)
// <Animatable.View animation="fadeIn" transition={{opacity: 1}} />
//   render() 里 throw new Error('You cannot combine animation and transition props')

两帧差分 + 对照组的复现方式

# 阈值:sum(|ΔR|+|ΔG|+|ΔB|) > 24
# 同一区域、同样采样密度,测两次:
#   在动的实例(iterationCount="infinite")⇒ 应得到较大的百分比
#   已结束的实例                        ⇒ 应为 0(对照组)
# 两者一起看,才能说"确实在动"

验证环境

项版本
React Native0.84.1
React19.2.3
RNOH(npm / ohpm)@react-native-oh/react-native-harmony / @rnoh/react-native-openharmony 0.84.3
Node.js / npmv24.14.0 / 11.9.0
DevEco Studio26.0.0.621
HarmonyOS SDKAPI 26(26.0.0.32)
设备HarmonyOS 7.0.0(26.0.0) Beta2 模拟器 Pura X View(ohos-x64),1320×2232,density 3
本机状态栏高度117px(页面留白取 48vp)
宿主 prop-types15.8.1
宿主 HAP 产物entry-default-signed.hap
本次构建assembleHap 4 分 45 秒(一轮就过,无重编)
验证规模设备侧 44 / 44 断言;末态像素判据 2 组(不透明 57.8% vs 透明 0);两帧差分 2 组(在动 60.05% vs 静止 0,含对照组);三类报错各 1 例(① 抓到 ② 抓到 ③ 故意不触发);UI 安全区 1 项

相关社区与项目地址

项目地址
React Native 鸿蒙官方社区(CPF-RN)https://atomgit.com/CPF-RN
RNOH 框架仓库https://atomgit.com/openharmony-RN/ohos_react_native
RNOH 三方库使用文档https://atomgit.com/OpenHarmony-RN/usage-docs
React Native for OpenHarmony 组织https://atomgit.com/oh-react-native
RN 三方库鸿蒙适配清单https://atomgit.com/oh-react-native/rn-ohos-adaptation-overview

欢迎加入 CPF-RN 鸿蒙社区:https://atomgit.com/CPF-RN

React Native for OpenHarmony 组织:https://atomgit.com/oh-react-native

RN 三方库鸿蒙适配清单:https://atomgit.com/oh-react-native/rn-ohos-adaptation-overview

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐