React NativeにはUIをアニメーションさせる機能があり、こちらの記事で取り上げたアプリでも一部で使用しています。

とはいえ、そのときは作ること優先でかなりベタに書くやり方をしました。今回はアニメーションの実装の仕方について整理したり、OSSのライブラリがどのような実装をしているのか、などを見ていきたいと思います。

ベーシックな書き方

公式ドキュメントのアニメーションのページにある最初のサンプルコードが一番シンプルな事例と言えそうなのでそれを持ってきます。

 1class FadeInView extends React.Component {
 2  state = {
 3    fadeAnim: new Animated.Value(0),  // Initial value for opacity: 0
 4  }
 5
 6  componentDidMount() {
 7    Animated.timing(                  // Animate over time
 8      this.state.fadeAnim,            // The animated value to drive
 9      {
10        toValue: 1,                   // Animate to opacity: 1 (opaque)
11        duration: 10000,              // Make it take a while
12      }
13    ).start();                        // Starts the animation
14  }
15
16  render() {
17    let { fadeAnim } = this.state;
18
19    return (
20      <Animated.View                 // Special animatable View
21        style={{
22          ...this.props.style,
23          opacity: fadeAnim,         // Bind opacity to animated value
24        }}
25      >
26        {this.props.children}
27      </Animated.View>
28    );
29  }
30}

ポイントは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

 1// 初期設定
 2this.animatedValue = new Animated.Value(0);
 3
 4// 表示するとき
 5Animated.timing(this.animatedValue, {
 6  toValue: 1,
 7  duration: this.state.durationToShow
 8}).start(this._showMessageBarAlertComplete());
 9
10// 非表示にするとき
11Animated.timing(this.animatedValue, {
12  toValue: 0,
13  duration: this.state.durationToHide
14}).start(this._hideMessageBarAlertComplete());

なるほど、Animated.ValueAnimated.timingが使われていて、最初の例とあまり変わらないですね。

では、このthis.animatedValueがどのように使われるかを見てみます。

 1_apllyAnimationTypeTransformation() {
 2// 略
 3  switch (animationType) {
 4    case 'SlideFromTop':
 5       var animationY = this.animatedValue.interpolate({
 6        inputRange: [0, 1],
 7        outputRange: [-windowHeight, 0]
 8      });
 9      this.animationTypeTransform = [{ translateY: animationY }];
10      break;
11// 略

ここでのポイントは、this.animationValue.interpolateです。inputRangeの区間をoutputRangeの区間に置き換えます。ここでは0〜1-windowHeight〜0としています。

ここで代入されているthis.animationTypeTransformは↓な感じでAnimated.Viewのスタイルとして使われています。

1<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

 1// 初期設定
 2suggestionRowHeight: new Animated.Value(0),
 3
 4// 表示
 5Animated.timing(this.state.suggestionRowHeight, {
 6  toValue: height ? height : this.props.suggestionRowHeight,
 7  duration: 100,
 8}).start();
 9
10// 非表示
11Animated.timing(this.state.suggestionRowHeight, {
12  toValue: 0,
13  duration: 100,
14}).start();

こちらはシンプルに0からheightまで値を変化させてますね。

render()では、

1<Animated.View style={[{ ...this.props.suggestionsPanelStyle }, { height: this.state.suggestionRowHeight }]}>
2  ...
3</Animated.View>

という感じで値をそのまま使っています。シンプル。

おわり

  • Animated.Value
  • Animated.View
  • Animated.timing

の3つに加えて、

  • interpolate

辺りを把握しておけば基本的なことは大体できそうな感じがしました。

あとアニメーションが絡む場合はコンポーネントに切り出して、アニメーション絡みのコードをコンポーネントに閉じたほうがよさそうです。と、ライブラリのコードを読んで思いました。 初期設定、表示、非表示とそれだけでコード量が増えるんですよね。リファクタリングしたい気持ち。