صفحه جزئیات فیلم (MovieDetailFragment) در اندروید تی وی، ظاهری به شکل عکس بالا خواهد داشت. البته این صفحه برای اپلیکیشن ما قرار هست توضیحات فیلم رو نشون بده، درکل می تونه برای نمایش دیتای هر نوع مدیایی استفاده بشه، آهنگ، هر نوع ویدیو، فیلم یا….

 عملا دو بخش اصلی در هر صفحه جزئیات فیلم هست:

DetailsOverviewRow : که قرار هست دیتا فیلم رو نشون بده، مثل عکس فیلم، عکس کاور، توضیحات، نام فیلم و یک سری دیتا که قرار هست فیلم یا ویدیو رو توصیف کنه.

ListRow: که در صفحه MainFragment هم استفاده کردیم و قرار هست یک لیست از سایر مدیاها نمایش بده. مثلا فیلم های مشابه، سایر ویدیوهای همین کانال، آهنگ های دیگر از همین آلبوم یا….

به احتمال زیاد در این صفحه شما یک DetailsOverviewRow دارید و یک یا چند ListRow برای نمایش یک سری دیتای بیشتر. و این مجموعه (همانطور که در پیاده سازی MainFragment) دیدیم داخل یک ArrayObjectAdapter قرار می¬گیرند. پس ساختار این صفحه به صورت زیر خواهد بود:

ساختار صفحه MovieDetailFragment در اندروید تی وی

برای بخش DetailsOverviewRow دو Presenter آماده توسط خود LeanBack ارائه شده.

  • DetailsOverviewRowPresenter: در حال حاضر deprecate شده ولی بخاطر دیزاین خاصی که داره هنوز هم استفاده میشه پس باهم یاد میگیریمش 😊

Edit My Profile

نحوه نمایش DetailsOverviewRowPresenter در اندروید تی وی
نحوه نمایش FullWidthDetailsOverviewRowPresenter در اندروید تی وی

پس در ادامه به هر دو روش DetailsOverviewRow رو پیاده سازی می­کنیم:

روش پیاده سازی اول: با استفاده از FullWidthDetailsOverviewRowPresenter :

این Presenter از سه بخش اصلی تشکیل شده:

  • یک Logo view که برای نمایش عکس اصلی مدیا هست و البته با پیاده سازی DetailsOverViewLogoPresenter قابل سفارشی کردن هم هست. در غیر این صورت با تابع detailOverviewRow.setImageBitmap  یا detailOverviewRow.setImageDrawable  عکس مدیا را می توان در بخش در نظر گرفته شده نمایش داد.

در آموزش های بعدی می بینیم که چطور می توان برخی از کلاس ها، presenterها و یا viewهای اندروید تی وی رو سفارشی سازی کرد.

  • Action list view که یک دیتای کوتاه یا دکمه های اکشن رو داخلش قرار میدیم و بصورت یک لیست افقی قابل اسکرول نمایش داده می شوند. این دکمه ها قابلیت پیاده سازی اکشن کلیک هم دارند.
  • Detailed description view که باید با پیاده سازی AbstractDetailsDescriptionPresenter برای پروژه شما شخصی سازی بشه و بخش عنوان اصلی، عنوان فرعی و توضیحات فیلم در این قسمت قرار می گیره.

برای ایجاد این صفحه اول یک اکتیویتی به نام MovieDetailsActivity : FragmentActivity()می سازیم با یک layout به نام activity_detail.xml. همینطور یک فرگمنت به اسم MovieDetailsFragment میسازیم که از DetailsSupportFragment ارث برده باشه. توسط کد داخل activity_detail.xml زیر اکتیویتی رو به فرگمنت متصل میکنم.

<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:id="@+id/details_fragment"
android:name="ir.iodroid.androidtvsample.views.fragments.MovieDetailFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".views.activities.MovieDetailsActivity" tools:deviceIds="tv" />

از آنجایی که قرار هست فیلم انتخاب شده توسط کاربر به این صفحه پاس داده بشه، بعد از Parcelable کردن کلاس Movie، با تعریف کد زیر دقیقا همانطور که onItemViewClickedListener رو تعریف کرده بودیم داخل MainFragment، صفحه MovieDetailsActivity  رو باز میکنیم:

