코리아 IT아카데미/android

15일차 | 지금까지 한 것 다 합한 예제

Sharon kim 2022. 3. 7. 18:24

//https://eva.design/

https://yts.lt/api/v2/list_movies.json?limit=20&page=1&sort_by=rating

https://www.jsonschema2pojo.org/

 

Eva Design System

Open Source Code Libraries No need to spend thousands of hours for implementation, testing and maintenance. Eva is supported by well known Angular and React Native UI component libraries. More to come.

eva.design

 

jsonschema2pojo

Reference properties For each property present in the 'properties' definition, we add a property to a given Java class according to the JavaBeans spec. A private field is added to the parent class, along with accompanying accessor methods (getter and sette

www.jsonschema2pojo.org

 

코드 추가

AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET" />

build.gradle


 buildTypes {
       
    ...

    buildFeatures{
//        dataBinding true
        viewBinding {
            enabled true
        }
    }
}

def retrofit_version ="2.9.0"

dependencies {

     ...

    implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
    implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"

    implementation 'com.github.bumptech.glide:glide:4.12.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'

    implementation 'de.hdodenhof:circleimageview:3.1.0'
}

Adapter>
MovieAdapter

package com.example.ytsmovie.Adapter;

import android.annotation.SuppressLint;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RatingBar;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.FitCenter;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.example.ytsmovie.models.Movie;
import com.example.ytsmovie.R;

import java.util.ArrayList;
import java.util.List;

public class MovieAdapter extends RecyclerView.Adapter<MovieAdapter.MyViewHolder> {

    private List<Movie> list = new ArrayList<>();
    private static final String TAG = MovieAdapter.class.getName();

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View movieItemView = inflater.inflate(R.layout.item_movie_card, parent, false);
        return new MyViewHolder(movieItemView); //R.id.layout.item_movie_card
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        Movie movie = list.get(position);
        holder.setItem(movie);
    }

    @Override
    public int getItemCount() {
        return list.size();
    }


    //내부 클래스
    public static class MyViewHolder extends RecyclerView.ViewHolder {
        ImageView posterIv;
        TextView titleTv;
        TextView ratingTv;
        RatingBar ratingBar;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);

            posterIv = itemView.findViewById(R.id.posterIv);
            titleTv = itemView.findViewById(R.id.titleTV);
            ratingTv = itemView.findViewById(R.id.ratingTV);
            ratingBar = itemView.findViewById(R.id.ratingBar);

        }

        public void setItem(Movie movie) {
            titleTv.setText(movie.getTitle());
            ratingTv.setText(String.valueOf(movie.getRating()));
            Glide.with(posterIv.getContext())
                    .load(movie.getMediumCoverImage())
                    .transform(new FitCenter(), new RoundedCorners(20))
                    .placeholder(R.drawable.round_image)
                    .into(posterIv);
            Log.d(TAG, "movie.getRating() :" + movie.getRating());
            ratingBar.setRating(movie.getRating().floatValue());
        }
    }

    //통신으로 데이터가 전달되면 여기 메서드로 데이터를 전달 받게 한다.
    @SuppressLint("NotifyDataSetChanged")
    public void addItems(List<Movie> list) {
        this.list = list;
        this.notifyDataSetChanged();
    }
}

 

models>
Data
Meta
Movie
YtsData


package com.example.ytsmovie.models;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

import java.util.List;

public class Data {

    @SerializedName("movie_count")
    @Expose
    private Integer movieCount;
    @SerializedName("limit")
    @Expose
    private Integer limit;
    @SerializedName("page_number")
    @Expose
    private Integer pageNumber;
    @SerializedName("movies")
    @Expose
    private List<Movie> movies = null;

    public Integer getMovieCount() {
        return movieCount;
    }

    public void setMovieCount(Integer movieCount) {
        this.movieCount = movieCount;
    }

    public Integer getLimit() {
        return limit;
    }

    public void setLimit(Integer limit) {
        this.limit = limit;
    }

    public Integer getPageNumber() {
        return pageNumber;
    }

    public void setPageNumber(Integer pageNumber) {
        this.pageNumber = pageNumber;
    }

    public List<Movie> getMovies() {
        return movies;
    }

