本文记录把 react-native-image-pan-zoom 适配到 HarmonyOS 的完整过程。

这个库是纯 JS 的图片平移缩放组件(单指拖动、双指捏合、双击缩放),零运行时依赖,接入很轻。但它把两个不同性质的问题一起带了进来,而且都不在"能不能跑"这个层面上:

  1. 它的交付包里有一个会删掉自己入口文件的 prepare 脚本 —— npm pack 和 npm install 都会因此失败,而且失败之后包是坏的;
  2. 它的效果全部由 transform 体现,而布局 dump 不反映 transform —— 也就是说,"用什么当判据"这件事本身要先解决,否则会把"生效了"误判成"没生效"。

第二条尤其值得记:我是先用一个正对照证明"变换确实生效、是判据不灵",才换成像素判据,最后拿到 57 / 117 / 35 和 300px 位移这些定量结果的。

在这里插入图片描述


一、先说结论

项结果
上游最新版2.1.12;gitHead = 30a7b4d4c832faebfcdb3c1131869801df56c6e3;许可证是 ISC(不是常见的 MIT)
库类型纯 JS 手势库(class 组件),把 transform 加在包裹 Image 的 Animated.View 上
是否需要原生适配不需要(无 harmony/、无 NativeModules、无 requireNativeComponent)
运行时依赖零(dependencies 不存在)
与 npm 上游的差异23 / 24 个文件去掉 CR 后逐字符一致(含全部 built/** 编译产物);唯一差异是 package.json
编译assembleHap 4 分 11 秒;HAP 82,050,332 字节(增量 +65,626)
打包资源Copied 14 assets(原 13 + 1 张自建测试图,精确对上)
设备侧断言✅ 45 / 45 全部通过
⚠️ 安装硬约束必须 npm install --ignore-scripts;不加则 exit 2,且 built/index.js 被删除(第 3 节)
★ 判据纠正布局 dump 不反映 transform ⇒ 这类库必须用像素判据(第 5 节)
★ 一个真实的 API 陷阱centerOn({x, y, scale}) 的 x/y 会被 scale 放大:y=50 在 scale=2 下实际位移 100vp = 300 像素(实测精确吻合,第 7 节)

一句话结论:库本身能用、渲染与变换都正确;但装它要加 --ignore-scripts,验它要换判据。


二、判定过程:三步走完,确认是纯 JS 库

步做法这个库的结果
①package.json 里有没有 harmony.autolinking没有
②仓库里有没有 harmony/没有
③代码里有没有 NativeModules / requireNativeComponent / TurboModuleRegistry全无

dependencies 字段直接不存在 ⇒ 零运行时依赖,不存在"传递依赖装不上"的问题。

但这个库的入口是编译产物,这一点与一般的纯 JS 库不同:

"main": "built/index.js",
"types": "built/index.d.ts",

也就是说 src/ 是 TypeScript 源码,真正被 require 的是仓库里已提交的 built/。

正因为如此,第 3 节那个 prepare 脚本才致命。


三、⚠️ 安装硬约束:prepare 会删掉交付包自己的入口

3.1 脚本长什么样

"scripts": {
  "prepare": "rm -rf built && tsc",
  "start": "tsc -w --outDir demo/built",
  ...
}

prepare 会在 npm pack 以及 从 tgz / git+ 安装 时自动执行。而 built/ 是已提交、随包发布的(12 个文件,.gitignore 并没有忽略它)。

于是它做了两件都很糟的事:

第一,rm -rf built 把交付包自带的入口文件删了。

第二,它重新编译时用的不是包里声明的 TypeScript。

包里 devDependencies 写的是 typescript@^3.9.2,但消费者安装时不会装 devDependencies,所以 tsc 解析到的是环境 PATH 上的全局版本。我这次实测到的是 TypeScript 6.0.3,直接报两个错:

tsconfig.json(10,15): error TS5107: Option 'target=ES5' is deprecated and will stop functioning in TypeScript 7.0.
tsconfig.json(11,5):  error TS5011: The common source directory of 'tsconfig.json' is './src'.
                                  The 'rootDir' setting must be explicitly set to this or another path to adjust your output's file layout.

而 tsc 默认即使报错也会 emit,加上缺 rootDir 把公共根判成了包根 ⇒ 产物多套了一层 src/:

布局
交付的原版built/index.js、built/image-zoom/*.js
prepare 跑完后built/src/index.js、built/src/image-zoom/*.js

⇒ main 指向的 built/index.js 不存在了。 失败模式从"装不上"升级成"包静默损坏"。

3.2 实测(三组对照)

⚠️ 实验是在库的副本上做的 —— 因为这个脚本会真的删目录。做完复查真库:built/ 12 个文件、git 干净。

实验命令exitbuilt/index.js出现 built/src结论
1npm pack2—(tgz 未产出)—连包都打不出来
2npm install(file:,不加 --ignore-scripts)2被删除是装不上,而且把入口删了
3npm install --ignore-scripts0保留(12 文件)否✅ 唯一可行

实验 2 的原始报错:

npm error code 2
npm error path <库目录>
npm error command C:\windows\system32\cmd.exe /d /s /c rm -rf built && tsc
npm error tsconfig.json(10,15): error TS5107: ...
npm error tsconfig.json(11,5): error TS5011: ...

3.3 这是谁的锅:上游的脚本,但交付包本该处理

  • npm 元数据里的 scripts.prepare 与交付包完全一致 ⇒ 不是适配者引入的;
  • 但交付包改了 package.json 的 4 个字段(description / repository / homepage / test),却把这行会破坏安装的 prepare 原样留下了;
  • ⇒ 作为鸿蒙交付包,至少应该把"必须 --ignore-scripts"写进 README,或者干脆修掉这个脚本(不删目录、或补上 rootDir)。

3.4 正确做法

# ✅ 必须这样装
npm install --ignore-scripts

# ❌ 这两种都会触发 prepare 并失败
npm install          # exit 2
npm pack             # exit 2

判据:凡是随包发布编译产物的库,只要看到 prepare / prepack 动了那个产物目录,就先做一次安装实验;而且实验必须在副本上做。


四、交付包质量

4.1 与 npm 上游逐文件比对(先去 CR)

类别结果
一致23 个文件(src/** 源码、.eslintrc.js、.prettierrc、tslint.json、tsconfig.json、LICENSE、README.md,以及 built/** 全部 12 个编译产物)
不同1 个:package.json
交付包缺少0
交付包多出6 个:.gitignore、两个 README.OpenHarmony*、代码检查报告、spec.json、契约测试

package.json 只改了 4 处:description 追加鸿蒙字样、repository.url 指向 AtomGit、homepage 指向 AtomGit、新增 scripts.test。

实现与产物双重零改动。

4.2 做得好的

  • 连编译产物都与 npm 逐字符一致(这点比只比源码更严格);
  • spec.json 带 license: ISC(与 npm 一致),并写了 native: false / rnoh 0.84.3 / reactNative 0.84.1;
  • 零运行时依赖;
  • 一个看着像错、其实没错的地方:包名是 react-native-image-pan-zoom,而 spec.json 里的 upstream.url 写的是 github.com/ascoders/react-native-image-zoom —— 查 npm 的 repository 字段证实上游仓库确实叫这个名,所以不是缺陷。(名字不一致要先查证再定性。)

4.3 问题

问题说明
prepare 会破坏安装(见第三节)交付包改了 package.json 4 个字段却没处理它;也没在 README 里写"必须 --ignore-scripts"
spec.json 缺 upstreamCommit而 npm 明确有 gitHead(30a7b4d4…)⇒ 属应修项
validation 是裸字符串 "pass"没有可复核的测试项清单
契约测试是包级 smoke test只检查包名 / 版本 / main 能 require.resolve / 中文 README 存在。一次组件都没渲染过 —— 本轮 45 条断言里任何一条写错,它都是绿的
两个交付 README 都没写① 必须 --ignore-scripts 安装;② 组件有"ref 命令式"与"centerOn prop 受控"两套独立驱动方式(行为不同,见 8.4)

五、验证设计:先解决"用什么当判据"

这个库的效果只有两种体现:scale 和 translate。所以我一开始的想法很自然:

它把 transform 加在包裹 Image 的 Animated.View 上,那读布局矩形不就能量到缩放和位移了吗?

这个想法是错的,而且如果不做对照,我会得到一个彻底的假结论。下面把过程完整记下来,因为它比结论更有用。

5.1 第一版判据:布局矩形

组件的结构是:

<View style={{width: cropWidth, height: cropHeight}} {...panHandlers}>
  <Animated.View style={{transform: [{scale}, {translateX}, {translateY}]}}>
    <Image style={{width: imageWidth, height: imageHeight}} />
  </Animated.View>
</View>

我通过 ref 调命令式方法把 scale 驱动到已知值(这样就不依赖双指手势),然后读布局:

时刻设备日志里的证据布局 dump 里的矩形
基线—Image x=60 y=684 w=900 h=600
调 centerOn({x:0, y:0, scale:2}) 后centerOn(...),随后 onMove centerOn x=0 y=0 s=2.000Image x=60 y=684 w=900 h=600(完全没变)
再调 centerOn({x:100, y:50, scale:2}) 后onMove centerOn x=100 y=50 s=2.000仍然完全没变

⇒ 三条证据互相矛盾:回调说 scale 已经是 2,布局说尺寸一点没变。

5.2 关键一步:做正对照,而不是直接写"没生效"

"测不到"和"没效果"必须用对照组分开。 我做的正对照是看屏幕:截图里

  • 变换区的图满屏红、看不到蓝边(放大 2× 后,图的蓝边被裁到裁剪窗外);
  • 而同页另一个未变换的区域的图,蓝边清晰可见(原尺寸)。

⇒ 变换确实生效了,是布局 dump 不反映它。

devecocli ui layout 给的是 Yoga 的布局帧,而 transform 是渲染层属性。所以对这个库来说,布局 dump 是无效判据。

5.3 换判据:给测试图做一把"像素标尺"

既然要读像素,就得让像素可解释。我自建了一张 300×200 的 PNG:

  • 红底;
  • 12px 蓝色边框 —— 作为"是否被放大到裁出窗外"的二值信号;
  • 正中间一条 20px 白条 —— 作为精确的 scale 标尺;
  • 外层容器的 backgroundColor: '#000000' —— 当图比裁剪窗小时会露出黑底。

裁剪窗实测矩形 x=60 y=684 w=900 h=600(= 300×200 vp,density 3),窗口中心 Y = 984。

⚠️ 一个自己踩的小坑:JPEG 有压缩,白条边界有几像素过渡(实测 57 / 117 / 35,而不是理论的 60 / 120 / 34.2)。用阈值扫描要接受 ±5% 误差,别把 57 当 60 就判失败。

在这里插入图片描述
在这里插入图片描述


六、像素判据的定量结果

沿裁剪窗中心竖线扫描白条的 Y 跨度:

命令式调用白条可见跨度高度中线 Y左边缘像素解读
reset()(s=1, x=0, y=0)955–101257984白基线:白条中线 = 窗口中心 ✓
centerOn({scale:2})925–1042117 ≈ 2×57984白scale 生效,且绕中心缩放(中线不动)✓
centerOn({scale:0.6})966–100135 ≈ 0.6×57984黑 (0,1,3)scale 生效 + 图小于窗口露出黑底 ✓(双重确认)
centerOn({x:100, y:50, scale:2})1226–1283(被窗口下沿 1284 截断)—1284黑内容确实位移 ✓

几条判据互相独立、互相印证:

  • 白条高度是连续量,直接给出 scale(2× 与 0.6× 都落在 ±5% 内);
  • 中线不动 ⇒ 缩放绕中心(不是绕左上角);
  • 左边缘由白变黑 ⇒ 图变小到比窗口还小,与 scale=0.6 一致;
  • 位移状态下左边缘也是黑的 + 白条整体下移 ⇒ 平移确实发生。

七、一个真实的 API 陷阱:centerOn 的 x/y 会被 scale 放大

最后一行的白条"高度只有 57",一开始让我以为"scale 没生效"。按模型算就完全对上了。

源码里 transform 数组的顺序是:

transform: [ {scale}, {translateX}, {translateY} ]

矩阵从前往后乘 ⇒ 平移量会被前面的 scale 放大。y: 50 在 scale: 2 下 ⇒ 实际位移 100 vp = 300 物理像素:

  • 白条中心 = 984 + 300 = 1284
  • 白条完整跨度 = 1284 ± 58.5 = 1225.5 … 1342.5
  • 裁剪窗下沿是 1284 ⇒ 可见部分 = 1226 … 1284
  • 实测 = 1226 … 1283 ✓✓ 精确吻合

⇒ centerOn({x, y, scale}) 里的 x / y 不是屏幕位移,而是会被 scale 乘一遍的"变换空间"位移。

这是一个只有做定量测量才会发现的结论:如果只检查"有没有动",就会漏掉这个 2 倍关系;如果只看"高度不对",就会误判成"缩放失败"。


八、静态契约与命令式语义(33 + 9 条断言)

8.1 默认导出是 class 组件

typeof ImageZoom === 'function'   // ✓ class 组件

不是 memo / forwardRef 包装出来的对象(那个坑本系列踩过,属于另一类库)。

8.2 defaultProps 是一个 ImageZoomProps 实例(30 个键)

关键默认值(实测值):

字段默认值
cropWidth / cropHeight / imageWidth / imageHeight100
minScale / maxScale0.6 / 10
panToMove / pinchToZoom / enableDoubleClickZoom / enableCenterFocustrue
enableSwipeDownfalse(swipeDownThreshold = 230)
useNativeDriver / useHardwareTextureAndroidfalse / true
clickDistance / doubleClickInterval / longPressTime / maxOverflow10 / 175 / 800 / 100

运行时命名导出:default、ImageZoomProps、ImageZoomState(export * 把两个 class 也带到了运行时)。

8.3 ⚠️ "方法存在"要分清原型方法与 class 属性箭头函数

这是一个很容易写错断言的地方:

写法挂在哪里
public centerOn(params) {}prototype 上
public reset() {}prototype 上
public resetScale = (): void => {}实例自有属性(原型上是 undefined)

我第一版把 resetScale 当原型方法断言,得到一个假失败。改成同时断言两件事(“不在原型上” + “在实例上”)之后,这条反而变成了一条有价值的记录。

⇒ 断言"某个方法存在"时,先看源码是哪种写法。

8.4 两套驱动方式互不相通

componentDidMount()   { if (this.props.centerOn) this.centerOn(this.props.centerOn); }
componentDidUpdate(p) { if (/* centerOn prop 发生变化 */) this.centerOn(this.props.centerOn); }

