본문 바로가기

프로그래밍/영화 TMDB API

영화 정보 앱 만들기 - TMDB API 사용, 인기 영화 정보 가져오기 2

반응형

인기 영화 정보 가져오기

 

완성하면 아래 앱이 됩니다.

https://play.google.com/store/apps/details?id=com.enigmah2k.movieinfo

 

영화정보 - Google Play 앱

영화 또는 TV 시리즈 정보를 검색할 수 있습니다. TMDB API를 사용하여 만들었습니다. 한국에 소개되지 않은 컨텐츠를 찾을 수 있습니다.

play.google.com

 

서버에서 movie/popular 를 호출하여 영화목록을 가져오는 것 까지 하였습니다.

 

이번에는 recyclerview 와 CardView 를 적용하여 아래와 같이 나타나도록 구현해 보겠습니다.

 

데이터를 잘 가져올 수 있다는 전제로 필요한 사항은 다음과 같습니다.

 

1. 개별 영화 아이템을 구성할 layout - CardView

2. 영화 목록을 보여줄 카테고리 layout - RecyclerView

3. 내용을 채울 어뎁터 - Adapter

 

RecyclerView 사용법만 알면 됩니다. 

즉, RecyclerView 사용법에 대한 사전 학습이 되어 있어야 합니다.

 

1. 개별 영화 아이템 layout 구성 - item_movie

아래와 같이 개별 영화 정보를 표시하는 layout을 구성해 보겠습니다.

\TMDBtest\app\src\main\res\layout 폴더에 item_movie.xml 을 생성합니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="128dp"
    android:layout_height="192dp"
    android:layout_marginEnd="8dp"
    app:cardCornerRadius="4dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/item_movie_poster"
            android:layout_width="match_parent"
            android:layout_height="172dp" />

        <TextView
            android:id="@+id/item_movie_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Title" />
    </LinearLayout>

</androidx.cardview.widget.CardView>

CardView 로 만들었고, ImageView 와 TextView 를 구성하였습니다.

 

 

2. 영화 목록 카테고리 layout - fragment_movie

MovieFragment.kt를 만들었으므로, fragment_movie.xml 에 구현하면 됩니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.movie.MovieFragment">

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <LinearLayout
                android:id="@+id/linearLayout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:layout_constraintTop_toTopOf="parent">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="16dp"
                    android:layout_marginTop="16dp"
                    android:layout_marginEnd="16dp"
                    android:text="@string/popular"
                    android:textSize="18sp"
                    android:textStyle="bold" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="16dp"
                    android:layout_marginEnd="16dp"
                    android:text="@string/most_popular" />

                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/popular_movies"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="8dp"
                    android:clipToPadding="false"
                    android:paddingStart="16dp"
                    android:paddingEnd="16dp" />

            </LinearLayout>

        </LinearLayout>
    </ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

 

향후 Top rated 정보나 Now playing 등 카테고리 확장을 위해 ScrollView 로 적용하였습니다.

제목과 세부설명은 TextView 로 영화 정보는 RecyclerView 로 구성하였습니다.

 

 

3. Adapter 구현 - MoviesAdapter.kt

class MoviesAdapter (var movies: MutableList<Movie>) : RecyclerView.Adapter<MoviesAdapter.MovieViewHolder>(){

