React Native Skia 入门 – wiki基地


拥抱高性能图形:React Native Skia 入门详解

在移动应用开发中,图形和动画是提升用户体验的关键。传统的 React Native 开发虽然方便快捷,但在处理复杂、动态或高性能的 2D 图形时,常常会遇到性能瓶颈或实现困难。例如,绘制复杂的图表、自定义动画、图像滤镜或游戏界面等,使用标准的 View 组件或 SVG 库可能会力不从心。

这时,我们需要一个更底层的、更强大的图形渲染引擎。而 React Native Skia (简称 RN Skia) 正是为此而生。它将 Google 开源的高性能 2D 图形库 Skia 引入到 React Native 世界,让我们能够以声明式的方式,利用 GPU 的强大能力,创建流畅、炫酷的图形界面。

本文将带领你深入了解 React Native Skia,从它是什么、为什么使用它,到如何安装、基本用法,再到更复杂的图形绘制技巧,助你轻松踏入高性能图形开发的大门。

第一部分:认识 React Native Skia

1. Skia 是什么?

在你理解 React Native Skia 之前,先了解一下 Skia 本身很有必要。Skia 是一个由 Google 开发并维护的开源 2D 图形库。它是一个跨平台的 C++ 库,为各种硬件和软件后端提供高效的、基于 CPU 和 GPU 的渲染。

Skia 是许多知名产品背后的图形引擎,例如:

  • Google Chrome: 网页内容的渲染。
  • Android: 整个操作系统的 UI 渲染。
  • Flutter: 跨平台 UI 框架的渲染。
  • Firefox: 部分图形渲染。

Skia 以其高性能、丰富的功能集和强大的兼容性而闻名。它支持各种图形原语、颜色、渐变、路径、文本、图像、滤镜、混合模式等,并且能够充分利用现代 GPU 的硬件加速能力。

2. React Native Skia 是什么?

React Native Skia 是 Shopify 团队开发的一个库,它为 React Native 提供了 Skia 图形引擎的绑定。简单来说,它是在 React Native 和底层的 Skia C++ 库之间搭建了一座桥梁,让你能够在 JavaScript/TypeScript 中,使用 React 的声明式范式来控制 Skia 进行图形绘制。

与传统的基于 View 组件的 RN 绘图(如 <View style={{ borderColor: 'red', borderWidth: 2 }} /> 来绘制矩形边框)不同,RN Skia 是直接在屏幕上的一块“画布” (<Canvas>) 上绘制像素。这种方式更接近于原生的图形 API 或 WebGL,但 RN Skia 通过声明式的组件抽象,极大地简化了开发复杂图形的过程。

你可以想象 <Canvas> 是一个空白画板,而你在 <Canvas> 内使用的各种 RN Skia 组件(如 <Circle>, <Rect>, <Path>, <Text> 等)就像是画笔和颜料,你通过组合这些组件,描述你想要绘制的图形,RN Skia 会负责将这些描述转化为底层的 Skia 绘制指令,最终渲染到屏幕上。

3. 为什么选择 React Native Skia?

使用 React Native Skia 相较于其他 RN 图形方案,有以下显著优势:

  • 卓越的性能: Skia 利用 GPU 进行硬件加速,对于复杂的图形、动画和大量的绘制操作,其性能通常远超 CPU 渲染或 WebView 嵌入的方案。这使得在 RN 中实现流畅的图表、复杂的粒子效果或高帧率动画成为可能。
  • 丰富的功能集: RN Skia 暴露了 Skia 大部分强大的功能,包括复杂的路径操作、高级混合模式、自定义着色器 (Shaders)、图像滤镜、精确的文本布局和渲染等。这些功能是标准 RN 组件或大多数 SVG 库难以提供的。
  • 声明式 API: 虽然底层是强大的 C++ 库,但 RN Skia 提供的是基于 React 组件的声明式 API。这意味着你可以像构建普通 RN UI 一样来构建你的图形界面,利用组件化、props、state 和 hooks 等 React 特性,这极大地提高了开发效率和代码可维护性。
  • 跨平台一致性: Skia 本身就是跨平台的,RN Skia 很好地继承了这一点。你在 iOS 和 Android 上看到的图形渲染效果会非常一致,这避免了许多跨平台适配的麻烦。
  • 与 React Native 生态集成: RN Skia 可以与 React Native 的其他库很好地集成,特别是 react-native-reanimated。通过 Reanimated,你可以轻松地为 Skia 绘制的图形元素添加高性能的、运行在 UI 线程的动画,实现复杂的交互和视觉效果。

