- 图形学
- 移动开发
- 跨平台
- UI组件
【免费下载链接】react-native-skia
High-performance React Native Graphics using Skia
react-native-skia(@shopify/react-native-skia)除了绘制 Skia 图形,还提供了一项少见的原生能力——对任意 React Native 原生视图“截图”。本文围绕官方文档 snapshot-views.md 展开,讲清makeImageFromView的用法、collapsable={false}这一关键注意事项,并沿 JS 封装层、JSI 宿主函数 到 Android/iOS 截图服务 逐层拆解底层实现,帮助你把“把 RN View 变成可再次绘制的 SkImage”这一能力真正用到项目中(例如做入场动画、分享卡片、离屏合成)。
makeImageFromView:从一个 ref 到一张 SkImage
makeImageFromView接受一个指向原生视图的 ref,返回一个 Promise,成功后 resolve 为SkImage实例。也就是说,快照结果不是 PNG 字节或ImageSource,而是一个可以直接参与 Skia 绘制的图像对象——你可以把它交给<Image>组件、ImageShader、RuntimeShader等任何接受SkImage的 API 继续做混合、滤镜或着色器处理。
从 Image.ts 的源码可以看到它的完整签名:
export const makeImageFromView = < T extends | number | React.Component<unknown, unknown> | React.ComponentClass<unknown> | null, >( viewRef: React.RefObject<T>, callback: | null | ((viewRef: React.RefObject<T>) => Promise<SkImage | null>) = null ) => { ... };两个参数:
viewRef:要快照的视图 ref。在原生平台上,内部通过Platform.findNodeHandle(viewRef.current)解析出 React 的 view tag,再调用Skia.Image.MakeImageFromViewTag(viewTag)交给原生层截图;callback:Web 平台必需。Web 实现会把工作完全委托给这个回调,由调用方自己实现“从 ref 生成 SkImage”的逻辑;如果 Web 上不提供 callback,函数会 reject 并提示 “Callback is required on web in the makeImageFromView function.”。因此原生平台上你可以忽略该参数,Web 平台则必须自行兜底。
官方示例:点击按钮为 View 拍照
以下是官方文档中的完整示例,可直接复制运行:
import { useState, useRef } from "react"; import { View, Text, PixelRatio, StyleSheet, Pressable } from "react-native"; import type { SkImage } from "@shopify/react-native-skia"; import { makeImageFromView, Canvas, Image } from "@shopify/react-native-skia"; const pd = PixelRatio.get(); const Demo = () => { // 为要快照的视图创建 ref const ref = useRef<View>(null); // 用 state 保存快照结果 const [image, setImage] = useState<SkImage | null>(null); const onPress = async () => { // 对视图进行快照 const snapshot = await makeImageFromView(ref); setImage(snapshot); }; return ( <View style={{ flex: 1 }}> <Pressable onPress={onPress}> <View ref={ref} // 这里是关键:collapsable={false} collapsable={false} style={{ backgroundColor: "cyan", flex: 1 }}> <Text>This is a React Native View</Text> </View> </Pressable> { image && ( <Canvas style={StyleSheet.absoluteFill}> <Image image={image} x={0} y={0} width={image.width() / pd} height={image.height() / pd} /> </Canvas> ) } </View> ) };这个示例包含三个必须注意的实战细节:
collapsable={false}不能省。文档明确警告:React Native 会把没有子节点交互、被认为“可折叠”的空 View 直接优化掉(不创建对应原生节点)。一旦根视图被优化移除,makeImageFromView会崩溃或返回错误结果。所以被快照视图的根部应显式设置collapsable={false}。SkImage的尺寸是物理像素。快照得到的SkImage分辨率为视图的物理像素尺寸(width × 屏幕密度)。用<Image>组件按逻辑点渲染时需要除以PixelRatio.get(),示例中image.width() / pd就是这么处理的;如果忘了缩放,图片会放大pd倍。- 结果是异步的。截图发生在平台主线程,Promise 可能跨越一帧甚至更久才 resolve,建议在
await期间避免移动/卸载被快照的视图。
JSI 层:MakeImageFromViewTag 的 Promise 链路
原生端入口是 JsiSkImageFactory.h 中注册为 JS 宿主方法的MakeImageFromViewTag:
JSI_HOST_FUNCTION(MakeImageFromViewTag) { auto viewTag = arguments[0].asNumber(); auto context = getContext(); return RNJsi::JsiPromises::createPromiseAsJSIValue( runtime, context = std::move(context), viewTag -> void { context->makeViewScreenshot( viewTag, &runtime, context = std::move(context), promise = std::move(promise) { context->runOnJavascriptThread([&runtime, ...]() { if (result == nullptr) { promise->reject("Failed to create image from view tag"); return; } promise->resolve(...); }); }); }); }这段代码揭示了失败语义:截图回调如果带回nullptr(视图不存在、尺寸为 0 等),Promise 会以 “Failed to create image from view tag” reject。这也与 JS 层的 “Invalid view tag” reject 形成两层防护。
makeViewScreenshot的默认实现在 RNSkPlatformContext.h:它在主线程上执行takeScreenshotFromViewTag(viewTag),完成后通过回调把sk_sp<SkImage>传回,再切回 JS 线程 resolve Promise。因此该 API 只能在视图已挂载并布局完成(宽高 > 0)后调用,否则会拿到 null。
Android 实现:ViewScreenshotService 递归重绘视图树
Android 侧的实际工作由 ViewScreenshotService.java 完成,PlatformContext.java 只是把 viewTag 转发进来。读这个文件能看到不少“截图保真”的工程细节:
- 入口:
makeViewScreenshotFromTag通过UIManagerHelper.getUIManagerForReactTag+uiManager.resolveView(tag)找到 View;视图解析失败或宽高 ≤ 0 时直接返回 null(对应上文的 Promise reject)。 - 位图格式:以视图宽高创建
ARGB_8888位图,画布先translate(-view.getLeft(), -view.getTop())再递归绘制,Paint 开启抗锯齿、滤镜与抖动(createPaint())。 - 递归渲染
renderViewToCanvas:逐层应用view.getMatrix()与位置/内边距/滚动偏移变换(applyTransformations),并按父级透明度 × 当前 alpha累积透明度绘制,保证带opacity的视图树截图结果与原样一致。 - ScrollView 视口裁剪:对
ScrollView/HorizontalScrollView显式clipRect到scrollX/scrollY + 宽高,防止超大子视图(例如宽大的 Skia<Canvas>TextureView)在截图中溢出视口。 - overflow 裁剪复刻:因为不会调用
dispatchDraw()(TextureView/SurfaceView 需要特殊处理,手动遍历子视图),代码显式复刻了ReactViewGroup.dispatchDraw()中overflow: hidden的裁剪逻辑(BackgroundStyleApplicator.clipToPaddingBox)。 - TextureView:用
getBitmap(...)取出其纹理内容后drawBitmap。 - SurfaceView:Android N 以上使用
PixelCopy.request(带 5 秒超时),失败时回退到 drawing cache。
这些细节决定了“哪些内容能被截到”:普通 RN 视图树、背景、变换、透明度基本都保真;而独立 Surface(SurfaceView)依赖 PixelCopy 结果,若被系统占用可能超时回退。
Apple 实现:ViewScreenshotService
Apple 端同样有一个独立服务,接口见 ViewScreenshotService.h:
@interface ViewScreenshotService : NSObject { } - (instancetype)initWithUiManager:(RCTUIManager *)uiManager; - (sk_sp<SkImage>)screenshotOfViewWithTag:(NSNumber *)viewTag; @end它持有RCTUIManager并按 view tag 生成sk_sp<SkImage>,与 Android 一样属于平台各自的takeScreenshotFromViewTag实现(macOS 分支通过 AppKit 导入头文件)。具体绘制逻辑在对应的.mm实现中,从源码结构看其流程与 Android 版本一一对应:按 tag 解析视图、离屏渲染、转换为 SkImage。
进阶玩法:把快照丢进 RuntimeShader
仓库自带的 Snapshot 示例 展示了快照图像的二次利用:先await makeImageFromView(viewRef)得到SkImage,再传入Skia.RuntimeEffect.Make创建的运行时着色器作为uniform shader image,实现正弦波纹扭曲的“拍照变形”效果:
const source = Skia.RuntimeEffect.Make(` uniform shader image; uniform float r; half4 main(float2 xy) { xy.x += sin(xy.y / r) * 4; return image.eval(xy).rgba; }`)!;同一文件中也可以看到collapsable={false}在真实项目里的用法(<View ref={viewRef} ... collapsable={false}>),快照结果被存入 reanimated 的useSharedValue里驱动动画。这说明快照不仅用于静态展示,也是做“截图 → 动画过渡 → 回到真实视图”这类无闪屏过渡的常用手段。测试入口可在 Tests.tsx 中找到对应的引用,示例路由入口为 API/Snapshot.tsx。
适用前提与常见失败点小结
| 场景 | 结果 | 依据 |
|---|---|---|
视图被 RN 折叠优化掉(未设collapsable={false}) | 崩溃或错误结果 | 官方文档警告 |
| Web 平台未提供 callback | reject:“Callback is required on web...” | Image.ts |
| ref 解析不出合法 view tag | reject:“Invalid view tag” | Image.ts |
| 视图宽高为 0 或解析失败 | Promise reject:“Failed to create image from view tag” | JsiSkImageFactory.h、ViewScreenshotService.java |
| SurfaceView 像素拷贝超时(>5s) | 回退 drawing cache,可能拿到旧帧 | ViewScreenshotService.java |
总结一下:makeImageFromView的使用成本极低——一个 ref 加一句await,得到的SkImage可以无缝进入 Skia 的整套绘制体系;工程上需要记住的只有三件事:根部视图加collapsable={false}、渲染时按PixelRatio缩放、Web 平台必须自行提供 callback。结合源码可以确认,这条 API 在 Android/iOS 上都是“主线程离屏重绘视图树 → 转 SkImage → 回 JS 线程 resolve”的可靠链路,且对变换、透明度、滚动裁剪、TextureView/SurfaceView 都做了专门处理,是 react-native-skia 把原生视图与 Skia 图形世界打通的关键桥梁。
- 图形学
- 移动开发
- 跨平台
- UI组件
【免费下载链接】react-native-skia
High-performance React Native Graphics using Skia
相关推荐
MARL-code-pytorch进阶:如何修改代码支持自定义多智能体环境?
MARL code pytorch进阶:如何修改代码支持自定义多智能体环境? MARL code pytorch是一个简洁的PyTorch实现的多智能体强化学习
人工智能强化学习为什么选择AI-ModelScope/vit-base-patch16-224?对比CNN模型的5大优势
为什么选择AI ModelScope/vit base patch16 224?对比CNN模型的5大优势 在计算机视觉领域,传统的卷积神经网络(CNN)长期以来
TiXL 快照控制视图(Snapshot Control View)完全指南:参数级快照捕获、控制器索引网格与 Section 分组实战
TiXL 快照控制视图(Snapshot Control View)完全指南:参数级快照捕获、控制器索引网格与 Section 分组实战 快照(Snapshot
音视频图形学桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考