見た目の差が、個人開発者の武器になる
App Store や Google Play で多くのアプリが競合しているなか、ユーザーが「インストールしたい」と思うアプリには共通点があります。それは、視覚的なクオリティが明らかに高いこと。
React Native の標準コンポーネントだけで構築したUIはどうしても似たような見た目になりがちです。一方、ネイティブエンジニアが Swift や Kotlin で精緻に作り込んだグラフィックスは、まるで別次元のクオリティを感じさせます。
React Native Skia は、その「別次元のクオリティ」をRorkアプリにも持ち込める強力なライブラリです。Googleが開発した2Dグラフィックスエンジン「Skia」をReact Nativeから直接操作でき、Canvas APIを通じて以下が可能になります。
- カスタム折れ線・棒・円グラフ(ライブラリに依存しないオリジナルデザイン)
- グラデーション・ブラー・シャドウなどのビジュアルエフェクト
- SL(Skia Shader Language)を使ったGLSL風シェーダーエフェクト
- Reanimatedとの統合によるスムーズなインタラクティブアニメーション
前提知識と環境準備
対象読者
この記事はRork Maxで開発し、React Native/Expoの基礎を理解していることを前提としています。具体的には次の知識を前提とします。
- React Native のコンポーネントとスタイリングの基礎
- TypeScript の型定義に対する基本的な理解
- Rork Maxでのプロジェクト作成経験
必要なパッケージ
まずプロジェクトに必要なパッケージをインストールします。
# Expo プロジェクトへのインストール(Rork Max 対応)
npx expo install @shopify/react-native-skia
# Reanimated との組み合わせ使用時(推奨)
npx expo install react-native-reanimated
注意: Rork MaxはExpoをベースとしているため、npx expo install を使うことでExpoのSDKバージョンと互換性のあるバージョンが自動選択されます。npm install を直接使うとバージョン不一致が起きやすいので注意してください。
metro.config.js の設定
Reanimatedを合わせて使う場合、metro.config.js への追記が必要です。
// metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
// Reanimated プラグインを追加
config.transformer.babelTransformerPath = require.resolve(
'react-native-reanimated/plugin'
);
module.exports = config;
また babel.config.js にも Reanimated のプラグインを追加します。
// babel.config.js
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: [
'react-native-reanimated/plugin', // 必ず最後に追加
],
};
};
Skia の基本概念:Canvas と描画モデル
Skiaを使いこなすには、まず「Canvasに命令を積み重ねて画面を描く」という描画モデルを理解することが大切です。
Canvas と描画の基本
import { Canvas, Circle, Paint, Rect, Path } from '@shopify/react-native-skia';
export default function BasicSkiaDemo() {
return (
// Canvas コンポーネントが描画領域を確保する
<Canvas style={{ width: 300, height: 300 }}>
{/* 背景の矩形 */}
<Rect x={0} y={0} width={300} height={300} color="#1a1a2e" />
{/* 円を描く */}
<Circle cx={150} cy={150} r={80} color="#e94560" />
{/* 枠線のある円(Paint で描画スタイルを定義) */}
<Circle cx={150} cy={150} r={80}>
<Paint color="white" style="stroke" strokeWidth={3} />
</Circle>
</Canvas>
);
}
期待する出力: 濃紺の背景に赤いサークルが白い枠線付きで表示されます。
Paint と描画スタイル
Paint コンポーネントは描画スタイル(色・線幅・ブレンドモード等)を定義します。
import { Canvas, Rect, Paint, BlurMask } from '@shopify/react-native-skia';
export default function PaintDemo() {
return (
<Canvas style={{ width: 300, height: 200 }}>
<Rect x={20} y={20} width={120} height={80}>
<Paint color="rgba(99, 179, 237, 0.8)">
{/* ソフトなグロー効果をつける */}
<BlurMask blur={12} style="normal" />
</Paint>
</Rect>
</Canvas>
);
}
実装パターン1:カスタム折れ線グラフ
Victory Native や recharts 等のライブラリを使わず、Skia で独自デザインの折れ線グラフを作る方法を解説します。
データからパスを生成する
import React, { useMemo } from 'react';
import { View, Text } from 'react-native';
import {
Canvas,
Path,
Skia,
LinearGradient,
vec,
} from '@shopify/react-native-skia';
interface LineChartProps {
data: number[];
width: number;
height: number;
color?: string;
}
export function CustomLineChart({
data,
width,
height,
color = '#6366f1',
}: LineChartProps) {
const padding = 20;
const chartWidth = width - padding * 2;
const chartHeight = height - padding * 2;
// データを Canvas 座標に変換してパスを生成
const { linePath, fillPath } = useMemo(() => {
const maxValue = Math.max(...data);
const minValue = Math.min(...data);
const range = maxValue - minValue || 1;
// 折れ線パス
const line = Skia.Path.Make();
// 塗りつぶしパス(グラデーション用)
const fill = Skia.Path.Make();
data.forEach((value, index) => {
const x = padding + (index / (data.length - 1)) * chartWidth;
const y =
padding + chartHeight - ((value - minValue) / range) * chartHeight;
if (index === 0) {
line.moveTo(x, y);
fill.moveTo(x, y);
} else {
line.lineTo(x, y);
fill.lineTo(x, y);
}
});
// 塗りつぶし領域を閉じる
fill.lineTo(padding + chartWidth, padding + chartHeight);
fill.lineTo(padding, padding + chartHeight);
fill.close();
return { linePath: line, fillPath: fill };
}, [data, chartWidth, chartHeight, padding]);
return (
<Canvas style={{ width, height }}>
{/* グラデーション塗りつぶし */}
<Path path={fillPath} style="fill">
<LinearGradient
start={vec(0, padding)}
end={vec(0, padding + chartHeight)}
colors={[`${color}40`, `${color}00`]}
/>
</Path>
{/* 折れ線 */}
<Path
path={linePath}
style="stroke"
strokeWidth={2.5}
color={color}
strokeCap="round"
strokeJoin="round"
/>
</Canvas>
);
}
// 使用例
export default function ChartScreen() {
const salesData = [12, 28, 19, 45, 38, 52, 61, 48, 70, 83, 72, 95];
return (
<View style={{ padding: 16 }}>
<Text style={{ fontSize: 18, fontWeight: '600', marginBottom: 12 }}>
月別売上推移
</Text>
<CustomLineChart data={salesData} width={340} height={200} color="#6366f1" />
</View>
);
}
期待する出力: 紫色の折れ線グラフに、下部にフェードアウトするグラデーション塗りつぶしが付いた洗練されたチャートが表示されます。
なお、Victory Native を使った標準的なチャート実装についてはRork アプリにインタラクティブなチャート&グラフを実装する — Victory Native 実践ガイドも参考にしてください。カスタムデザインが不要な場合はそちらの方が実装コストが低いです。
ドーナツチャートの実装
import React from 'react';
import { Canvas, Path, Skia } from '@shopify/react-native-skia';
interface DonutChartProps {
data: { value: number; color: string }[];
size: number;
strokeWidth?: number;
}
export function DonutChart({
data,
size,
strokeWidth = 24,
}: DonutChartProps) {
const total = data.reduce((sum, d) => sum + d.value, 0);
const cx = size / 2;
const cy = size / 2;
const radius = (size - strokeWidth) / 2;
let startAngle = -Math.PI / 2; // 12時の位置から開始
const segments = data.map((segment) => {
const angle = (segment.value / total) * Math.PI * 2;
const endAngle = startAngle + angle;
// 円弧パスを生成
const path = Skia.Path.Make();
const rect = {
x: cx - radius,
y: cy - radius,
width: radius * 2,
height: radius * 2,
};
// Skiaの addArc メソッドでパスを生成
const startDeg = (startAngle * 180) / Math.PI;
const sweepDeg = (angle * 180) / Math.PI;
path.addArc(rect, startDeg, sweepDeg);
startAngle = endAngle;
return { path, color: segment.color };
});
return (
<Canvas style={{ width: size, height: size }}>
{segments.map((seg, i) => (
<Path
key={i}
path={seg.path}
color={seg.color}
style="stroke"
strokeWidth={strokeWidth}
strokeCap="butt"
/>
))}
</Canvas>
);
}
実装パターン2:グラデーションとビジュアルエフェクト
複雑なグラデーション背景
import {
Canvas,
Rect,
RadialGradient,
LinearGradient,
vec,
Group,
BlurMask,
Circle,
} from '@shopify/react-native-skia';
interface GradientCardProps {
width: number;
height: number;
}
export function GradientCard({ width, height }: GradientCardProps) {
return (
<Canvas style={{ width, height }}>
{/* ベースグラデーション */}
<Rect x={0} y={0} width={width} height={height}>
<LinearGradient
start={vec(0, 0)}
end={vec(width, height)}
colors={['#667eea', '#764ba2']}
/>
</Rect>
{/* グロー効果を持つアクセントサークル */}
<Group>
<Circle cx={width * 0.2} cy={height * 0.3} r={60} color="#ffffff20">
<BlurMask blur={30} style="normal" />
</Circle>
</Group>
<Group>
<Circle cx={width * 0.8} cy={height * 0.7} r={80} color="#ffffff15">
<BlurMask blur={40} style="normal" />
</Circle>
</Group>
</Canvas>
);
}
ガラスモーフィズム風カード
ガラスモーフィズムはここ数年のUI/UXトレンドです。Skiaを使えばReact Nativeでもスタイリッシュな実装が可能です。
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import {
Canvas,
Rect,
RoundedRect,
LinearGradient,
vec,
BlurMask,
Paint,
} from '@shopify/react-native-skia';
export function GlassMorphismCard() {
const cardWidth = 320;
const cardHeight = 180;
return (
<View style={styles.container}>
{/* Skia Canvas でガラス効果を描画 */}
<Canvas
style={[styles.canvas, { width: cardWidth, height: cardHeight }]}
>
{/* 半透明のガラス背景 */}
<RoundedRect
x={0}
y={0}
width={cardWidth}
height={cardHeight}
r={20}
color="rgba(255,255,255,0.15)"
/>
{/* 上部の光沢ライン */}
<RoundedRect
x={1}
y={1}
width={cardWidth - 2}
height={cardHeight / 2}
r={20}
>
<LinearGradient
start={vec(0, 0)}
end={vec(0, cardHeight / 2)}
colors={['rgba(255,255,255,0.3)', 'rgba(255,255,255,0.0)']}
/>
</RoundedRect>
{/* ボーダー */}
<RoundedRect
x={0}
y={0}
width={cardWidth}
height={cardHeight}
r={20}
>
<Paint
color="rgba(255,255,255,0.2)"
style="stroke"
strokeWidth={1}
/>
</RoundedRect>
</Canvas>
{/* テキストコンテンツをオーバーレイ */}
<View style={[styles.content, { width: cardWidth }]}>
<Text style={styles.title}>Premium Card</Text>
<Text style={styles.subtitle}>ガラスモーフィズムUI</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: { position: 'relative' },
canvas: { position: 'absolute' },
content: {
position: 'absolute',
padding: 24,
},
title: {
color: 'white',
fontSize: 22,
fontWeight: '700',
},
subtitle: {
color: 'rgba(255,255,255,0.7)',
fontSize: 14,
marginTop: 4,
},
});
実装パターン3:Reanimatedとの統合によるインタラクティブアニメーション
Skia単体でもアニメーションは可能ですが、React Native Reanimatedと組み合わせることで、より表現力豊かなインタラクションが実現できます。Reanimated の基本的な使い方についてはRork × React Native Reanimated & Gesture Handler 実践ガイド — 60fps を維持するアニメーション設計と高度なジェスチャーインタラクションを先に読んでおくと理解がスムーズです。
useSharedValueとSkiaの接続
import React, { useEffect } from 'react';
import { TouchableOpacity, View } from 'react-native';
import {
Canvas,
Circle,
Paint,
useValue,
runTiming,
Easing,
} from '@shopify/react-native-skia';
// Skia 独自のアニメーションAPIを使う方法
export function SkiaPulseAnimation() {
const radius = useValue(40);
const opacity = useValue(1);
const startPulse = () => {
// 外側に広がりながらフェードアウト
runTiming(radius, 80, {
duration: 1000,
easing: Easing.out(Easing.cubic),
});
runTiming(opacity, 0, {
duration: 1000,
easing: Easing.out(Easing.cubic),
});
};
return (
<TouchableOpacity onPress={startPulse}>
<Canvas style={{ width: 200, height: 200 }}>
<Circle cx={100} cy={100} r={radius}>
<Paint color={`rgba(99, 102, 241, ${opacity})`} />
</Circle>
{/* 中心の固定サークル */}
<Circle cx={100} cy={100} r={40} color="#6366f1" />
</Canvas>
</TouchableOpacity>
);
}
インタラクティブなプログレスリング
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import {
Canvas,
Path,
Skia,
useSharedValueEffect,
useValue,
} from '@shopify/react-native-skia';
import Animated, {
useSharedValue,
withTiming,
useAnimatedProps,
Easing as ReanimatedEasing,
} from 'react-native-reanimated';
interface AnimatedRingProps {
progress: number; // 0〜1
size: number;
strokeWidth?: number;
color?: string;
}
export function AnimatedProgressRing({
progress,
size,
strokeWidth = 12,
color = '#6366f1',
}: AnimatedRingProps) {
const cx = size / 2;
const cy = size / 2;
const r = (size - strokeWidth) / 2;
const circumference = 2 * Math.PI * r;
// Reanimated で進捗をアニメーション
const animatedProgress = useSharedValue(0);
// Skia の useValue でブリッジ
const skiaProgress = useValue(0);
useSharedValueEffect(() => {
skiaProgress.current = animatedProgress.value;
}, animatedProgress);
// progress が変わったらアニメーション実行
React.useEffect(() => {
animatedProgress.value = withTiming(progress, {
duration: 1200,
easing: ReanimatedEasing.out(ReanimatedEasing.cubic),
});
}, [progress]);
// パスを動的に生成
const path = Skia.Path.Make();
path.addCircle(cx, cy, r);
return (
<View style={{ width: size, height: size }}>
<Canvas style={{ width: size, height: size }}>
{/* 背景リング */}
<Path
path={path}
style="stroke"
strokeWidth={strokeWidth}
color={`${color}20`}
strokeCap="round"
/>
{/* プログレスリング */}
<Path
path={path}
style="stroke"
strokeWidth={strokeWidth}
color={color}
strokeCap="round"
start={0}
end={skiaProgress}
/>
</Canvas>
<View style={[StyleSheet.absoluteFill, styles.center]}>
<Text style={[styles.progressText, { color }]}>
{Math.round(progress * 100)}%
</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
center: {
alignItems: 'center',
justifyContent: 'center',
},
progressText: {
fontSize: 18,
fontWeight: '700',
},
});
期待する出力: 0から指定した進捗率まで滑らかにアニメーションするプログレスリングが表示されます。
実装パターン4:Skia Shader Language(SL)によるシェーダーエフェクト
Skia Shader Language(SL)はGLSLに似たシェーダー言語で、ピクセル単位のエフェクトを定義できます。これを使うと、標準的なReact Nativeでは絶対に再現できない特殊なビジュアルエフェクトが実現できます。
ウェーブシェーダー
import React, { useEffect } from 'react';
import {
Canvas,
Shader,
Skia,
Fill,
useValue,
runTiming,
Easing,
} from '@shopify/react-native-skia';
// SL(Skia Shader Language)でウェーブを定義
const waveShaderSource = Skia.RuntimeEffect.Make(`
uniform float time;
uniform float2 resolution;
half4 main(float2 pos) {
float2 uv = pos / resolution;
// ウェーブのパラメータ
float wave1 = sin(uv.x * 10.0 + time * 2.0) * 0.05;
float wave2 = sin(uv.x * 7.0 - time * 1.5) * 0.04;
float y = uv.y + wave1 + wave2;
// グラデーションカラー
half3 color1 = half3(0.4, 0.4, 0.9); // 青紫
half3 color2 = half3(0.1, 0.8, 0.6); // ティール
float blend = smoothstep(0.4, 0.6, y);
half3 color = mix(color1, color2, blend);
// 透明度(ウェーブの境界をなめらかに)
float alpha = smoothstep(0.45, 0.55, y);
return half4(color * alpha, alpha);
}
`);
interface WaveAnimationProps {
width: number;
height: number;
}
export function WaveAnimation({ width, height }: WaveAnimationProps) {
const time = useValue(0);
useEffect(() => {
// 時間を無限ループで進める
const loop = () => {
runTiming(time, time.current + Math.PI * 2, {
duration: 4000,
easing: Easing.linear,
});
};
loop();
const interval = setInterval(loop, 4000);
return () => clearInterval(interval);
}, []);
if (!waveShaderSource) return null;
return (
<Canvas style={{ width, height }}>
<Fill>
<Shader
source={waveShaderSource}
uniforms={{
time,
resolution: [width, height],
}}
/>
</Fill>
</Canvas>
);
}
ノイズテクスチャシェーダー
const noiseShaderSource = Skia.RuntimeEffect.Make(`
uniform float time;
uniform float2 resolution;
// Simplex Noise 近似(軽量版)
float hash(float2 p) {
p = fract(p * float2(234.34, 435.345));
p += dot(p, p + 34.23);
return fract(p.x * p.y);
}
float noise(float2 p) {
float2 i = floor(p);
float2 f = fract(p);
float2 u = f * f * (3.0 - 2.0 * f);
return mix(
mix(hash(i), hash(i + float2(1, 0)), u.x),
mix(hash(i + float2(0, 1)), hash(i + float2(1, 1)), u.x),
u.y
);
}
half4 main(float2 pos) {
float2 uv = pos / resolution;
// 複数レイヤーのノイズを重ねる(fbm: fractal brownian motion)
float n = 0.0;
float amplitude = 0.5;
float frequency = 3.0;
for (int i = 0; i < 5; i++) {
n += amplitude * noise(uv * frequency + time * 0.2);
amplitude *= 0.5;
frequency *= 2.0;
}
// カラーマッピング
half3 color = mix(
half3(0.05, 0.05, 0.15), // 深い紺
half3(0.3, 0.1, 0.5), // 紫
n
);
color = mix(color, half3(0.6, 0.3, 0.9), pow(n, 2.0) * 0.5);
return half4(color, 1.0);
}
`);
パフォーマンス最適化のベストプラクティス
Skiaは非常に強力ですが、使い方を誤るとパフォーマンスが著しく低下します。以下のポイントを必ず守ってください。アプリ全体のパフォーマンスチューニングについてはRork アプリ パフォーマンス最適化完全ガイド:レンダリング・メモリ・バンドルサイズを徹底チューニングもあわせて参照してください。
1. パスの再生成を最小化する
最も多いパフォーマンス問題は、「毎フレームパスを再生成する」ことです。
// ❌ 悪い例:レンダーごとにパスを生成する
function BadExample({ data }: { data: number[] }) {
// これはレンダーのたびに実行される
const path = Skia.Path.Make();
data.forEach((v, i) => {
// ...パス生成
});
return (
<Canvas style={...}>
<Path path={path} />
</Canvas>
);
}
// ✅ 良い例:useMemo でパスをメモ化する
function GoodExample({ data }: { data: number[] }) {
const path = useMemo(() => {
const p = Skia.Path.Make();
data.forEach((v, i) => {
// ...パス生成
});
return p;
}, [data]); // data が変わった時だけ再生成
return (
<Canvas style={...}>
<Path path={path} />
</Canvas>
);
}
2. Canvas のサイズを最小化する
Canvasが大きいほどGPUの負荷が増します。
// ❌ 悪い例:画面全体をCanvasにする
<Canvas style={{ flex: 1 }}>
{/* コンテンツの一部だけグラフィックスを使っているのに画面全体をCanvas化 */}
</Canvas>
// ✅ 良い例:必要な領域だけをCanvasにする
<View style={{ flex: 1 }}>
<Text>通常のテキスト</Text>
<Canvas style={{ width: 300, height: 200 }}>
{/* グラフィックス部分だけ */}
</Canvas>
<Text>別のテキスト</Text>
</View>
3. アニメーション中のレイアウト更新を避ける
// ❌ 悪い例:アニメーション中に JavaScript bridge を経由する
const width = useSharedValue(100);
// Reanimated の useAnimatedStyle + React Native View を使うと
// JS→Native bridge が発生してフレームドロップの原因に
const animatedStyle = useAnimatedStyle(() => ({
width: width.value, // これはReact Native側のレイアウトを更新する
}));
// ✅ 良い例:すべてSkia Canvas内で完結させる
const skiaWidth = useValue(100);
<Canvas style={{ width: 300, height: 200 }}>
<Rect x={0} y={0} width={skiaWidth} height={50} color="#6366f1" />
</Canvas>
// SkiaのアニメーションはGPUスレッドで完結するのでJSブリッジを経由しない
4. ImageFilter のコスト意識
BlurMask や ImageFilter は計算コストが高いため、静止した背景にのみ使うのが望ましいです。アニメーション中の要素に適用すると顕著なパフォーマンス低下が起きます。
よくあるエラーと対処法
エラー1:Canvas の外に Skia コンポーネントを配置している
Error: Cannot use Skia components outside of a Canvas
解決策: すべての Skia コンポーネント(Circle, Rect, Path 等)は必ず Canvas の直接子または子孫でなければなりません。
エラー2:RuntimeEffect.Make がnullを返す
Skia.RuntimeEffect.Make(shaderSource) はシェーダーのコンパイルエラーがあると null を返します。
const effect = Skia.RuntimeEffect.Make(shaderSource);
if (!effect) {
console.error('Shader compilation failed');
// フォールバックUIを表示
return <View />;
}
SLのデバッグにはエラーメッセージを確認し、GLSL経験があればSLはほぼ同じ構文なので参考にできます。
エラー3:Reanimated と Skia の SharedValue が型不一致
TypeError: Cannot assign Reanimated SharedValue to Skia value
react-native-reanimated の SharedValue と @shopify/react-native-skia の SkiaValue は異なる型です。useSharedValueEffect でブリッジする必要があります。
import { useSharedValueEffect, useValue } from '@shopify/react-native-skia';
import { useSharedValue } from 'react-native-reanimated';
const reanimatedValue = useSharedValue(0);
const skiaValue = useValue(0);
// ブリッジ:Reanimated → Skia
useSharedValueEffect(() => {
skiaValue.current = reanimatedValue.value;
}, reanimatedValue);
応用:実際のアプリへの統合事例
ヘルスケアアプリのウィークリーサマリー画面
Skia を使ったグラフィックスは、ヘルスケアアプリのダッシュボードで特に映えます。歩数・カロリー・睡眠時間を視覚的に美しく表示するには、標準コンポーネントでは限界があります。
// ウィークリーバーチャートのコンセプト
export function WeeklyBarChart({ weekData }: { weekData: DayData[] }) {
const chartWidth = 340;
const chartHeight = 160;
const barWidth = 32;
const maxSteps = Math.max(...weekData.map((d) => d.steps));
const bars = useMemo(() =>
weekData.map((day, i) => {
const x = 10 + i * (barWidth + 8);
const barHeight = (day.steps / maxSteps) * chartHeight * 0.8;
const y = chartHeight - barHeight;
const path = Skia.Path.Make();
// 上部だけ丸みをつけたバー
path.addRRect({
rect: { x, y, width: barWidth, height: barHeight },
rx: 8, ry: 8,
});
return { path, achieved: day.steps >= day.goal };
}), [weekData]
);
return (
<Canvas style={{ width: chartWidth, height: chartHeight }}>
{bars.map((bar, i) => (
<Path
key={i}
path={bar.path}
color={bar.achieved ? '#10b981' : '#6366f1'}
>
{/* 達成した日はグラデーションに */}
{bar.achieved && (
<LinearGradient
start={vec(0, 0)}
end={vec(0, chartHeight)}
colors={['#34d399', '#10b981']}
/>
)}
</Path>
))}
</Canvas>
);
}
フィットネスアプリのリング進捗画面
Apple のアクティビティアプリのような3つのリングをSkiaで実装する場合も、上述の AnimatedProgressRing コンポーネントを組み合わせることで実現できます。
個人開発者の視点から(実体験メモ)
まとめ
React Native Skia は、Rorkアプリのビジュアルクオリティを次のステージへ引き上げる強力なツールです。本記事で解説したポイントを振り返ります。
Canvas と Paint を使った基本的なグラフィックスの描画から始まり、折れ線グラフ・ドーナツチャートといったカスタムデータビジュアライゼーション、グラデーション・ガラスモーフィズムなどのビジュアルエフェクト、そしてReanimatedとの組み合わせによるインタラクティブアニメーション、さらにシェーダー言語SLを使ったGPUレベルのエフェクトまで、段階的に実装できます。
初めてSkiaを触る際は、まず CustomLineChart のような小さなコンポーネントから始め、動作を確認しながら徐々に複雑なエフェクトへ挑戦することをおすすめします。App Storeで「あのアプリのグラフィックス、すごい」と言われるアプリを目指す方は、ぜひ本記事のコードを参考にしてください。
グラフィックスだけでなくアニメーションやネイティブ統合まで幅広くカバーされており、Rork Maxでの開発を体系的に学ぶのに役立ちます。