与其他 RN 图形方案的比较:

  • 标准的 React Native View 组件: 适合简单的矩形、边框、背景色、阴影等。不适合绘制曲线、复杂路径、渐变填充、高级滤镜等。性能有限。
  • react-native-svg: 基于 SVG 标准,适合绘制静态的矢量图形,如图标、简单的图表。对于动态变化、大量元素或需要像素级操作的场景,性能和功能有限。
  • 基于 WebView 或 <Canvas> 组件的方案 (如 react-native-canvas): 通常是将 Web 的 Canvas API 嵌入到 RN 中。性能受限于 WebView 渲染,且 API 是命令式的,与 React 的声明式范式不太契合。

总而言之,如果你需要在 React Native 应用中实现高性能、复杂、动态或高度定制化的 2D 图形,React Native Skia 是目前最强大和最推荐的方案。

第二部分:React Native Skia 入门实践

1. 环境准备与安装

在开始使用 RN Skia 之前,你需要确保你的 React Native 项目环境是健全的。RN Skia 依赖于 react-native-reanimatedreact-native-gesture-handler(尽管后者不是必须的,但通常与动画和交互一起使用),所以你需要先安装这些库。

假设你已经有一个 React Native 项目(使用 Expo 或 React Native CLI 创建均可),打开终端,导航到你的项目根目录,然后执行以下命令安装必要的库:

“`bash

使用 yarn

yarn add @shopify/react-native-skia react-native-reanimated react-native-gesture-handler

或者使用 npm

npm install @shopify/react-native-skia react-native-reanimated react-native-gesture-handler
“`

安装完成后,你可能需要根据 react-native-reanimatedreact-native-gesture-handler 的文档进行一些额外的配置(尤其是对于 React Native CLI 项目),例如在 MainActivity.java (Android) 或 Podfile (iOS) 中进行设置。Expo 用户通常不需要手动链接。

对于 React Native CLI 项目,在安装依赖后,通常需要运行 pod install 进入 ios 目录:

bash
cd ios && pod install && cd ..

完成安装和配置后,启动你的开发服务器:

“`bash

使用 yarn

yarn start

或者使用 npm

npm start
“`

2. 你的第一个 Skia 画布

RN Skia 的一切绘制都发生在 <Canvas> 组件内部。<Canvas> 组件需要指定其宽度和高度,它充当了 Skia 绘图的容器。

让我们创建一个简单的组件来放置我们的画布:

“`jsx
import React from ‘react’;
import { View, StyleSheet } from ‘react-native’;
import { Canvas, Circle, rrect, rect, LinearGradient, vec, Paint, CircleProps } from ‘@shopify/react-native-skia’;

const SkiaBasics = () => {
const canvasWidth = 300;
const canvasHeight = 300;

return (

{/ 我们的 Skia 画布 /}

{/ 绘制内容将放在这里 /}
{/ 例如,绘制一个蓝色的圆 /}

  </Canvas>
</View>

);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: ‘center’,
alignItems: ‘center’,
backgroundColor: ‘#f0f0f0’,
},
});

export default SkiaBasics;
“`

SkiaBasics 组件导入到你的应用主文件(如 App.jsApp.tsx)并渲染它。你应该能在屏幕中央看到一个蓝色的圆,位于一个 300×300 像素的画布中。

解释:

  • import { Canvas, Circle } from '@shopify/react-native-skia';: 导入核心的 Canvas 组件和用于绘制圆的 Circle 组件。
  • <Canvas style={{ width: canvasWidth, height: canvasHeight }}>: 创建一个 Canvas 区域,并使用 React Native 的 style prop 设置其尺寸。Canvas 内部的坐标系原点 (0, 0) 默认位于其左上角。
  • <Circle cx={...} cy={...} r={...} color="..." />: 在 Canvas 内部绘制一个圆。cxcy 指定圆心的坐标,r 指定半径。color prop 直接设置填充颜色。