⇒ 只有传 centerOn prop 才会有"自动居中/重放"行为;通过 ref 调 centerOn() 不会触发这两者。也就是说"ref 命令式"与"prop 受控"是两套独立的驱动方式,别混着理解。

8.5 reset() 不发 onMove

public reset() {
  this.scale = 1;              this.animatedScale.setValue(1);
  this.positionX = 0;          this.animatedPositionX.setValue(0);
  this.positionY = 0;          this.animatedPositionY.setValue(0);
}
  • 立即生效(setValue,没有动画);
  • 不调用 imageDidMove ⇒ 复位后 onMove 不会回调,页面上的"最近 onMove"会停留在复位前的值。

⚠️ 这一点很容易误判成"复位没生效" —— 实际上画面已经复位了,只是回调没发。

8.6 onMove 是唯一的观测通道

载荷实测:

{"type":"centerOn","positionX":0,"positionY":0,"scale":2,"zoomCurrentDistance":0}

它同时是"命令式调用是否完成"的信号(centerOn 动画结束时回调 type: 'centerOn')。


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

9.1 能力与坑的清单

项说明
安装必须 npm install --ignore-scripts(第 3 节)
判据效果全是 transform ⇒ 布局 dump 无效,必须用像素(第 5 节)
centerOn 的 x/y会被 scale 放大(第 7 节)
reset()立即生效但不发 onMove(8.5)
驱动方式ref 命令式与 centerOn prop 两套独立(8.4)
方法位置centerOn/reset 在原型上,resetScale 在实例上(8.3)