    public void setMovies(List<Movie> movies) {
        this.movies = movies;
    }

}

 


package com.example.ytsmovie.models;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

public class Meta {

    @SerializedName("server_time")
    @Expose
    private Integer serverTime;
    @SerializedName("server_timezone")
    @Expose
    private String serverTimezone;
    @SerializedName("api_version")
    @Expose
    private Integer apiVersion;
    @SerializedName("execution_time")
    @Expose
    private String executionTime;

    public Integer getServerTime() {
        return serverTime;
    }

    public void setServerTime(Integer serverTime) {
        this.serverTime = serverTime;
    }

    public String getServerTimezone() {
        return serverTimezone;
    }

    public void setServerTimezone(String serverTimezone) {
        this.serverTimezone = serverTimezone;
    }

    public Integer getApiVersion() {
        return apiVersion;
    }

    public void setApiVersion(Integer apiVersion) {
        this.apiVersion = apiVersion;
    }

    public String getExecutionTime() {
        return executionTime;
    }

    public void setExecutionTime(String executionTime) {
        this.executionTime = executionTime;
    }

}

 


package com.example.ytsmovie.models;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

import java.util.List;

public class Movie {

    @SerializedName("id")
    @Expose
    private Integer id;
    @SerializedName("url")
    @Expose
    private String url;
    @SerializedName("imdb_code")
    @Expose
    private String imdbCode;
    @SerializedName("title")
    @Expose
    private String title;
    @SerializedName("title_english")
    @Expose
    private String titleEnglish;
    @SerializedName("title_long")
    @Expose
    private String titleLong;
    @SerializedName("slug")
    @Expose
    private String slug;
    @SerializedName("year")
    @Expose
    private Integer year;
    @SerializedName("rating")
    @Expose
    private Float rating;
    @SerializedName("runtime")
    @Expose
    private Integer runtime;
    @SerializedName("genres")
    @Expose
    private List<String> genres = null;
    @SerializedName("summary")
    @Expose
    private String summary;
    @SerializedName("description_full")
    @Expose
    private String descriptionFull;
    @SerializedName("synopsis")
    @Expose
    private String synopsis;
    @SerializedName("yt_trailer_code")
    @Expose
    private String ytTrailerCode;
    @SerializedName("language")
    @Expose
    private String language;
    @SerializedName("mpa_rating")
    @Expose
    private String mpaRating;
    @SerializedName("background_image")
    @Expose
    private String backgroundImage;
    @SerializedName("background_image_original")
    @Expose
    private String backgroundImageOriginal;
    @SerializedName("small_cover_image")
    @Expose
    private String smallCoverImage;
    @SerializedName("medium_cover_image")
    @Expose
    private String mediumCoverImage;
    @SerializedName("large_cover_image")
    @Expose
    private String largeCoverImage;
    @SerializedName("state")
    @Expose
    private String state;


    @SerializedName("date_uploaded")
    @Expose
    private String dateUploaded;
    @SerializedName("date_uploaded_unix")
    @Expose
    private Integer dateUploadedUnix;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getImdbCode() {
        return imdbCode;
    }