3. 基本图形绘制 (Primitives)

RN Skia 提供了一系列组件来绘制基本的图形:

  • <Circle>: 绘制圆 (cx, cy, r, color, paint).
  • <Rect>: 绘制矩形 (x, y, width, height, color, paint).
  • <RRect>: 绘制圆角矩形 (rect, rx, ry, color, paint). 需要先创建矩形对象 (Skia.Rect(...)rect(...))。
  • <Line>: 绘制直线 (p1, p2, color, paint). 需要使用向量对象 (Skia.vec(...)vec(...)) 表示点。
  • <Path>: 绘制任意复杂的路径。这是最强大的基础图形,可以使用 SVG 路径字符串或 Skia 的路径构建 API (Skia.Path.createFromSVG(...), path().moveTo().lineTo().quadTo()...).
  • <Oval>: 绘制椭圆 (rect, color, paint).
  • <Vertices>: 绘制由顶点定义的图形,常用于绘制三角形、多边形。

让我们在画布上添加更多形状:

“`jsx
import React from ‘react’;
import { View, StyleSheet } from ‘react-native’;
// 导入更多组件和 Skia 工具函数
import {
Canvas, Circle, Rect, RRect, Line, Path, Oval,
vec, rect, rrect,
Skia // Skia 对象包含了创建各种 Skia 对象的工具
} from ‘@shopify/react-native-skia’;

const SkiaPrimitives = () => {
const canvasWidth = 300;
const canvasHeight = 400; // 增加高度以便容纳更多图形

// 创建一个圆角矩形的对象
const rectangle = rect(20, 20, 100, 80); // x, y, width, height
const roundRect = rrect(rectangle, 15, 15); // rectangle, corner radius x, corner radius y

// 创建一个路径对象 (例如,一个简单的三角形)
const trianglePath = Skia.Path.Parse(‘M 150 120 L 250 120 L 200 180 Z’); // SVG path string

return (

    {/* 绘制一个圆 */}
    <Circle cx={canvasWidth / 2} cy={50} r={40} color="rgba(255, 0, 0, 0.5)" /> {/* 半透明红色 */}

    {/* 绘制一个矩形 */}
    <Rect x={20} y={120} width={100} height={80} color="green" />

    {/* 绘制一个圆角矩形 */}
    <RRect rect={roundRect} color="purple" />

    {/* 绘制一条直线 */}
    <Line p1={vec(150, 220)} p2={vec(250, 280)} color="orange" />

    {/* 绘制一个路径 (三角形) */}
    <Path path={trianglePath} color="teal" />

    {/* 绘制一个椭圆 */}
    <Oval rect={rect(50, 300, 200, 80)} color="brown" />

  </Canvas>
</View>

);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: ‘center’,
alignItems: ‘center’,
backgroundColor: ‘#f0f0f0’,
},
});

export default SkiaPrimitives;

“`

解释:

  • 我们导入了更多的组件 (Rect, RRect, Line, Path, Oval) 和一些辅助函数 (vec, rect, rrect) 以及核心的 Skia 对象。
  • vec(x, y) 创建一个表示二维向量或点的对象。
  • rect(x, y, width, height) 创建一个表示矩形的对象。
  • rrect(rect, rx, ry) 创建一个表示圆角矩形的对象,基于一个普通矩形对象和圆角半径。
  • Skia.Path.Parse('...') 从 SVG 路径字符串创建一个 Skia Path 对象。SVG 路径是一种标准格式,用于描述复杂的形状(直线、曲线、弧线等)。学习 SVG 路径语法对于使用 <Path> 组件非常有帮助。
  • 每个图形组件都接收 color prop 来设置填充颜色。颜色可以是 CSS 颜色字符串(如 "red", "blue"),十六进制字符串(如 "#FF0000", "#FF000080"),或使用 Skia.Color() 函数创建的颜色对象。

4. 使用 Paint 进行样式控制

