☰
react-native-skia 视图快照实战:用 makeImageFromView 将任意原生 View 捕获为 SkImage
2026/9/25 3:27:11 网站建设 项目流程
  • 图形学
  • 移动开发
  • 跨平台
  • UI组件

【免费下载链接】react-native-skia

High-performance React Native Graphics using Skia

项目地址:https://gitcode.com/gh_mirrors/re/react-native-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> ) };

这个示例包含三个必须注意的实战细节:

  1. collapsable={false}不能省。文档明确警告:React Native 会把没有子节点交互、被认为“可折叠”的空 View 直接优化掉(不创建对应原生节点)。一旦根视图被优化移除,makeImageFromView会崩溃或返回错误结果。所以被快照视图的根部应显式设置collapsable={false}。
  2. SkImage的尺寸是物理像素。快照得到的SkImage分辨率为视图的物理像素尺寸(width × 屏幕密度)。用<Image>组件按逻辑点渲染时需要除以PixelRatio.get(),示例中image.width() / pd就是这么处理的;如果忘了缩放,图片会放大pd倍。
  3. 结果是异步的。截图发生在平台主线程,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 平台未提供 callbackreject:“Callback is required on web...”Image.ts
ref 解析不出合法 view tagreject:“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

项目地址:https://gitcode.com/gh_mirrors/re/react-native-skia
点击查看免费下载

相关推荐

上一篇:react-native-router-flux 测试覆盖率报告:趋势与改进
下一篇:RAWGraphs-app设计决策案例:从用户研究到界面实现记录

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询