    inner class MovieViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val poster: ImageView = itemView.findViewById(R.id.item_movie_poster)
        fun bind(movie: Movie) {
            Glide.with(itemView)
                .load("https://image.tmdb.org/t/p/w342${movie.poster_path}")
                .transform(CenterCrop())
                .into(poster)
            itemView.item_movie_title.text = movie.title
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder {
        val view = LayoutInflater
            .from(parent.context)
            .inflate(R.layout.item_movie, parent, false)
        return MovieViewHolder(view)
    }

    override fun getItemCount(): Int = movies.size

    override fun onBindViewHolder(holder: MovieViewHolder, position: Int) {
        holder.bind(movies[position])
    }

    fun appendMovies(movies: List<Movie>) {
        this.movies.addAll(movies)
        notifyItemRangeInserted(
            this.movies.size,
            movies.size - 1
        )
    }
}

 

item_movie.xml 로 구성된 개별 영화 정보 layout으로 ViewHolder를 구성합니다.

이미지는 Glide 를 사용하여 load 합니다.

 

실제 이미지가 있는 URL은 https://image.tmdb.org/t/p/ 입니다.

 

w342 는 이미지의 사이즈를 나타냅니다.

이미지 경로와 사이즈는 아래 API 명세에 잘 나와 있습니다.

https://developers.themoviedb.org/3/configuration/get-api-configuration

 

{
  "images": {
    "base_url": "http://image.tmdb.org/t/p/",
    "secure_base_url": "https://image.tmdb.org/t/p/",
    "backdrop_sizes": [
      "w300",
      "w780",
      "w1280",
      "original"
    ],
    "logo_sizes": [
      "w45",
      "w92",
      "w154",
      "w185",
      "w300",
      "w500",
      "original"
    ],
    "poster_sizes": [
      "w92",
      "w154",
      "w185",
      "w342",
      "w500",
      "w780",
      "original"
    ],

 

영화 정보를 가져왔을 때 이미지 위치는 base url 과 사이즈 값이 없는 파일명만 넘어옵니다.

 

 

4. MoviesAdapter 실행

영화 정보 결과를 RecyclerView 에 적용할 수 있도록 Adapter를 아래 형식으로 구성합니다.

    private lateinit var popularMovies: RecyclerView
    private lateinit var popularMoviesAdapter: MoviesAdapter
    private lateinit var popularMoviesLayoutMgr: LinearLayoutManager

    override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle? ): View? {
        root = inflater.inflate(R.layout.fragment_movie, container, false)

        popularMovies = root.findViewById(R.id.popular_movies)
        popularMoviesLayoutMgr = LinearLayoutManager(
            context,
            LinearLayoutManager.HORIZONTAL,
            false
        )
        popularMovies.layoutManager = popularMoviesLayoutMgr
        popularMoviesAdapter = MoviesAdapter(mutableListOf())
        popularMovies.adapter = popularMoviesAdapter

        getPopularMovies()

        return root
    }

    private fun onPopularMoviesFetched(movies: List<Movie>) {
        popularMoviesAdapter.appendMovies(movies)
    }

 

onPopularMoviesFetched() 에서 영화 정보를 append 합니다.

 

실행을 하면 아래와 같이 잘 나타납니다.

하지만 처음 호출 결과 인 첫페이지 정보 만큼만 스크롤 됩니다.

더 많은 정보를 가져오기 위해 추가 페이지 결과를 표시하는 것이 필요합니다.

 

 

5. 다음 페이지 정보 추가 

 

page 값을 저장하는 변수를 선언합니다.

private var popularMoviesPage = 1

 

getPopularMovies() 호출시 전달하는 page 값을 popularMoviesPage 로 변경합니다.

 

스크롤이 넘어갈 경우 다음 페이지 정보를 호출하는 OnScrollListener 를 추가합니다.

    private fun attachPopularMoviesOnScrollListener() {
        popularMovies.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                val totalItemCount = popularMoviesLayoutMgr.itemCount
                val visibleItemCount = popularMoviesLayoutMgr.childCount
                val firstVisibleItem = popularMoviesLayoutMgr.findFirstVisibleItemPosition()

                if (firstVisibleItem + visibleItemCount >= totalItemCount / 2) {
                    popularMovies.removeOnScrollListener(this)
                    popularMoviesPage++
                    getPopularMovies()
                }
            }
        })
    }

 

onPopularMoviesFetched() 에서 attachPopularMoviesOnScrollListener() 를 호출합니다.

    private fun onPopularMoviesFetched(movies: List<Movie>) {
        popularMoviesAdapter.appendMovies(movies)
        attachPopularMoviesOnScrollListener()
    }

 

 

이렇게 하면 스크롤이 계속 됩니다.

 

 

MovieFragment.kt 의 전체 코드는 아래와 같습니다.

class MovieFragment : Fragment() {
    lateinit var root: View

    private lateinit var popularMovies: RecyclerView
    private lateinit var popularMoviesAdapter: MoviesAdapter
    private lateinit var popularMoviesLayoutMgr: LinearLayoutManager
    private var popularMoviesPage = 1

    override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle? ): View? {
        root = inflater.inflate(R.layout.fragment_movie, container, false)

        popularMovies = root.findViewById(R.id.popular_movies)
        popularMoviesLayoutMgr = LinearLayoutManager(
            context,
            LinearLayoutManager.HORIZONTAL,
            false
        )
        popularMovies.layoutManager = popularMoviesLayoutMgr
        popularMoviesAdapter = MoviesAdapter(mutableListOf())
        popularMovies.adapter = popularMoviesAdapter

        getPopularMovies()

        return root
    }

    private fun getPopularMovies() {
        MoviesRepository.getPopularMovies(
            popularMoviesPage,
            ::onPopularMoviesFetched,
            ::onError
        )
    }

    private fun onPopularMoviesFetched(movies: List<Movie>) {
        popularMoviesAdapter.appendMovies(movies)
        attachPopularMoviesOnScrollListener()
    }

    private fun attachPopularMoviesOnScrollListener() {
        popularMovies.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                val totalItemCount = popularMoviesLayoutMgr.itemCount
                val visibleItemCount = popularMoviesLayoutMgr.childCount
                val firstVisibleItem = popularMoviesLayoutMgr.findFirstVisibleItemPosition()

                if (firstVisibleItem + visibleItemCount >= totalItemCount / 2) {
                    popularMovies.removeOnScrollListener(this)
                    popularMoviesPage++
                    getPopularMovies()
                }
            }
        })
    }

    private fun onError() {
        Toast.makeText(activity, "error Movies", Toast.LENGTH_SHORT).show()
    }

}

 

 

다음에는 API를 추가하여 Top rated, Upcomming, Now playing, Discover 카테고리를 추가해 보겠습니다.

 

TMDB API 를 활용한 Android 앱 만들기

https://stockant.tistory.com/530

 

 

반응형