在前面的例子中,我们直接使用 color prop 设置了图形的颜色。但这只是最基本的用法。Skia 使用一个叫做 Paint 的概念来控制如何绘制图形,包括颜色、描边样式、渐变、图案、滤镜、混合模式等。

Paint 可以被认为是“画笔”或“样式”。你可以创建不同的 Paint 对象,然后将它们应用到图形组件上。一个 Paint 对象可以控制:

  • style: PaintStyle.Fill (填充), PaintStyle.Stroke (描边).
  • color: 填充或描边的颜色。
  • strokeWidth: 描边宽度 (当 styleStroke 时).
  • strokeJoin: 描边连接处样式 (Miter, Round, Bevel).
  • strokeCap: 描边端点样式 (Butt, Round, Square).
  • antiAlias: 是否开启抗锯齿 (通常保持 true).
  • shader: 用于填充的渐变、图案或自定义效果。
  • maskFilter: 用于模糊、浮雕等效果。
  • colorFilter: 用于改变颜色。
  • blendMode: 如何与下方内容混合。

一个图形组件(如 <Circle>, <Rect>, <Path>) 可以接收一个 paint prop,其值是一个或多个 <Paint> 组件。<Paint> 组件不会单独渲染任何东西,它只定义绘制的样式。

“`jsx
import React from ‘react’;
import { View, StyleSheet } from ‘react-native’;
import { Canvas, Rect, Circle, Path, Paint, useValue, runTiming, rect, Skia, vec } from ‘@shopify/react-native-skia’;
import { useDerivedValue } from ‘react-native-reanimated’;

const SkiaPaintStyles = () => {
const canvasWidth = 300;
const canvasHeight = 400;

// 描边 Paint
const strokePaint = (

);

// 填充 Paint
const fillPaint = (

);

// 同时使用描边和填充,需要两个 Paint 组件
const strokeAndFillPaint = (
<>



);

// 复杂的路径
const complexPath = Skia.Path.Parse(‘M 50 250 Q 150 200 250 250 T 450 250’); // 使用二次贝塞尔曲线

return (

    {/* 使用填充 Paint 绘制矩形 */}
    <Rect x={20} y={20} width={100} height={80}>
      {fillPaint}
    </Rect>

    {/* 使用描边 Paint 绘制圆 */}
    <Circle cx={canvasWidth - 70} cy={60} r={40}>
      {strokePaint}
    </Circle>

    {/* 同时使用描边和填充绘制另一个矩形 */}
    <Rect x={20} y={120} width={100} height={80}>
      {strokeAndFillPaint}
    </Rect>

    {/* 使用描边绘制路径 */}
    <Path path={complexPath}>
      {strokePaint} {/* 可以复用同一个 Paint 对象 */}
    </Path>

    {/* 使用渐变填充 */}
    <Rect x={20} y={300} width={260} height={80}>
      <Paint style="fill">
        <Skia.RuntimeShader source="uniform vec4 color1; uniform vec4 color2; void main() { SkijaCoordinate transform = skija.local; vec4 c = mix(color1, color2, transform.x / 260.0); sk_FragColor = c; }" uniforms={{ color1: Skia.Color("cyan"), color2: Skia.Color("magenta") }} />
         {/* 注意:这里的 RuntimeShader 是更高级的用法,一个简单的线性渐变可以使用 LinearGradient 组件 */}
         {/* 简单线性渐变示例 (需要导入 LinearGradient, vec): */}
         {/*
         <LinearGradient
            start={vec(20, 300)}
            end={vec(280, 380)}
            colors={[Skia.Color("cyan"), Skia.Color("magenta")]}
          />
         */}
      </Paint>
    </Rect>


  </Canvas>
</View>

);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: ‘center’,
alignItems: ‘center’,
backgroundColor: ‘#f0f0f0’,
},
});

export default SkiaPaintStyles;

“`

