React NativeにはUIをアニメーションさせる機能があり、こちらの記事で取り上げたアプリでも一部で使用しています。
とはいえ、そのときは作ること優先でかなりベタに書くやり方をしました。今回はアニメーションの実装の仕方について整理したり、OSSのライブラリがどのような実装をしているのか、などを見ていきたいと思います。
ベーシックな書き方
公式ドキュメントのアニメーションのページにある最初のサンプルコードが一番シンプルな事例と言えそうなのでそれを持ってきます。
class FadeInView extends React.Component {
state = {
fadeAnim: new Animated.Value(0), // Initial value for opacity: 0
}
componentDidMount() {
Animated.timing( // Animate over time
this.state.fadeAnim, // The animated value to drive
{
toValue: 1, // Animate to opacity: 1 (opaque)
duration: 10000, // Make it take a while
}
).start(); // Starts the animation
}
render() {
let { fadeAnim } = this.state;
return (
<Animated.View // Special animatable View
style={{
...this.props.style,
opacity: fadeAnim, // Bind opacity to animated value
}}
>
{this.props.children}
</Animated.View>
);
}
}
ポイントは3つかなと思っています。
Animated.Value
アニメーションで変化する値。コンポーネントはこれをstateに持ちます。
styleや色々なpropertyとして使うことができます。
Animated.View
アニメーション可能な<View>
で、stateとして持っているAnimated.Value
な値をstyleに持っていて、値の変化に応じてViewが変化していきます。
Animated
には他にも
Animated.Image
Animated.ScrollView
Animated.Text
などがあるそうですが、使ったことないです。Text!?って感じです。
Animated.timing
Animated.Value
な値をどのように変化させるか、というアニメーションの肝を司る命令です。この例ではコンポーネントマウント時に値を変化させるため、componentDidMount()
内でstartさせています。
と、ここまでは僕の知ってる書き方で、うんまあそうだねという感じ。
ライブラリを覗いてみる
react-native-message-bar
アプリの上にニュッと出てくるアラートみたいなのを手軽に実装できるライブラリで、先のアプリでは通信の失敗のときなんかに使っています。滅多に見ないはず。
メインのコンポーネントを覗いてみます。
react-native-message-bar/MessageBar.js at master · KBLNY/react-native-message-bar
// 初期設定
this.animatedValue = new Animated.Value(0);
// 表示するとき
Animated.timing(this.animatedValue, {
toValue: 1,
duration: this.state.durationToShow
}).start(this._showMessageBarAlertComplete());
// 非表示にするとき
Animated.timing(this.animatedValue, {
toValue: 0,
duration: this.state.durationToHide
}).start(this._hideMessageBarAlertComplete());
なるほど、Animated.Value
とAnimated.timing
が使われていて、最初の例とあまり変わらないですね。
では、このthis.animatedValue
がどのように使われるかを見てみます。
_apllyAnimationTypeTransformation() {
// 略
switch (animationType) {
case 'SlideFromTop':
var animationY = this.animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [-windowHeight, 0]
});
this.animationTypeTransform = [{ translateY: animationY }];
break;
// 略
ここでのポイントは、this.animationValue.interpolate
です。inputRange
の区間をoutputRange
の区間に置き換えます。ここでは0〜1
を-windowHeight〜0
としています。
ここで代入されているthis.animationTypeTransform
は↓な感じでAnimated.View
のスタイルとして使われています。
<Animated.View style={{ transform: this.animationTypeTransform, ...
transformY
は-windowHeight〜0
の間で変化するというわけですね。このようにinterpolateを使うと、Animated.Value
の変化を別の区間にマッピングして使うことができます。
このライブラリは設定によって表示位置変えることができます。そのため、設定に応じてアニメーションに必要なパラメータは異なります。それぞれにAnimated.Value
を用いるのではなく、0〜1
に変化する1つのパラメータを設定内容とinterpolate
によって使いまわすことでシンプルな実装にできています。
もう1個ぐらい見てみましょう。
react-native-mentions
メンション候補をサジェストしてくれるUIをもったInputです。
こちらは下からニュッと出て、また下にニュッと消える動きですね。
アニメーションの実装部分を抜粋するとこんな感じです。
react-native-mentions/MentionsTextInput.js at master · harshq/react-native-mentions
// 初期設定
suggestionRowHeight: new Animated.Value(0),
// 表示
Animated.timing(this.state.suggestionRowHeight, {
toValue: height ? height : this.props.suggestionRowHeight,
duration: 100,
}).start();
// 非表示
Animated.timing(this.state.suggestionRowHeight, {
toValue: 0,
duration: 100,
}).start();
こちらはシンプルに0
からheight
まで値を変化させてますね。
render()
では、
<Animated.View style={[{ ...this.props.suggestionsPanelStyle }, { height: this.state.suggestionRowHeight }]}>
...
</Animated.View>
という感じで値をそのまま使っています。シンプル。
おわり
Animated.Value
Animated.View
Animated.timing
の3つに加えて、
interpolate
辺りを把握しておけば基本的なことは大体できそうな感じがしました。
あとアニメーションが絡む場合はコンポーネントに切り出して、アニメーション絡みのコードをコンポーネントに閉じたほうがよさそうです。と、ライブラリのコードを読んで思いました。 初期設定、表示、非表示とそれだけでコード量が増えるんですよね。リファクタリングしたい気持ち。