9.2 本次验证的边界

  1. 只在一台模拟器上验证(Pura X View,density 3,1320×2232),没有真机。
  2. 双指捏合缩放(pinchToZoom)没有做:uitest uiInput 不支持多指手势,本轮是用 centerOn() 把 scale 驱动到已知值来验证缩放通道的 ⇒ "捏合手势本身"能否被 RNOH 的 PanResponder 正确解析,未验证。
  3. 单指拖拽没有做定量测量:页面上留了拖拽区,但没做"拖 → 量位移"的定量对比(缩放/平移通道已由命令式方式定量验证)。
  4. 双击缩放、长按、下滑退出(enableSwipeDown / onSwipeDown)、onClick / onDoubleClick、边界回弹(maxOverflow)、horizontalOuterRangeOffset / onDragLeft / responderRelease 均未测。
  5. minScale / maxScale 的实际钳制未测:只断言了默认值是 0.6 / 10,没测"缩放到超界是否被钳住"。
  6. enableCenterFocus(居中吸附)未测。
  7. useNativeDriver: true 未测(默认 false 时动画由 JS 驱动)。
  8. useHardwareTextureAndroid 在鸿蒙上的实际效果未测。
  9. centerOn prop 受控路径未测(本轮全部走 ref 命令式)。
  10. 没有跨平台对照(未在 iOS/Android 上跑同一套用例)。