解释:

  • 我们创建了 <Paint> 组件,并在其 style prop 中指定是 fill 还是 stroke
  • <Paint> 组件作为图形组件(如 <Rect>, <Circle>, <Path>) 的子元素来应用样式。
  • 一个图形组件可以有多个 <Paint> 子元素,它们会按照声明的顺序应用。通常先是填充,然后是描边。
  • 我们展示了如何设置描边的宽度 (strokeWidth) 和样式 (strokeJoin, strokeCap).
  • 渐变 (LinearGradient, RadialGradient) 和着色器 (RuntimeShader) 是更高级的 Paint 效果,它们作为 Paint 的子组件来定义填充或描边的方式,而不是简单的颜色。上面示例中使用了 RuntimeShader 来演示自定义着色器,但对于常见的线性渐变,使用 <LinearGradient> 组件更方便。

5. 变换 (Transformations)

你可以对图形或一组图形应用变换,包括平移 (translate)、旋转 (rotate)、缩放 (scale) 和斜切 (skew)。这些变换可以通过 <Group> 组件或直接在图形组件上通过 transform prop 实现。

<Group> 组件是一个容器,它里面的所有子元素都会应用相同的变换。这对于对多个图形进行整体操作非常有用。

“`jsx
import React from ‘react’;
import { View, StyleSheet } from ‘react-native’;
import { Canvas, Rect, Group, Paint, Skia } from ‘@shopify/react-native-skia’;
import { useSharedValue, useAnimatedStyle, withTiming, repeat, interpolate } from ‘react-native-reanimated’; // 用于动画

const SkiaTransformations = () => {
const canvasWidth = 300;
const canvasHeight = 400;

// 使用 Reanimated 创建一个共享值用于控制旋转角度
const rotation = useSharedValue(0);

// 启动一个重复的旋转动画
React.useEffect(() => {
rotation.value = repeat(withTiming(1, { duration: 3000 }), -1, false); // 旋转到 1 圈 (360度), 持续 3秒, 无限重复
}, []);

// 使用 useDerivedValue 计算旋转角度(弧度转度)
const rotatedDegrees = useDerivedValue(() => {
return interpolate(rotation.value, [0, 1], [0, 360]); // 将 0-1 的范围映射到 0-360 度
}, [rotation]);

return (

    {/* 绘制一个普通矩形作为参考 */}
    <Rect x={50} y={50} width={80} height={60} color="gray" />

    {/* 对一个矩形应用平移和缩放 */}
    <Rect
      x={50} y={150} width={80} height={60}
      color="orange"
      transform={[{ translateX: 50 }, { translateY: 20 }, { scale: 1.5 }]} // 先平移,后缩放
    />

    {/* 对一组图形应用旋转 */}
    <Group
      origin={{ x: 150, y: 300 }} // 旋转中心
      transform={[{ rotate: rotation.value * Math.PI * 2 }]} // 旋转角度,Reanimated 共享值,需要弧度
    >
      {/* 旋转中心为 Group 的 origin prop */}
      <Rect x={120} y={270} width={60} height={30} color="cyan" />
      <Circle cx={150} cy={320} r={15} color="magenta" />
    </Group>

  </Canvas>
</View>

);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: ‘center’,
alignItems: ‘center’,
backgroundColor: ‘#f0f0f0’,
},
});

export default SkiaTransformations;
“`

解释:

  • transform prop 接收一个数组,数组中的每个元素是一个变换对象,如 { translateX: value }, { translateY: value }, { scale: value }, { rotate: value_in_radians }. 变换的顺序很重要。
  • <Group> 组件可以将多个图形分组,并对整个组应用变换。
  • <Group origin={{ x, y }}> 指定了变换的原点。对于旋转和缩放,指定原点非常重要,否则默认以 Canvas 的左上角 (0,0) 或图形自身的左上角为原点。
  • 我们使用了 react-native-reanimateduseSharedValuewithTiming 来创建一个随时间变化的旋转角度,并将其应用到 <Group>transform prop 上,实现了动画效果。注意,Skia 的旋转角度需要是弧度,所以我们将共享值 rotation.value (范围 0-1) 乘以 Math.PI * 2

6. 文本渲染 (Text)

RN Skia 可以渲染高质量的文本,并且提供了多种文本绘制选项。

  • <Text>: 绘制单行文本。需要指定文本内容 (text), 位置 (x, y), 和字体 (font).
  • <Paragraph>: 绘制多行文本,支持文本自动换行、对齐等,更适合绘制段落。需要指定文本样式 (textStyle), 宽度限制 (width), 和字体 (font).