onItemViewClickedListener = OnItemViewClickedListener { _, item, _, _ ->
            if (item is Movie){
                startActivity(Intent(activity, MovieDetailsActivity::class.java).putExtra(MovieDetailsFragment.EXTRA_MOVIE
                    , item))
            }
        }

و کد اکتیویتی و فرگمنت مربوط به نمایش جزئیات فیلم، به صورت زیر هست:

class MovieDetailsActivity : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_detail)
    }
}

class MovieDetailsFragment : DetailsSupportFragment() {

    companion object {
        const val EXTRA_MOVIE = "EXTRA_MOVIE"
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        val selectedMovie =
            activity?.intent?.getParcelableExtra<Parcelable>(EXTRA_MOVIE) as Movie
    }
}

حالا به ترتیب بخش های پایین رو انجام میدیم:
1- ایجاد کلاس MovieDetailsDescriptionPresenter برای نمایش اطلاعات متنی فیلم شامل عنوان، عنوان فرعی و توضیحات:

class MovieDetailsDescriptionPresenter : AbstractDetailsDescriptionPresenter() {
    override fun onBindDescription(
        viewHolder: ViewHolder,
        item: Any
    ) {
        val movie =
            item as? Movie
        if (movie != null) {
            viewHolder.title.text = movie.title
            viewHolder.subtitle.text = movie.director
            viewHolder.body.text = movie.description
        }
    }
}

البته در نظر بگیرید که چون بخش توضیحاتی که قبلا داشتیم برای کلاس Movie دیتای زیادی نداشت، من این کلاس رو به شکل زیر تغییر دادم از قبل:

data class Movie(val title: String, val director: String, val coverUrl: String, val description: String): Parcelable{
    constructor(parcel: Parcel) : this(
        parcel.readString(),
        parcel.readString(),
        parcel.readString(),
        parcel.readString()
    ) {
    }

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(title)
        parcel.writeString(director)
        parcel.writeString(coverUrl)
        parcel.writeString(description)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<Movie> {
        override fun createFromParcel(parcel: Parcel): Movie {
            return Movie(parcel)
        }

        override fun newArray(size: Int): Array<Movie?> {
            return arrayOfNulls(size)
        }
    }
}

در ادامه داخل MainFragment کارهای اصلی ما انجام میگیره.

  1. تعریف مقدارهای اولیه در بالای فرگمنت:
companion object {
    private const val DETAIL_THUMB_WIDTH = 300
    private const val DETAIL_THUMB_HEIGHT = 400
    const val EXTRA_MOVIE = "EXTRA_MOVIE"
}

private val detailFullOverviewPresenter =
    FullWidthDetailsOverviewRowPresenter(MovieDetailsDescriptionPresenter())

lateinit var detailOverviewRow: DetailsOverviewRow

3-خواندن مقدار کلاس Movie در زمان ساخته شدن این صفحه و سپس مقدار دهی سایر مقادیر بر اساس این فیلم:

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)

    val selectedMovie =
        activity?.intent?.getParcelableExtra<Parcelable>(EXTRA_MOVIE) as Movie

    detailOverviewRow = DetailsOverviewRow(selectedMovie)

    setMovieThumbnail(selectedMovie)
    setMovieDetailActions()
    setMovieDetailAdapter()
}

4) تابع setMovieThumbnail:
داخل این تابع بعد از لود کردن عکس فیلم توسط Glide آن را به detailOverviewRow پاس میدهم. البته دلیل اینکار این هست که detailOverviewRow فقط Bitmap یا Drawable قبول می کند:

private fun setMovieThumbnail(selectedMovie: Movie) {
    Glide.with(activity!!)
        .asBitmap()
        .load(selectedMovie.coverUrl)
        .apply(RequestOptions().override(DETAIL_THUMB_WIDTH, DETAIL_THUMB_HEIGHT).centerCrop())
        .into(object : CustomTarget<Bitmap>() {
            override fun onLoadCleared(placeholder: Drawable?) {}
            override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
                detailOverviewRow.setImageBitmap(activity, resource)
            }
        })
}

