BLOG

【ReactNative】映画情報アプリを作ろう!/#3ライブラリを使って画面遷移編

2023-02-10
どうも、ロックシステムのコジです!
Youtubeでお送りしているReactNativeをつかった「映画検索アプリ」制作も3回目です。

■過去回はこちら
#1環境設定と画面を作ろう編
#2APIを使って映画一覧画面を作ろう編
前回はAPIを使ってアプリに映画一覧画面を作ることができました。
今回のテーマは画面遷移のライブラリを使って映画詳細画面を作っていきます!
分かりやすく言うと一覧画面のポスター画像をタップすると詳細情報の画面に飛ばすということですね。
以前、ニュースアプリを作成した時にも使いましたが、面遷移をするにはReact Navigationというライブラリが必要になります。
実はReact Native自体には画面遷移が備わっていないので、推奨されているこのライブラリのインストールが必要です。
動画ではインストール方法から詳細画面の作成、実際に画面遷移の実装などご紹介しています!

ファイル数が増えて書くコードの量も増えているので、以下のコードを参考に是非チャレンジしてみてください!


01:08 React Navigationのインストールコマンド
npm install @react-navigation/native
01:15 Native-Stackのインストールコマンド
npm install @react-navigation/native-stack
06:50 追加したstyle
const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#202328'
    },
    textBox: {
        paddingHorizontal:30,
        paddingVertical: 5
    },
    title: {
        color: '#fff',
        fontSize: 26,
        fontWeight: 'bold'
    },
    movieReleaseDate: {
        color: '#ccc',
        marginBottom: 10
    },
    overview: {
        color: '#fff',
        fontSize: 18
    },
    movieImage: {
        height: 480,
        resizeMode: 'contain'
    },
    vote: {
        flexDirection: 'row',
        marginTop: 10,
        alignItems: 'center'
    },
    voteCount: {
        color: '#ccc',
        marginLeft: 3
    },
    star: {
        color: 'yellow',
        backgroundColor: 'transparent',
        textShadowColor: 'black',
        textShadowOffset: {width: 1, height: 1},
        textShadowRadius: 2,
    }
})
07:12 react native starsのインストールコマンド
expo install react-native-stars
expo install prop-types

今回の最終コードはこちら!コピぺでも作れちゃいます!

