React Native Reanimated
react-native-reanimated の特徴はなんと言っても、
ということです。
公式ページ の説明によれば、React Native の Animated API では React Native 側のスレッドで実行されるためUIに反映されるまで1フレームの遅るそうです。
一方、Reanimated API はUIスレッドで実行されるので即時反映されるそうです。 そしてこれを実現するために大きな役割を果たしているのが次の2つの機能です。
重要な2つの機能
- worklet
- SharedValue
では、この2つの機能を詳しく説明していきましょう!と言いたいところですが、実際にアニメーションを実現するコードを見た方が分かりやすいと思うので、そのコードから見ていきましょう!
アニメーションするには4つのステップがあります。
アニメーションの4つのステップ
- SharedValue の定義
- AnimatedStyleの生成
- Animated.Valueタグのstyleを設定
- SharedValueの変化を開始
この4つのステップを順に見ていきましょう!
1. SharedValueの定義
reanimated のアニメーションはReact Nativeのスレッドではなく、UIスレッドで実行されると先に述べましたが、その2つのスレッドでデータをやり取りする方法を提供するのが SharedValue です。SharedValue はReact NativeのスレッドとUIスレッドの間で値を共有することができます。ただし、そのデータの参照が共有される訳ではなく実際には値のコピーが渡されます。React Native 側で SharedValue が更新されると、そのコピがUIスレッドに渡され、逆にUIスレッドでSharedValueが更新されると、そのコピーがReact Nativeスレッドに渡されます。
SharedValue は次のようにして生成できます。以下の場合、sharedValの初期値は0です。
const sharedVal = useSharedValue(0);
では、このSharedValueを使ってどのようにアニメーションするのかについて以下で確認していきましょう!
2. AnimatedStyleの生成
ほとんどのアニメーションはコンポーネントの位置や色などを変化させることで実現されます。つまりstyleを少しづつ変化させるということですね。reanimatedのアニメーションに対応したstyleを作成するには、useAnimatedStyleフックを使います。
以下の例はコンポーネントをx軸方向に移動するstyleです。
const animatedStyles = useAnimatedStyle(() => {
return {
transform: [{ translateX: sharedVal.value * 255 }],
};
});
ここでsharedValが使われているのに注意してください。sharedValが変化するとtranslateXが変化するという仕組みになっています。この時点ではsharedValは、初期値の0のままなので、アニメーションは起こりません。sharedValを変化させる方法は後述します。
3. Animated.Viewのstyleを設定する
useAnimatedStyleで作成したstyleはReact Nativeの通常のViewに設定することは出来ません。styleを設定するには、 reanimatedのAnimated.Viewを使います。React NativeにもデフォルトでAnimated.Viewが存在しますが、それではなくreanimatedのAnimated.Viewなので注意してください。
まずは、インポートします。
import Animated from 'react-native-reanimated';
そして、Animated.Viewのstyle propにuseAnimatedStyleで作成したstyleを設定します。
<Animated.View style={[styles.box, animatedStyles]} />
ちなみに、React NativeのAnimated.ViewとreanimatedのAnimated.Viewとの混同を避けるためにimportする時に名前を変更するとよいかも知れません。
import Reanimated from 'react-native-reanimated';
<Reanimated.View style={[styles.box, animatedStyles]} />
4. SharedValueの変化を開始
あとはSharedVelueを変化させることで、紐付けられたstyleもそれに合わせて変化します。つまりアニメーションになります。ここではSharedValueを変化させる方法としてwithTimingを使います。reanimatedには他の関数も用意されていますが、それは公式APIリファレンスを参照してください。
withTimingは、SharedValueを指定した値まで滑らかに変化させてくれる関数です。以下のように使います。
<View>
onPress={() => {
sharedVal.value = withTiming(1);
}}
</View>
上記の例では、コンポーネントがタップされた時にアニメーションが開始されます。その後SharedValueは300msかけて1まで変化します。関連付けられているtranslateXは、sharedVal.value x 255が割り当てられているので、0から255まで変化します。したがって対象コンポーネントが右に255px移動するアニメーションになります。
今回の例のようにSharedValueの変化を開始させたいタイミングでwithTimingを設定します。コンポーネントのレンダリングの度にwithTimingを適用してはいけません。通常はコンポーネントのイベントハンドラで使うか、useEffectで使うことが多いでしょう。
ちなみに、アニメーションの途中で新たにwithTimingが設定されると、前のアニメーションがキャンセルされて新しいアニメーションが始まります。
参考までにwithTimingの仕様を載せておきます。
withTimingの仕様
function withTiming<T extends AnimatableValue>(
toValue: T,
userConfig?: WithTimingConfig,
callback?: (finished?: boolean, current?: AnimatableValue) => void,
): T;
引数
パラメータ | 型 | 説明 |
---|---|---|
toValue | T | SharedValue |
userConfig? | WithTimingConfig | オプション |
callback? | (finished?: boolean, current?: AnimatableValue) => void | アニメーション終了時に呼ばれるコールバック |
WithTimingConfig
パラメータ | 型 | 説明 |
---|---|---|
duration? | number | 変化までの時間(デフォルトは300ms) |
easing? | (value: number) => number | easing function |
アニメーションの応用例
インターポレーション
interpolateを使うと、SharedValueの変化に応じて、より複雑に変化する値を作成することが出来ます。以下の例では、sharedValが0から1まで変化すると、100から50まで変化する値を生成しています。
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{
translateY: interpolate(sharedVal.value, [0, 1], [100, 50], {
extrapolateRight: Extrapolation.CLAMP,
}),
},
],
}));
詳しくは公式APIリファレンスを参照ください。
アニメーションを組み合わせる
以下のようにアニメーションを組み合わせることも可能です。
sharedVal.value = withSequence(
withTiming(-10, { duration: 50 }),
withRepeat(withTiming(ANGLE, { duration: 100 }), 6, true),
withTiming(0, { duration: 50 })
);
アニメーションの終了を検知する
withTimingなどのwithXXX系の関数の最後の引数にコールバックを渡すことで、アニメーションの終了を検知することができます。
withTiming(0, { duration: 300 }, _ => {
// do something
});
withTimingのコールバックは、UIスレッドで実行されるので、React Nativeスレッド側で実行される状態更新関数等はコールバックの中で実行することはできないことに注意してくさい。実行するとアプリがクラッシュします。
コールバックの中で何かしらの情報をReact Nativeスレッド側に伝えたい場合は、SharedValueを使うとよいでしょう。
ただし、SharedValueの値の更新でコンポーネントのレンダリングが走るわけではないので、SharedValueの変更を検知する仕組みが必要です。ここでは単純にSharedValueの値を定期的に確認するという方法でそれを回避することにしましょう。
function watchSharedValue<A>(
a: SharedValue<A>,
onChange: (_: SharedValue<A>) => void,
interval = 100,
prev: A | null = null,
): void {
if (prev === null) {
watchSharedValue(a, onChange, interval, a.value);
} else {
setTimeout(() => {
if (a.value === prev) {
watchSharedValue(a, onChange, interval, prev);
} else {
onChange(a);
}
}, interval);
}
}
watchSharedValue(sharedVal, () => {
// sharedVal.valueが変更されたら呼ばれる
// ここで状態等の更新する
});
withTiming(0, { duration: 300 }, _ => {
sharedVal.value = true;
});
以上今回はreact-native-reanimatedを基本的な使い方を確認しました!