5) تابع setMovieDetailActions
در این متد من اکشن هایی که قرار هست در بالای صفحه فیلم لیست شوند را ایجاد می کنم. این مقادیر می توانند دکمه ای با اکشن مشخص باشند یا صرفا داده ای را نشان دهند. همینطور چهار مقدار میگیرند: شناسه(id) که در صورت تعریف اکشن کلیک استفاده می شوند پس باید منحصر بفرد باشد، عنوان اول، عنوان دوم، آیکون
• در صورت نداشتن هرکدام از این آیکون ها، از گیت پروژه می تونید دانلود کنید یا اینکه برای خودتون یکی ایجاد کنید.

private fun setMovieDetailActions() {
    val actionsAdapter = ArrayObjectAdapter().apply {
        add(Action(1, "Watch").apply {
            icon =
                ContextCompat.getDrawable(requireContext(), R.drawable.ic_play_arrow_white_24dp)
        })
        add(Action(2, "78 People", "Liked the Movie").apply {
            icon =
                ContextCompat.getDrawable(requireContext(), R.drawable.ic_thumb_up_white_24dp)
        })
        add(Action(3, "Watch Trailer"))
    }
    detailOverviewRow.actionsAdapter = actionsAdapter
}

6) تابع generateRelatedMoviesRow برای ایجاد داده هایی در بخش “فیلم های مرتبط”
همینطور که در صفحه MainFragment چهارفیلم قرار دادیم، در اینجا هم دقیقا به همان روش باید یک ListRow ایجاد کنیم که داخلش به کمک MovieViewPresenter چهار فیلم و یک HeaderItem قرار دارد.

private fun generateRelatedMoviesRow(): ListRow {
    val movieViewPresenter = MovieViewPresenter(requireContext())
    val listRowAdapter = ArrayObjectAdapter(movieViewPresenter).apply {
        add(
            Movie(
                "Sleeping Beauty",
                "Directed by: Clyde Geronimi",
                "https://i1.wp.com/www.tor.com/wp-content/uploads/2015/07/Sleeping2-740x360.jpg?fit=740%2C%209999&crop=0%2C0%2C100%2C360px",
                "Sleeping Beauty is a 1959 American animated musical fantasy film produced by Walt Disney based on The Sleeping Beauty by Charles Perrault. The 16th Disney animated feature film, it was released to theaters on January 29, 1959, by Buena Vista Distribution. This was the last Disney adaptation of a fairy tale for some years because of its initial mixed critical reception and underperformance at the box office; the studio did not return to the genre until 30 years later, after Walt Disney died in 1966, with the release of The Little Mermaid (1989)."
            )
        )
        add(
            Movie(
                "Arthur Christmas",
                "Directed by: Sarah Smith, Barry Cook",
                "https://www.gannett-cdn.com/-mm-/b2b05a4ab25f4fca0316459e1c7404c537a89702/c=0-0-1365-768/local/-/media/2018/06/11/USATODAY/usatsports/247WallSt.com-247WS-469696-arthur-christmas.jpg",
                "Arthur Christmas is a 2011 British-American 3D computer-animated Christmas comedy film, produced by Aardman Animations and Sony Pictures Animation as their first collaborative project. The film was released on 11 November 2011, in the UK, and on 23 November 2011, in the US.\n" +
                 "Directed by Sarah Smith and co-directed by Barry Cook,[4] it stars the voices of James McAvoy, Hugh Laurie, Bill Nighy, Jim Broadbent, Imelda Staunton, and Ashley Jensen."
            )
        )
        add(
            (Movie(
                "Frankenweenie",
                "Directed by: Tim Burton",
                "https://www.gannett-cdn.com/-mm-/1f8ac36e35875bcd7baab5ef2b330f818b7ad867/c=12-0-588-324/local/-/media/2018/05/14/USATODAY/usatsports/wp-USAT-allthemoms-front1-11182-frankenweenie.jpg",
                "Frankenweenie is a 2012 American 3D stop motion-animated supernatural horror comedy film directed by Tim Burton and produced by Walt Disney Pictures.[3] It is a remake of Burton's 1984 short film of the same name and is also both a parody of and homage to the 1931 film Frankenstein, based on Mary Shelley's book of the same name. The voice cast includes four actors who worked with Burton on previous films: Winona Ryder (Beetlejuice and Edward Scissorhands); Martin Short (Mars Attacks!); Catherine O'Hara (Beetlejuice and The Nightmare Before Christmas); and Martin Landau (Ed Wood and Sleepy Hollow), along with some new voice actors, such as Charlie Tahan and Atticus Shaffer."
            ))
        )
        add(
            Movie(
                "Winnie the Pooh",
                "Directed by: Stephen J. Anderson, Don Hall",
                "https://www.gannett-cdn.com/-mm-/b2b05a4ab25f4fca0316459e1c7404c537a89702/c=0-0-1365-768/local/-/media/2018/06/11/USATODAY/usatsports/winnie-the-pooh-2011.jpg",
                "Winnie-the-Pooh, also called Pooh Bear and Pooh, is a fictional anthropomorphic teddy bear created by English author A. A. Milne.\n" +
                "The first collection of stories about the character was the book Winnie-the-Pooh (1926), and this was followed by The House at Pooh Corner (1928). Milne also included a poem about the bear in the children's verse book When We Were Very Young (1924) and many more in Now We Are Six (1927). All four volumes were illustrated by E. H. Shepard."
            )
        )
    }
    val headerItem = HeaderItem(0, "Related Movies")

    return ListRow(headerItem, listRowAdapter)
}
  • قطعا این حجم دیتا وسط کد وحشتناکه، به زودی خیلی تمیزتر از Api میخونیم داده هارو 😊