使用文本组件需要加载字体:

“`jsx
import React from ‘react’;
import { View, StyleSheet } from ‘react-native’;
import { Canvas, Text, useFont, Skia, Paragraph } from ‘@shopify/react-native-skia’;

// 假设你在项目中有一个字体文件,例如 ‘assets/fonts/Roboto-Bold.ttf’
// 或者使用系统字体 ‘System’ (不推荐,跨平台不一致)
// const customFont = require(‘../../assets/fonts/Roboto-Bold.ttf’); // 使用 require 导入字体文件

const SkiaTextRendering = () => {
const canvasWidth = 300;
const canvasHeight = 400;

// 加载字体
// const font = useFont(customFont, 30); // 从文件加载,字号 30
// 或者使用系统字体 (仅供示例,不推荐用于生产)
const font = useFont(“System”, 30); // 使用系统默认字体,字号 30
const fontLarge = useFont(“System”, 50); // 字号 50

if (!font || !fontLarge) {
// 字体加载需要时间,可能在加载完成前渲染,此时 font 是 null
return Loading fonts…;
}

// Paragraph 文本样式
const textStyle = {
color: Skia.Color(“darkblue”),
};

// Paragraph 内联样式(更复杂,这里只做简单示例)
const paragraphText = [
{ text: “Hello Skia “, style: { color: Skia.Color(“red”) } },
{ text: “Text Rendering!\n”, style: { color: Skia.Color(“green”), font: fontLarge } },
{ text: “This is a longer paragraph showing line breaks and wrapping.”, style: textStyle },
];

return (

    {/* 绘制单行文本 */}
    <Text
      text="Hello, Skia!"
      x={50} // 文本基线的 x 坐标
      y={50} // 文本基线的 y 坐标
      font={fontLarge} // 应用字号 50 的字体
      color="black" // 直接设置颜色
    />

    {/* 绘制另一行文本,使用 Paint 设置样式 */}
    <Text
       text="Styled Text"
       x={50}
       y={100}
       font={font} // 应用字号 30 的字体
    >
       <Paint style="fill" color="purple" />
    </Text>

    {/* 绘制多行文本 (Paragraph) */}
    {/* Paragraph 的 x, y 是整个文本块的左上角 */}
    <Paragraph
       x={20}
       y={150}
       width={canvasWidth - 40} // 设置文本块宽度以触发换行
       text={paragraphText} // 段落文本内容及内联样式
       font={font} // 段落的默认字体
    >
       {/* Paragraph 的 Paint 可以设置整体样式,但内联样式优先 */}
       <Paint style="fill" color="gray" />
    </Paragraph>


  </Canvas>
</View>

);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: ‘center’,
alignItems: ‘center’,
backgroundColor: ‘#f0f0f0’,
},
});

export default SkiaTextRendering;
“`

解释:

  • useFont(source, size) 是一个 Hook,用于异步加载字体。第一个参数是字体源(可以是 require('...') 的结果或系统字体名),第二个参数是字号。字体加载完成后,Hook 会返回一个 Font 对象。你需要处理字体未加载时的渲染状态。
  • <Text>xy props 指的是文本基线的坐标。
  • <Paragraph> 更适合绘制多行文本,它需要一个 width prop 来确定何时换行。text prop 可以是一个字符串数组,每个字符串可以带 style 对象,实现内联样式。
  • 文本的颜色可以通过 color prop 或通过子级的 <Paint> 设置。
  • 加载自定义字体通常需要将字体文件添加到项目资产中,并在项目的构建配置中正确引用。Expo 用户可以使用 expo-font 加载资产字体。

7. 使用图像 (Images)

在 Skia 画布上绘制图像也很简单。你需要先加载图像,然后使用 <Image> 组件进行绘制。