★App.js
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import MovieList from './screens/MovieList';
import MovieDetail from './screens/MovieDetail';
​
const Stack = createNativeStackNavigator();
export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="MovieList" component={MovieList} options={{
          title: "映画一覧",
          headerStyle: {
            backgroundColor: '#202328',
          },
          headerTintColor: '#fff'
        }}/>
        <Stack.Screen name="MovieDetail" component={MovieDetail} options={{
          title: "映画詳細",
          headerStyle: {
            backgroundColor: '#202328',
          },
          headerTintColor: '#fff'
        }}/>
      </Stack.Navigator>
    </NavigationContainer>
  )
};
★MovieDetail.js
import { Text, View, ScrollView, StyleSheet, Image } from "react-native";
import Star from "react-native-stars";
import Ionicons from "@expo/vector-icons/Ionicons";
​
export default function MovieDetail(props) {
    const { movie } = props.route.params;
    return (
        <ScrollView style={styles.container} >
            <Image style={styles.movieImage} source={{uri: `https://image.tmdb.org/t/p/w780${movie.poster_path}`}}></Image>
            <View>
                <Text style={styles.title}>{movie.title}</Text>
                <View style={styles.vote}>
                    <Star
                        default={(movie.vote_average/2)}
                        count={5}
                        half={true}
                        fullStar={<Ionicons name="star-sharp" style={styles.star}></Ionicons>}
                        emptyStar={<Ionicons name="star-outline" style={styles.star}></Ionicons>}
                        halfStar={<Ionicons name="star-half-sharp" style={styles.star}></Ionicons>}
                        >
                    </Star>
                <Text style={styles.voteCount}>{movie.vote_count}</Text>
                </View>
                
                <Text style={styles.movieReleaseDate}>{movie.release_date}</Text>
                <Text style={styles.overview}>{movie.overview}</Text>
            </View>
        </ScrollView>
    );
}
​
const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#202328'
    },
    textBox: {
        paddingHorizontal:30,
        paddingVertical: 5
    },
    title: {
        color: '#fff',
        fontSize: 26,
        fontWeight: 'bold'
    },
    movieReleaseDate: {
        color: '#ccc',
        marginBottom: 10
    },
    overview: {
        color: '#fff',
        fontSize: 18
    },
    movieImage: {
        height: 480,
        resizeMode: 'contain'
    },
    vote: {
        flexDirection: 'row',
        marginTop: 10,
        alignItems: 'center'
    },
    voteCount: {
        color: '#ccc',
        marginLeft: 3
    },
    star: {
        color: 'yellow',
        backgroundColor: 'transparent',
        textShadowColor: 'black',
        textShadowOffset: {width: 1, height: 1},
        textShadowRadius: 2,
    }
})
★MovieList.js
import { StyleSheet, Text, View, ScrollView, FlatList, Image, TouchableOpacity } from 'react-native';
import { requests } from '../request';
import axios from 'axios';
import { useState, useEffect } from 'react';
​
export default function MovieList({ navigation }) {
  const [nowPlaying, setNowPlaying] = useState({});
  const [commingSoon, setCommingSoon] = useState({});
  const [populars, setPopulars] = useState({});
  const [topRated, setTopRated] = useState({});
  const [picupMovies, setPicupMovies] = useState({});
​
  useEffect(() => {
    async function getMovies() {
      try {
        const nowPlayingMovies = await axios.get(requests.NOW_PLAYING);
        setNowPlaying(nowPlayingMovies.data.results);
​
        const commingSoonMovies = await axios.get(requests.COMMING_SOON);
        setCommingSoon(commingSoonMovies.data.results);
​
        const popularsMovies = await axios.get(requests.POPULARS);
        setPopulars(popularsMovies.data.results);
​
        const topRatedMovies = await axios.get(requests.TOP_RATED);
        setTopRated(topRatedMovies.data.results);
      } catch (error) {
        console.log(error);
      }
    }
    async function getPickUpMovie() {
      try {
        const result = await axios.get(requests.NOW_PLAYING);
        const number = Math.floor(Math.random() * (result.data.results.length - 1) + 1);
        setPicupMovies(result.data.results[number]);
      } catch (error) {
        console.log(error);
      }
    }
    getMovies();
    getPickUpMovie();
  }, []);
  return (
    <ScrollView style={styles.container}>
    <TouchableOpacity onPress={() => navigation.navigate("MovieDetail", {movie: picupMovies})}>
      <View style={styles.pickupContainer}>
        <Image style={styles.pickupImage} source={{uri: `https://image.tmdb.org/t/p/w780${picupMovies.poster_path}`}}></Image>
        <Text style={styles.pickupTitle}>{picupMovies.title}</Text>
      </View>
    </TouchableOpacity>
    
      <Text style={styles.listName}>公開中の映画</Text>
​
      <FlatList
        data={nowPlaying}
        keyExtractor={item => item.id}
        horizontal={true}
        flashScrollIndicators
        renderItem={({ item }) => (
        <TouchableOpacity onPress={() => navigation.navigate("MovieDetail", {movie: item})}>
            <View style={styles.movieContainer}>
            <Image style={styles.movieImage} source={{uri: `https://image.tmdb.org/t/p/w300${item.poster_path}`}}></Image>
            <Text numberOfLines={1} style={styles.movieTitle}>{item.title}</Text>
            </View>
        </TouchableOpacity>
        )}>
      </FlatList>
​
      <Text style={styles.listName}>公開予定の映画</Text>
​
      <FlatList
        data={commingSoon}
        keyExtractor={item => item.id}
        horizontal={true}
        flashScrollIndicators
        renderItem={({ item }) => (
        <TouchableOpacity onPress={() => navigation.navigate("MovieDetail", {movie: item})}>
            <View style={styles.movieContainer}>
            <Image style={styles.movieImage} source={{uri: `https://image.tmdb.org/t/p/w300${item.poster_path}`}}></Image>
            <Text numberOfLines={1} style={styles.movieTitle}>{item.title}</Text>
            </View>
        </TouchableOpacity>
        )}>
      </FlatList>
​
      <Text style={styles.listName}>人気の映画</Text>
​
      <FlatList
        data={populars}
        keyExtractor={item => item.id}
        horizontal={true}
        flashScrollIndicators
        renderItem={({ item }) => (
        <TouchableOpacity onPress={() => navigation.navigate("MovieDetail", {movie: item})}>    
            <View style={styles.movieContainer}>
            <Image style={styles.movieImage} source={{uri: `https://image.tmdb.org/t/p/w300${item.poster_path}`}}></Image>
            <Text numberOfLines={1} style={styles.movieTitle}>{item.title}</Text>
            </View>
        </TouchableOpacity>
        )}>
      </FlatList>
​
      <Text style={styles.listName}>高評価の映画</Text>
​
      <FlatList
        data={topRated}
        keyExtractor={item => item.id}
        horizontal={true}
        flashScrollIndicators
        renderItem={({ item }) => (
        <TouchableOpacity onPress={() => navigation.navigate("MovieDetail", {movie: item})}>    
            <View style={styles.movieContainer}>
            <Image style={styles.movieImage} source={{uri: `https://image.tmdb.org/t/p/w300${item.poster_path}`}}></Image>
            <Text numberOfLines={1} style={styles.movieTitle}>{item.title}</Text>
            </View>
        </TouchableOpacity>
        )}>
      </FlatList>
    </ScrollView>
  );
}
​
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#202328'
  },
  pickupContainer: {
    width: '100%', 
    flexDirection: 'row', 
    alignItems: 'center', 
    justifyContent: 'center'
  },
  pickupImage: {
    height: 350, 
    width: '45%',
    resizeMode: 'contain'
  },
  pickupTitle: {
    color: '#fff', 
    fontSize: 24, 
    fontWeight: 'bold', 
    width: '45%', 
    marginLeft: 5
  },
  listName: {
    color: '#fff', 
    fontSize: 18, 
    fontWeight: 'bold',
  },
  movieContainer: {
    width: 130,
    marginBottom:30
  },
  movieImage: {
    height: 200,
    marginRight: 10,
    resizeMode: 'contain'
  },
  movieTitle: {
    color: '#ccc', 
    fontSize: 14
  }
});

株式会社ロックシステム

「ブラック企業をやっつけろ!!」を企業理念にエンジニアが働きやすい環境をつきつめる大阪のシステム開発会社。2014年会社設立以来、残業時間ほぼゼロを達成し、高い従業員還元率でエンジニアファーストな会社としてIT業界に蔓延るブラックなイメージをホワイトに変えられる起爆剤となるべく日々活動中!絶賛エンジニア募集中。