    public void setImdbCode(String imdbCode) {
        this.imdbCode = imdbCode;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getTitleEnglish() {
        return titleEnglish;
    }

    public void setTitleEnglish(String titleEnglish) {
        this.titleEnglish = titleEnglish;
    }

    public String getTitleLong() {
        return titleLong;
    }

    public void setTitleLong(String titleLong) {
        this.titleLong = titleLong;
    }

    public String getSlug() {
        return slug;
    }

    public void setSlug(String slug) {
        this.slug = slug;
    }

    public Integer getYear() {
        return year;
    }

    public void setYear(Integer year) {
        this.year = year;
    }

    public Float getRating() {
        return rating;
    }

    public void setRating(Float rating) {
        this.rating = rating;
    }

    public Integer getRuntime() {
        return runtime;
    }

    public void setRuntime(Integer runtime) {
        this.runtime = runtime;
    }

    public List<String> getGenres() {
        return genres;
    }

    public void setGenres(List<String> genres) {
        this.genres = genres;
    }

    public String getSummary() {
        return summary;
    }

    public void setSummary(String summary) {
        this.summary = summary;
    }

    public String getDescriptionFull() {
        return descriptionFull;
    }

    public void setDescriptionFull(String descriptionFull) {
        this.descriptionFull = descriptionFull;
    }

    public String getSynopsis() {
        return synopsis;
    }

    public void setSynopsis(String synopsis) {
        this.synopsis = synopsis;
    }

    public String getYtTrailerCode() {
        return ytTrailerCode;
    }

    public void setYtTrailerCode(String ytTrailerCode) {
        this.ytTrailerCode = ytTrailerCode;
    }

    public String getLanguage() {
        return language;
    }

    public void setLanguage(String language) {
        this.language = language;
    }

    public String getMpaRating() {
        return mpaRating;
    }

    public void setMpaRating(String mpaRating) {
        this.mpaRating = mpaRating;
    }

    public String getBackgroundImage() {
        return backgroundImage;
    }

    public void setBackgroundImage(String backgroundImage) {
        this.backgroundImage = backgroundImage;
    }

    public String getBackgroundImageOriginal() {
        return backgroundImageOriginal;
    }

    public void setBackgroundImageOriginal(String backgroundImageOriginal) {
        this.backgroundImageOriginal = backgroundImageOriginal;
    }

    public String getSmallCoverImage() {
        return smallCoverImage;
    }

    public void setSmallCoverImage(String smallCoverImage) {
        this.smallCoverImage = smallCoverImage;
    }

    public String getMediumCoverImage() {
        return mediumCoverImage;
    }

    public void setMediumCoverImage(String mediumCoverImage) {
        this.mediumCoverImage = mediumCoverImage;
    }

    public String getLargeCoverImage() {
        return largeCoverImage;
    }

    public void setLargeCoverImage(String largeCoverImage) {
        this.largeCoverImage = largeCoverImage;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }


    public String getDateUploaded() {
        return dateUploaded;
    }

    public void setDateUploaded(String dateUploaded) {
        this.dateUploaded = dateUploaded;
    }

    public Integer getDateUploadedUnix() {
        return dateUploadedUnix;
    }

    public void setDateUploadedUnix(Integer dateUploadedUnix) {
        this.dateUploadedUnix = dateUploadedUnix;
    }

}

 


package com.example.ytsmovie.models;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

public class YtsData {

    @SerializedName("status")
    @Expose
    private String status;
    @SerializedName("status_message")
    @Expose
    private String statusMessage;
    @SerializedName("data")
    @Expose
    private Data data;


    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getStatusMessage() {
        return statusMessage;
    }

    public void setStatusMessage(String statusMessage) {
        this.statusMessage = statusMessage;
    }

    public Data getData() {
        return data;
    }

    public void setData(Data data) {
        this.data = data;
    }

}

repository>
MovieService

package com.example.ytsmovie.repository;

import com.example.ytsmovie.models.YtsData;
import com.example.ytsmovie.Utils.Define;

import retrofit2.Call;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.GET;
import retrofit2.http.Query;

//https://yts.lt/api/v2/list_movies.json?limit=20&page=1&sort_by=rating
public interface MovieService {

    //public static final이 생략되어 있음
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(Define.BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build();

    //dto 만들어 주기
    @GET("list_movies.json")
    Call<YtsData> repoContributors(@Query("sort_by") String sortBy,
                                   @Query("page") int page,
                                   @Query("limit") int limit);

}

Utils>
Define
FragmentType

package com.example.ytsmovie.Utils;

public class Define {
    public static String BASE_URL = "https://yts.lt/api/v2/";
}

 

package com.example.ytsmovie.Utils;
        //상수값
public enum FragmentType {
    MOVIE, INFO
}

InfoFragment

package com.example.ytsmovie;

import android.icu.text.IDNA;
import android.os.Bundle;

import androidx.fragment.app.Fragment;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.example.ytsmovie.databinding.FragmentInfoBinding;
import com.example.ytsmovie.databinding.FragmentMovieBinding;


public class InfoFragment extends Fragment {
    //view Binding
    private FragmentInfoBinding binding;
    //싱글톤 패턴 적용
    private static InfoFragment infoFragment;


    public InfoFragment() {
        // Required empty public constructor
    }