未改动库代码。


十、常见问题

Q1:为什么 npm install 装不上,还报 rm -rf built 失败?

因为交付包的 package.json 里有 "prepare": "rm -rf built && tsc"。prepare 会在 npm pack 和从 tgz / git+ 安装时自动执行:

  • rm -rf built 把随包发布的 built/(也就是 main 指向的入口)删掉;
  • tsc 解析到的是环境里的全局 TypeScript(消费者装不到包内 pin 的 devDependency),加上 tsconfig.json 缺 rootDir,产物被生成到 built/src/ 下;
  • ⇒ built/index.js 不存在了,npm 以 exit 2 失败。

唯一可行做法:

npm install --ignore-scripts

Q2:npm pack 也失败,正常吗?

正常,同一个原因(pack 也会跑 prepare)。这条路当前不可用。

Q3:为什么用布局 dump 量不出缩放?图片明明变大了。

因为 devecocli ui layout 给的是 Yoga 布局帧,而缩放是加在 Animated.View 上的 transform(渲染层属性)。实测:onMove 报 scale=2、屏幕上蓝边被放大裁掉,但矩形三次完全不变。

⇒ 这个库必须用像素判据(见 Q4)。

Q4:没有双指手势工具,怎么验证缩放?

用命令式方法把 scale 驱动到已知值,再用像素验证:

