先日、Animatedを使ったアニメーションの実装方法について以下の記事で紹介した。
今回はAnimatedを使った無限ループアニメーションの実装方法を紹介していく。
コンテンツ
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。