先日、Animatedを使ったアニメーションの実装方法について以下の記事で紹介した。
今回はAnimatedを使った無限ループアニメーションの実装方法を紹介していく。
Contents
start()メソッドのコールバック関数
まずは前回書いたanimatedメソッドの内容を確認しよう。
animate() { Animated.timing(this.state.opacity, { toValue: 1, duration: 1000, }).start(); }
start()メソッドでアニメーションを実行するのだが、ここにコールバック関数を指定することで無限ループを実現することが可能になる。
このコールバック関数は、指定したアニメーションが終了した際に呼び出されるので、関数内で更にアニメーションを実行することで無限ループを実装できる。
前回までのコードと今回の目標物
前回記事で作成したサンプルアプリのコードを以下に記載しておく。
import React, { Component } from 'react'; import { Text, View, FlatList, Image, Dimensions, ActivityIndicator, Animated } from 'react-native'; export default class App extends Component { constructor() { super(); this.state = { isLoading: true, threads: [], opacity: new Animated.Value(0), } } animate() { Animated.timing(this.state.opacity, { toValue: 1, duration: 1000, }).start(); } componentDidMount() { fetch("https://www.reddit.com/r/newsokur/hot.json") .then((response) => response.json()) .then((responseJson) => { let threads = responseJson.data.children; threads = threads.map(i => { i.key = i.data.url; return i; }); this.setState({threads: threads, isLoading: false}); }) .catch((error) => { console.error(error); }) } render() { const { threads, isLoading, opacity } = this.state; const { width } = Dimensions.get('window'); if(!isLoading) this.animate(); return( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', }}> {isLoading ? <ActivityIndicator /> : <Animated.View style={{ opacity: opacity}}> <FlatList data={threads} renderItem={({item}) => { return( <View style={{ flex: 1, flexDirection: 'row', width: '100%' }}> <Image style={{ width: 50, height: 50 }} source={item.data.thumbnail} /> <View style={{ flex: 1, flexDirection: 'column' }}> <Text>{item.data.title}</Text> <Text style={{color: '#ababab', fontSize: 10}}>{item.data.domain}</Text> </View> </View> ) }} /> </Animated.View> } </View> ) } }
APIで記事を読み込み、リスト表示するだけのアプリだ。
記事の読み込み中は、ActivityIndicatorを使ってローディングマークを表示させているのだが、これを別の画像(今回は猫のイラスト画像)に代替し、無限ループを使ってグルグル回り続けるようにコードを書き換えていく。
無限ループ用のコンポーネントを追加する
まずはコード最下部に以下のコンポーネントを追加する。
class Spining extends Component { constructor() { super(); this.state = { degree: new Animated.Value(0) } } render() { const { degree } = this.state; const _degree = degree.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '360deg'], }) return ( <View> <Animated.Image source={require('./assets/cat.jpg')} style={{ transform:[{rotate: _degree}], width: 50, height: 50, }} /> </View> ) } }
コード内にinterpolateという関数があるが、これはoutputRangeに指定した初期値・終了値を、inputRangeで指定する初期値・終了値に置き換えることのできる便利な関数だ。
アニメーションのように特定の値を滑らかに動かす場合に重宝する。
次にメインとなるanimated()メソッド、更にコンポーネントがビューにマウントされた際に自動的にアニメーションが実行されるよう、ライフサイクル関数を定義する。
最終的に出来上がったコンポーネントがこちら。
class Spining extends Component { constructor() { super(); this.state = { degree: new Animated.Value(0) } } componentDidMount() { this.animated() } componentWillUnmount() { this.animated = () => { return false } } animated() { Animated.timing( this.state.degree, { toValue: 1, duration: 4000, } ).start(() => { this.setState({degree: new Animated.Value(0)}) this.animated() }); } render() { const { degree } = this.state; const _degree = degree.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '360deg'], }) return ( <View> <Animated.Image source={require('./assets/cat.jpg')} style={{ transform:[{rotate: _degree}], width: 50, height: 50, }} /> </View> ) } }
メインコンポーネントから呼び出す
最後にメインのAppコンポーネントから、追加したSpiningコンポーネントを呼び出す。
コード内、ローディング中に表示するActivityIndicatorをSpiningに置き換える。
{isLoading ? <ActivityIndicator /> : ↓ 以下に変更 {isLoading ? <Spining /> :
ただ、これだけでは記事の読み込みが終わると記事リストが表示され、無限ループのアニメーションが確認できないので、AppコンポーネントのcomponentDidMount()メソッド内、state値の変更をコメントアウトし無効化する。
componentDidMount() { fetch("https://www.reddit.com/r/newsokur/hot.json") .then((response) => response.json()) .then((responseJson) => { let threads = responseJson.data.children; threads = threads.map(i => { i.key = i.data.url; return i; }); // 以下をコメントアウト // this.setState({threads: threads, isLoading: false}); }) .catch((error) => { console.error(error); }) }
ここまでの変更を適用したコード全文がこちら。
import React, { Component } from 'react'; import { Text, View, FlatList, Image, Dimensions, ActivityIndicator, Animated } from 'react-native'; export default class App extends Component { constructor() { super(); this.state = { isLoading: true, threads: [], opacity: new Animated.Value(0), } } animate() { Animated.timing(this.state.opacity, { toValue: 1, duration: 1000, }).start(); } componentDidMount() { fetch("https://www.reddit.com/r/newsokur/hot.json") .then((response) => response.json()) .then((responseJson) => { let threads = responseJson.data.children; threads = threads.map(i => { i.key = i.data.url; return i; }); // this.setState({threads: threads, isLoading: false}); }) .catch((error) => { console.error(error); }) } render() { const { threads, isLoading, opacity } = this.state; const { width } = Dimensions.get('window'); if(!isLoading) this.animate(); return( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', }}> {isLoading ? <Spining /> : <Animated.View style={{ opacity: opacity}}> <FlatList data={threads} renderItem={({item}) => { return( <View style={{ flex: 1, flexDirection: 'row', width: '100%' }}> <Image style={{ width: 50, height: 50 }} source={item.data.thumbnail} /> <View style={{ flex: 1, flexDirection: 'column' }}> <Text>{item.data.title}</Text> <Text style={{color: '#ababab', fontSize: 10}}>{item.data.domain}</Text> </View> </View> ) }} /> </Animated.View> } </View> ) } } class Spining extends Component { constructor() { super(); this.state = { degree: new Animated.Value(0) } } componentDidMount() { this.animated() } componentWillUnmount() { this.animated = () => { return false } } animated() { Animated.timing( this.state.degree, { toValue: 1, duration: 4000, } ).start(() => { this.setState({degree: new Animated.Value(0)}) this.animated() }); } render() { const { degree } = this.state; const _degree = degree.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '360deg'], }) return ( <View> <Animated.Image source={require('./assets/cat.jpg')} style={{ transform:[{rotate: _degree}], width: 50, height: 50, }} /> </View> ) } }
expo startコマンドを実行するとサンプルアプリが立ち上がる。
猫のイラストがグルグル回るアニメーションが表示されればOK。