zoomRef.current.centerOn({x: 0, y: 0, scale: 2, duration: 0});

我让测试图自带蓝色边框(放大后会被裁掉)和正中间的白色横条(高度是精确的 scale 标尺),实测:

调用白条高度结论
reset()57基线
scale=2117 ≈ 2×✓
scale=0.635 ≈ 0.6× + 边缘露黑底✓

Q5:centerOn({x: 100, y: 50, scale: 2}) 的位移为什么是 300 像素而不是 150?

因为 transform 数组顺序是 [{scale}, {translateX}, {translateY}],矩阵从前往后乘 ⇒ 平移量会被 scale 放大。

y: 50 × scale: 2 = 100 vp = 300 物理像素(density 3)。实测白条中心从 984 移到 1284,正好 +300 ✓

Q6:我调了 reset(),但"最近 onMove"还显示旧值,是没生效吗?

生效了。 reset() 用 setValue 立即复位,但不调用 imageDidMove ⇒ 不会触发 onMove 回调,所以显示停留在旧值。以画面为准。

Q7:通过 ref 调 centerOn() 和传 centerOn prop 有什么区别?

两套独立方式:

  • 传 centerOn prop ⇒ componentDidMount 会居中一次,且 componentDidUpdate 在该 prop 变化时重放;
  • ref 调 centerOn() ⇒ 只是立刻执行一次动画,不经过那两个生命周期,也不会因为 prop 变化而重放。

Q8:minScale / maxScale 默认是多少?

0.6 / 10(defaultProps 实测值)。注意最小值小于 1,也就是说默认允许把图缩到比裁剪窗还小(我实测 scale=0.6 时图比窗口小、露出了容器的黑底)。是否钳制未在本轮实测。

Q9:为什么 ImageZoom.prototype.resetScale 是 undefined?

因为源码里它是 class 属性箭头函数(public resetScale = (): void => {}),属于实例自有属性;而 centerOn / reset 是普通方法,挂在 prototype 上。断言"方法存在"时要注意这个区别。

Q10:怎么接入宿主?

npm install "git+https://atomgit.com/oh-react-native/react-native-image-pan-zoom.git#2.1.12-ohos-1.0.0" --ignore-scripts
// metro.config.js —— file: 装法是软链,需要把真实目录加进 watchFolders
watchFolders: [ path.resolve(__dirname, '../react-native-image-pan-zoom') ],
<ImageZoom
  ref={zoomRef}
  cropWidth={300} cropHeight={200}
  imageWidth={300} imageHeight={200}
  panToMove pinchToZoom
  onMove={(p) => console.log(p.type, p.scale, p.positionX, p.positionY)}
>
  <Image source={require('./photo.png')} style={{width: 300, height: 200}} />
</ImageZoom>

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


小结

react-native-image-pan-zoom 是一个纯 JS、零运行时依赖的图片平移缩放组件:23/24 个文件与上游逐字符一致(含全部编译产物),设备侧 45 / 45 断言全过,缩放与位移都经过定量像素验证。