7) نهایتا نمایش داده ها در تابع setMovieDetailAdapter

همینطور که می بینید، داخل این Fragment بر خلاف چیزی که در MainFragment دیدیم، من دو Presenter مختلف دارم، یکی از نوع ListRowPresenter و دیگری از نوع FullWidthDetailsOverviewRowPresenter. برای اینکه صفحه متوجه بشه که این داده ها قرار هست چطور کنار هم قرار بگیرند، از کلاسی به نام ClassPresenterSelector استفاده می کنیم که طبق تعریفش در داکیومنت گوگل:

یک Presenter مناسب با توجه به نوع item انتخاب می کند!

پس ما اینجا داده های بخش جزئیات فیلم و بعدش داده های بخش فیلم های مرتبط رو بهش میدیم، و اون تصمیم میگیره که با توجه به این داده کدام پرزنتر رو باید استفاده کنه:

private fun setMovieDetailAdapter() {
    val classPresenterSelector = ClassPresenterSelector().apply {
        addClassPresenter(DetailsOverviewRow::class.java, detailFullOverviewPresenter)
        addClassPresenter(ListRow::class.java, ListRowPresenter())
    }

    val detailPageAdapter = ArrayObjectAdapter(classPresenterSelector).apply {
        add(detailOverviewRow)
        add(generateRelatedMoviesRow())
    }

    setAdapter(detailPageAdapter)
}

همینطور که می بینید در هنگام تعریف این کلاس، گفتم که یا داده از از نوع DetailOverviewRow میگیره، که باید برای این بخش از FullWidthDetailsOverviewRowPresenter که بالاتر تعریف شده استفاده کنه، یا داده ای از جنس ListRow می گیره که در این صورت از کلاس ListRowPresenter پیشفرض خود LeanBack باید استفاده کنه. در ادامه این مقادیر رو بهش دادم و در نهایت آداپتر MovieDetailsFragment رو مقدار دهی کردم.

و حالاا اجرا (با اسکرول صفحه) :

روش پیاده سازی دوم: با استفاده از DetailsOverviewRowPresenter:
کافیه فقط در کد قبل، FullWidthDetailsOverviewRowPresenter رو با DetailsOverviewRowPresenter در MovieDetailsFragment جایگزین کنین! و تمام!

طبق قول کدها قرار هست داخل گیت قرار بگیرند. پس من داخل ریپوزیتوری گیتهابی که به این پروژه تخصیص دادم، روی برنچ iodroid_tv/add_background_manager کد این بخش از آموزش رو قرار دادم. (طبیعتا کد نهایی هم روی برنج master موجود هست)
برای دسترسی به این شاخه از پروژه گیتهاب کلیک کنید 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *
You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>