    public static InfoFragment getInstance() {
        if (infoFragment == null){
            infoFragment = new InfoFragment();
        }
        return infoFragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        binding = FragmentInfoBinding.inflate(inflater, container, false);
        return binding.getRoot();
    }
}


MainActivity

package com.example.ytsmovie;

import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;

import android.os.Bundle;

import com.example.ytsmovie.Utils.FragmentType;
import com.example.ytsmovie.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {
    
    //1. viewBind 사용하기
     private ActivityMainBinding binding;
    private static final String TAG = MainActivity.class.getName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //뷰 바인딩 설정 방법 (Activity)
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        initData();
        addBottomNavigationListener();
        replaceFragment(FragmentType.MOVIE);

        
    }

    private void initData() {

    }

    private void replaceFragment(FragmentType type){
        Fragment fragment;
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        if (type == FragmentType.MOVIE) {
            fragment = MovieFragment.getInstance();
        } else {
            fragment = InfoFragment.getInstance();
        }
        transaction.replace(binding.mainContainer.getId(), fragment);
        transaction.commit();
        // 매니저
        // 트랜젝션

    }
    private void addBottomNavigationListener(){
        binding.bottomNavigation.setOnItemSelectedListener(item -> {
            switch (item.getItemId()){
                case R.id.page_1:
                    replaceFragment(FragmentType.MOVIE);
                    break;
                case R.id.page_2:
                    replaceFragment(FragmentType.INFO);
                    break;
            }
            return true;
        });
    }
}


MovieFragment

package com.example.ytsmovie;

import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;

import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.example.ytsmovie.Adapter.MovieAdapter;
import com.example.ytsmovie.models.Movie;
import com.example.ytsmovie.models.YtsData;
import com.example.ytsmovie.databinding.FragmentMovieBinding;
import com.example.ytsmovie.repository.MovieService;

import java.util.List;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;


public class MovieFragment extends Fragment {

    private static final String TAG = MovieFragment.class.getName();
    // view Binding
    private FragmentMovieBinding binding;

    // 싱글톤 패턴
    private static MovieFragment movieFragment;
    private MovieAdapter movieAdapter;
    private LinearLayoutManager linearLayoutManager;

    private MovieService service;

    private MovieFragment() {
        // Required empty public constructor
    }


    public static MovieFragment getInstance() {
        if (movieFragment == null) {
            movieFragment = new MovieFragment();
        }
        return movieFragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        service = MovieService.retrofit.create(MovieService.class);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // 플래그먼트에서 view binding 사용 방법
        binding = FragmentMovieBinding.inflate(inflater, container, false);
        return binding.getRoot();
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        initRecyclerView();
        requestMoviesData();
    }

    private void initRecyclerView() {
        //1. 어댑터 필요
        //2. 매니저 필요
        //3. 세팅
        movieAdapter = new MovieAdapter();
        linearLayoutManager = new LinearLayoutManager(getContext());
        binding.movieRecyclerView.setAdapter(movieAdapter);
        binding.movieRecyclerView.setLayoutManager(linearLayoutManager);
        binding.movieRecyclerView.hasFixedSize();

    }

    private void requestMoviesData() {
    service.repoContributors("rating", 1, 10)
            .enqueue(new Callback<YtsData>() {
                @Override
                public void onResponse(Call<YtsData> call, Response<YtsData> response) {
                    Log.d(TAG, "status code" + response.code());
                    if (response.isSuccessful()){
                        YtsData ytsData = response.body();
                        List<Movie> list = ytsData.getData().getMovies();
                        movieAdapter.addItems(ytsData.getData().getMovies());
                    }else{
                        Log.d(TAG, response.errorBody().toString());
                    }
                }

                @Override
                public void onFailure(Call<YtsData> call, Throwable t) {
                    Log.d(TAG, t.getMessage());
                }
            });
    }
}

 

drawable 
아이콘 3개 생성

 

menu>
bottom_navigation_menu
top_app_bar

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/page_1"
        android:icon="@drawable/ic_baseline_movie_creation_24"
        android:title="@string/str_movie" />