真正值得记下来的是这几条:

  1. "判据"本身要先验证。 我原以为"布局 dump 反映 transform",这一轮用正对照(onMove 报 scale=2 + 屏幕上蓝边被放大裁掉,而矩形纹丝不动)证明它不反映。⇒ 换成像素判据之后,才拿到 57 / 117 / 35 与 300px 位移这些定量结果。 如果不做这个对照,就会写成"缩放没生效"——一个彻底的假结论。("测不到"和"没效果"必须用对照组分开。)

  2. prepare 脚本可以毁掉交付包自己。 这次不是"打不出包",而是 rm -rf built 把 main 指向的入口删掉,再用环境里的 tsc(消费者装不到包内 pin 的版本)重新生成到另一个目录。⇒ 凡是随包发布编译产物的库,看到会动那个目录的 prepare / prepack,就必须先做安装实验,而且一定要在副本上做(它会真删)。

  3. transform 数组的顺序会改变语义。 [{scale}, {translateX}, {translateY}] 让平移被 scale 放大 —— y: 50 实际走了 300 像素。这只有做定量测量才会发现:只检查"有没有动"会漏掉 2 倍关系,只看"高度不对"又会误判成缩放失败。

  4. 断言"方法存在"要分清原型与实例。 public foo() {} 在 prototype 上;public foo = () => {} 在实例上。写错位置就是一个假失败。

  5. 回调类断言必须排在交互之后。 “一键跑全部"很容易把这类断言的顺序搞反 ⇒ 要么标成 informational,要么在协议里明确规定"先交互、后断言”。

  6. "名字不一致"要先查证再定性。 包叫 react-native-image-pan-zoom,上游仓库却叫 react-native-image-zoom —— 看着像填错,但 npm 的 repository 字段证实上游确实如此,不是缺陷。

在这里插入图片描述


本篇用到的库

项内容
三方库react-native-image-pan-zoom(上游 2.1.12 的鸿蒙适配版)
适配仓库https://atomgit.com/oh-react-native/react-native-image-pan-zoom
适配 TAG2.1.12-ohos-1.0.0
需要 HAR / 权限 / ohpm都不需要(纯 JS,零运行时依赖)
库类型纯 JS 手势库:单指拖动、双指捏合、双击缩放的图片查看组件
运行时依赖零(dependencies 字段不存在)
许可证ISC(上游与交付包一致)
上游仓库https://github.com/ascoders/react-native-image-zoom(⚠️ 仓库名与包名不同,已核实)
上游基线npm gitHead = 30a7b4d4c832faebfcdb3c1131869801df56c6e3
宿主工程RNOH084Demo(测试页 rnAppKey = ImagePanZoomTestApp)
安装方式(重要)必须加 --ignore-scripts
# ⚠️ 必须带 --ignore-scripts:否则 prepare 会 `rm -rf built` 并让 main 目标消失
npm install "git+https://atomgit.com/oh-react-native/react-native-image-pan-zoom.git#2.1.12-ohos-1.0.0" --ignore-scripts
// metro.config.js —— file: / 软链装法需要把真实目录加进 watchFolders
watchFolders: [ path.resolve(__dirname, '../react-native-image-pan-zoom') ],
// 用法
import ImageZoom from 'react-native-image-pan-zoom';

const zoomRef = useRef(null);

<ImageZoom
  ref={zoomRef}
  cropWidth={300}
  cropHeight={200}
  imageWidth={300}
  imageHeight={200}
  minScale={0.6}
  maxScale={10}
  panToMove
  pinchToZoom
  onMove={(p) => console.log(p.type, p.scale, p.positionX, p.positionY)}
>
  <Image source={require('./photo.png')} style={{width: 300, height: 200}} />
</ImageZoom>;

// 命令式驱动(不依赖双指手势,可精确到已知值)
zoomRef.current?.centerOn({x: 0, y: 0, scale: 2, duration: 300});
zoomRef.current?.reset();        // 注意:立即生效,但**不会**触发 onMove
# 换页启动测试页(force-stop 不能省,换页参数只在冷启动生效)
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey ImagePanZoomTestApp

验证环境

项版本
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
宿主 HAP 产物entry-default-signed.hap(82,050,332 字节)
本次增量构建assembleHap 4 分 11 秒 / 重编 4 分 15 秒,HAP +65,626 字节;打包资源 13 → 14
验证规模设备侧 45 / 45 断言全部通过;像素判据 4 个状态;prepare 缺陷 3 组安装对照;上游 24 个文件逐字符比对

欢迎加入 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开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