鸿蒙 React Native 图片缩放组件接入与踩坑
在鸿蒙环境下做 React Native 应用,图片预览是最常见的高频场景之一。真正麻烦的是手势缩放、多图切换和下滑关闭这些交互,如果组件依赖原生代码,就得针对鸿蒙重新适配。react-native-image-zoom-viewer 是一个纯 JS 实现的图片缩放浏览组件,不依赖任何原生链接,在鸿蒙上可以直接运行。它内部依赖的 react-native-image-pan-zoom 也是纯 JS 手势计算库,所以整个方案可以做到开箱即用。安装很简单,直接使用 yarn 添加依赖即可,3.0.1 版本就是纯 JS 库,不需要配置原生链接。
yarn add react-native-image-zoom-viewer
基础用法是把 ImageViewer 放在 Modal 中,通过 imageUrls 传入图片数组,然后设置 enableSwipeDown 和 onSwipeDown 来实现下滑关闭。onClick 也可以用来在轻点时退出预览。下面是一个最简单的实现。
import React, { useState } from 'react';
import { Modal, View, Text, TouchableOpacity } from 'react-native';
import ImageViewer from 'react-native-image-zoom-viewer';
const images = [
{ url: 'https://picsum.photos/id/1/800/600', width: 800, height: 600 },
{ url: 'https://picsum.photos/id/10/1200/800', width: 1200, height: 800 },
];
export default function Gallery() {
const = useState(false);
return (
<Modal visible={visible} transparent onRequestClose={() => setVisible(false)}>
<ImageViewer
imageUrls={images}
index={0}
onClick={() => setVisible(false)}
enableSwipeDown
onSwipeDown={() => setVisible(false)}
/>
</Modal>
);
}
实际项目里通常不会只用默认样式,还需要自定义头部信息、加载状态,以及监听当前预览到第几张。ImageViewer 提供了 renderHeader 和 loadingRender 这类接口,配合 onChange 可以拿到当前索引。下面这个完整示例展示了如何实现一个带关闭按钮、页码显示和加载动画的图片浏览器。
import React, { useState } from 'react';
import { Modal, View, Text, TouchableOpacity, ActivityIndicator } from 'react-native';
import ImageViewer from 'react-native-image-zoom-viewer';
const images = [
{ url: 'https://picsum.photos/id/1/800/600' },
{ url: 'https://picsum.photos/id/10/1200/800' },
{ url: 'https://picsum.photos/id/100/600/900' },
];
export default function ImageViewerDemo() {
const = useState(false);
const = useState(0);
return (
<Modal visible={visible} transparent animationType="fade">
<ImageViewer
imageUrls={images}
index={currentIndex}
enableSwipeDown
enablePreload
saveToLocalByLongPress
backgroundColor="#000"
onClick={() => setVisible(false)}
onSwipeDown={() => setVisible(false)}
onChange={(index) => console.log('切换到第', index! + 1, '张')}
renderHeader={(currentIndex) => (
<View style={{
position: 'absolute', top: 0, left: 0, right: 0,
paddingTop: 50, paddingHorizontal: 16, flexDirection: 'row',
justifyContent: 'space-between', zIndex: 100,
}}>
<TouchableOpacity onPress={() => setVisible(false)}
style={{ backgroundColor: 'rgba(0,0,0,0.5)', borderRadius: 20, padding: 8 }}>
<Text style={{ color: '#fff' }}>✕ 关闭</Text>
</TouchableOpacity>
<Text style={{ color: '#fff' }}>
{(currentIndex || 0) + 1} / {images.length}
</Text>
</View>
)}
loadingRender={() => (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size="large" color="#fff" />
</View>
)}
/>
</Modal>
);
}
接入过程中有几个容易踩的坑,这里整理一下。
第一个是图片数组格式。imageUrls 不接受 string[],必须使用对象数组。很多新手直接传入 url 字符串列表,结果图片不显示。
// 错误:imageUrls 不接受 string[]
const images = ['url1', 'url2'];
// 正确:必须用对象数组
const images = [
{ url: 'https://example.com/image.jpg' },
{ url: '', props: { source: require('./local.png') } },
];
第二个是本地图片的传入方式。如果要展示本地图片,需要通过 props.source 来传,而不能直接塞一个 path 进去。url 可以留空,props.source 里用 require 加载资源。
const images = [{
url: '',
props: {
source: require('./assets/photo.png'),
},
}];
第三个是 index 变化不触发重渲染。ImageViewer 内部使用 getDerivedStateFromProps 检测 index prop 的变化,但在某些场景下父组件直接修改 index 可能不会让视图跳转到对应图片。一个可靠的解决方法是给 ImageViewer 加 key={index},强制重建组件。
// 可能不生效
export default function Gallery() {
const = useState(0);
return <ImageViewer imageUrls={images} index={index} />;
}
// 使用 key 强制重建
const = useState(0);
<ImageViewer key={index} imageUrls={images} index={index} />
第四个是 onClick 和 onSwipeDown 可能冲突。同时开启 enableSwipeDown 并设置 onClick 后,轻点屏幕时可能两个回调都会被触发。尽量避免同时使用,或者在 onClick 里加防抖或手势判断。
第五个是长按保存菜单。saveToLocalByLongPress 在鸿蒙上长按图片时会弹出一个内置的 JS 菜单,包含“保存图片”和“取消”。这个菜单本身可以正常工作,但保存动作依赖 CameraRoll 模块,如果工程里没有安装这个模块,保存会静默失败,不会报错也不会弹提示。
第六个是图片宽高信息。imageUrls 里的对象可以带 width 和 height,建议尽量提供。提供后组件可以立即按尺寸渲染,不用等图片加载完再自动测量;不提供的话组件会自己去拉取图片尺寸,首次渲染会有明显延迟。
const images = [
{ url: '...' }, // 自动获取尺寸,稍慢
{ url: '...', width: 800, height: 600 }, // 指定尺寸,立即渲染
];
性能方面,可以交给几个配置项来处理。开启 enablePreload 后,库会预加载当前图片前后各一张,减少切换时的等待。对于图片数量较多的场景,建议只在 Modal 打开时才渲染 ImageViewer,关闭时直接销毁,避免在后台做无谓的视图计算。网络图片也应尽量先压缩到合适尺寸,最大宽度控制在 2048px 左右,既能保证清晰度,又不会让内存和带宽压力太大。
整体来看,react-native-image-zoom-viewer 是目前鸿蒙 RN 项目里比较值得推荐的图片预览方案。它不依赖原生代码,功能覆盖了双指缩放、双击放大、多图切换、下滑关闭和长按保存等核心交互,接入成本低。真正需要留神的只有数据格式、状态同步和手势冲突这几个点,处理好了就能稳定用起来。
Re: 鸿蒙 React Native 图片缩放组件接入与踩坑
看到楼主分享鸿蒙上接入 React Native 图片缩放组件的经验,很实用。特别是这种纯 JS 实现的方案,在鸿蒙这种需要重新适配原生模块的环境下,确实能省掉不少麻烦。我之前也折腾过类似的图片预览,native 依赖在鸿蒙上经常要各种改配置,像这种开箱即用的库就很舒服。 想请教一下楼主,在鸿蒙上实际运行的时候,图片数量多或者单图分辨率比较大的情况下,手势缩放和滑动的流畅度怎么样?有没有遇到过内存占用偏高或者图片加载白屏的问题?另外,`renderHeader` 里的 `currentIndex` 这个参数看起来有点绕,楼主实际用的时候是怎么处理的?Re: 鸿蒙 React Native 图片缩放组件接入与踩坑
感谢楼主分享,这个纯 JS 方案对鸿蒙适配确实省心不少。我最近也在折腾 RN 的图片预览,之前用原生组件在鸿蒙上各种兼容问题,看到这个库直接跑通有点惊喜。下滑关闭和预加载这两个点很实用,回头我把长按保存也加上试试。另外想问下楼主,多图快速滑动时内存表现怎么样?有没有做缩略图或懒加载的优化?Re: 鸿蒙 React Native 图片缩放组件接入与踩坑
这个组件确实省心,纯 JS 在鸿蒙上不用折腾原生适配,直接就能跑。下滑关闭和手势缩放这些交互在真机上体验很顺滑,renderHeader 自定义也灵活,我们项目里就是用它做的图片预览,踩坑不多。感谢分享!
页:
[1]