    <item
        android:id="@+id/page_2"
        android:icon="@drawable/ic_baseline_info_24"
        android:title="@string/str_info" />

</menu>

 

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/favorite"
        android:icon="@drawable/ic_baseline_favorite_24"
        android:title="@string/str_favorite"
        app:showAsAction="ifRoom"/>

</menu>

 

layout>
activity_main
fragment_info
fragment_movie
item_movie_card

<?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=".MainActivity">

    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/topAppBar"
        style="@style/Widget.MaterialComponents.Toolbar.Primary"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:menu="@menu/top_app_bar"
        app:title="@string/app_name" />

    <LinearLayout

        android:id="@+id/mainContainer"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:orientation="vertical"
        app:layout_constraintBottom_toTopOf="@id/bottomNaviContainer"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/topAppBar" />

    <LinearLayout
        android:id="@+id/bottomNaviContainer"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/mainContainer"
        tools:layout_height="48dp">

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottomNavigation"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            style="@style/Widget.MaterialComponents.BottomNavigationView.Colored"
            app:menu="@menu/bottom_navigation_menu" />

    </LinearLayout>


</androidx.constraintlayout.widget.ConstraintLayout>

 

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/info"
    tools:context=".InfoFragment">

    <!-- TODO: Update blank fragment layout -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/hello_blank_fragment" />

</FrameLayout>

 

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

<!--    리사이클러뷰 선언-->
    <androidx.recyclerview.widget.RecyclerView
        tools:listitem="layout/item_movie_card"
        android:orientation="vertical"
        android:id="@+id/movieRecyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>


</androidx.constraintlayout.widget.ConstraintLayout>

 

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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="wrap_content">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <androidx.cardview.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="120dp"
            android:layout_marginStart="20dp"
            android:layout_marginTop="20dp"
            android:layout_marginEnd="20dp"
            android:layout_marginBottom="20dp"
            android:backgroundTint="@color/danger"
            app:cardCornerRadius="10dp">

            <androidx.constraintlayout.widget.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <androidx.constraintlayout.widget.Guideline
                    android:id="@+id/guideline1"
                    android:layout_width="1dp"
                    android:layout_height="match_parent"
                    android:orientation="vertical"
                    app:layout_constraintGuide_begin="135dp" />

                <androidx.constraintlayout.widget.Guideline
                    android:id="@+id/guideline2"
                    android:layout_width="1dp"
                    android:layout_height="match_parent"
                    android:orientation="vertical"
                    app:layout_constraintGuide_end="16dp" />

                <TextView
                    android:id="@+id/titleTV"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:ellipsize="end"
                    android:gravity="end"
                    android:maxLines="2"
                    android:textColor="@color/black"
                    app:layout_constraintBottom_toTopOf="@id/ratingTV"
                    app:layout_constraintEnd_toEndOf="@id/guideline2"
                    app:layout_constraintStart_toStartOf="@id/guideline1"
                    app:layout_constraintTop_toTopOf="parent"
                    app:layout_constraintVertical_bias="0.7"
                    app:layout_constraintVertical_chainStyle="packed"
                    tools:text="벤자민 버튼의 시간은 거꾸로 간다 " />

                <TextView
                    android:id="@+id/ratingTV"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:gravity="end"
                    android:text="9.9"
                    app:layout_constraintBottom_toTopOf="@id/ratingBar"
                    app:layout_constraintEnd_toEndOf="@id/guideline2"
                    app:layout_constraintStart_toStartOf="@id/guideline1"
                    app:layout_constraintTop_toBottomOf="@id/titleTV" />

                <RatingBar
                    android:id="@+id/ratingBar"
                    style="?ratingBarStyleSmall"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:numStars="10"
                    tools:rating="1"
                    android:stepSize="1"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintEnd_toEndOf="@id/guideline2"
                    app:layout_constraintStart_toStartOf="@id/guideline1"
                    app:layout_constraintTop_toBottomOf="@id/ratingTV" />

            </androidx.constraintlayout.widget.ConstraintLayout>
        </androidx.cardview.widget.CardView>
    </RelativeLayout>

    <ImageView
        android:id="@+id/posterIv"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_marginStart="35dp"
        android:layout_marginTop="10dp"
        android:scaleType="fitCenter"
        android:src="@drawable/round_image" />

</FrameLayout>