“`jsx
import React from ‘react’;
import { View, StyleSheet } from ‘react-native’;
import { Canvas, Image, useImage, Skia } from ‘@shopify/react-native-skia’;

// 假设你在项目中有一个图片文件,例如 ‘assets/logo.png’
// const localImage = require(‘../../assets/logo.png’); // 使用 require 导入图片文件

const SkiaImageRendering = () => {
const canvasWidth = 300;
const canvasHeight = 400;

// 从本地文件加载图片
// const logoImage = useImage(localImage); // 从 require 结果加载
// 或者从网络加载图片 (异步)
const networkImageUrl = ‘https://reactnative.dev/img/logo-og.png’;
const rnLogoImage = useImage(networkImageUrl); // 从网络URL加载

if (!rnLogoImage) {
// 图片加载需要时间
return Loading image…;
}

return (

    {/* 绘制网络加载的图片 */}
    <Image
      image={rnLogoImage} // 图片对象
      x={50} // 左上角 x 坐标
      y={50} // 左上角 y 坐标
      width={200} // 绘制宽度
      height={200} // 绘制高度
      fit="contain" // 图片适应模式 ('fill', 'contain', 'cover', 'scaleDown', 'none')
      // opacity={0.8} // 可以设置透明度
    />

    {/* 可以在图像上叠加其他绘制,例如边框 */}
    <Rect x={50} y={50} width={200} height={200}>
        <Paint style="stroke" color="red" strokeWidth={2} />
    </Rect>


  </Canvas>
</View>

);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: ‘center’,
alignItems: ‘center’,
backgroundColor: ‘#f0f0f0’,
},
});

export default SkiaImageRendering;
“`

解释:

  • useImage(source) 是一个 Hook,用于异步加载图像。源可以是 require('...') 的结果,也可以是网络 URL。加载完成后返回一个 Image 对象。同样需要处理加载中的状态。
  • <Image> 组件需要 image prop 来接收 Image 对象。
  • x, y, width, height props 定义了图像绘制的目标矩形区域。
  • fit prop 控制图像如何在目标矩形内缩放和对齐,类似于 CSS 的 object-fit
  • 你可以通过在 <Image> 组件内放置 <Paint> 组件来应用滤镜、混合模式等高级效果,或者在图像的同一位置绘制其他图形(如上面的红色边框示例)。

第三部分:进阶与注意事项

1. 组合与图层 (Group and Layer)

前面我们使用了 <Group> 来对一组图形应用变换。除了变换,<Group> 还可以用于设置透明度 (opacity)。

<Layer> 组件是另一个重要的组合容器。与 <Group> 主要用于变换不同,<Layer> 主要用于应用高级效果,如:

  • 混合模式 (Blend Modes): 控制 Layer 中的内容如何与下方内容进行混合 (blendMode prop)。
  • 背景 (Backdrop): 允许 Layer 访问其下方已经绘制的内容,常用于实现背景模糊等效果。
  • ImageFilter / ColorFilter: 对 Layer 中的所有内容应用统一的滤镜。

使用 <Layer> 通常会创建一个离屏缓冲区来绘制其内容,然后再将这个缓冲区按照指定的混合模式或效果绘制回主画布,这可能会带来一些性能开销,但对于实现某些视觉效果是必须的。

2. 动画与交互

RN Skia 与 react-native-reanimated 集成得非常好。你可以使用 Reanimated 的 useSharedValue, useAnimatedProps, useDerivedValue, withTiming, withSpring 等工具来驱动 Skia 图形的属性变化,从而实现高性能的动画。

关键在于:

  • 使用 useSharedValueuseDerivedValue 来存储动画相关的数值。
  • 将这些共享值绑定到 Skia 组件的 props 上。对于某些直接接受 Reanimated 共享值的 props (如 transform 数组中的值),RN Skia 会自动处理。
  • 对于需要根据 Reanimated 共享值计算复杂属性的情况,可以使用 useDerivedValue 来创建一个计算值,或者使用 useAnimatedProps (通常用于自定义组件或底层属性)。

前面变换部分的旋转动画就是一个简单的 Reanimated 集成示例。

交互方面,由于 Skia 绘制的是像素而不是 React Native 的 View 组件,它本身没有内置的触摸事件处理。要实现交互,你需要:

  1. 使用 react-native-gesture-handler<Canvas> 组件上添加手势识别。
  2. 根据手势事件的坐标,计算出触摸点是否落在了你绘制的某个图形区域内。这通常需要手动进行“点击测试” (hit testing),例如判断点是否在圆内、矩形内或路径内。RN Skia 提供了一些工具函数(如 Path 对象的 contains() 方法)来辅助进行点击测试。

3. 性能考量

虽然 Skia 性能很高,但在 React Native 环境中仍然需要注意一些性能最佳实践:

  • 避免在渲染循环中频繁创建 Skia 对象: Skia 对象(如 Paint, Path, Font, Image, Shader 等)的创建是有成本的。如果一个对象在每次渲染时都会重新创建,即使它的属性没有改变,也会浪费资源。对于不变或通过 Reanimated 共享值变化的 Skia 对象,尽量在组件外部或使用 useMemo, useRefSkia.use... 系列的 Hook 来创建,并在依赖项变化时才更新。例如,useFont, useImage, useSharedValue 都属于这类 Hook。
  • 合理使用 useDerivedValue: 当 Skia 组件的某个 prop 需要根据一个或多个 Reanimated 共享值计算得出时,使用 useDerivedValue 是最佳实践,它可以在 UI 线程高效地计算出新的值。
  • Canvas 尺寸: 避免创建过大或频繁改变尺寸的 Canvas。
  • 复杂路径和着色器: 复杂的路径绘制和自定义着色器可能会消耗更多 GPU 资源,注意优化。
  • 图层 (<Layer>): 如前所述,Layer 会引入离屏绘制,适度使用。
  • 使用 Skia.release() (罕用): 对于某些手动创建的 Skia 对象,如果你确定不再需要它们,可以使用 Skia.release() 来释放内存,但这通常由 RN Skia 内部管理得很好,普通应用开发中很少需要手动调用。

4. 调试

调试 RN Skia 代码可能会比调试普通的 RN View 组件稍微复杂。你无法像查看 DOM 元素那样直接检查 Skia 绘制的元素。

  • 视觉调试: 最直接的方法是调整颜色、位置、边框等属性,观察它们在屏幕上的变化,从而定位问题。
  • React Native Debugger: 你仍然可以使用 React Native Debugger 来查看组件树、props、state 和 console.log 输出。对于 Reanimated 相关的调试,Reanimated 的调试工具也很有用。
  • Skia Inspector (实验性): Skia 本身有一个调试工具叫做 Skia Inspector,可以连接到正在运行的应用,查看绘制指令。RN Skia 也提供了实验性的支持,但设置可能比较复杂,且主要面向高级用户。

第四部分:继续学习的资源

  • 官方文档: @shopify/react-native-skia 的官方文档是学习和查阅的最重要资源。它详细介绍了每个组件的 props、Skia API 的绑定以及各种高级概念。
    https://shopify.github.io/react-native-skia/
  • Skia 文档: 如果你需要深入了解某个 Skia 概念(如 PaintStyle, BlendMode, Shaders, Path 命令等),查阅 Skia 官方文档(虽然是 C++ 为主,但概念是通用的)或相关的教程也很有帮助。
    https://skia.org/
  • 示例项目: 查看 RN Skia 官方仓库中的示例项目,它们展示了如何实现各种图表、动画和效果,是非常好的学习素材。
  • 社区: 在 GitHub 仓库的 Discussions 或者相关的 React Native 社区(如 Discord, 论坛)中提问和交流。

总结

React Native Skia 是一个强大的工具,它为 React Native 应用带来了高性能的 2D 图形渲染能力。通过基于 React 组件的声明式 API,即使是新手也能够相对容易地开始绘制复杂的图形。

从基础的 <Canvas>、图形原语、<Paint> 样式,到变换、文本和图像处理,RN Skia 提供了一套完整的工具集。结合 react-native-reanimated,你可以创建出令人惊艳的动画和交互效果。

虽然入门需要理解一些新的概念(尤其是 Skia 的 Paint 模型),并且复杂图形的绘制需要更多的代码和逻辑,但其带来的性能和功能优势是无可替代的。

希望这篇详细的入门指南能帮助你顺利迈出使用 React Native Skia 的第一步。现在,是时候打开你的代码编辑器,开始在你的 React Native 应用中绘制无限可能吧!祝你玩得开心!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部