یکی از View هایی که در کتابخانه leanback بصورت پیشفرض موجود است، BaseCardView هست. یک layout شبیه CardView که از سه جزء اصلی Main, Info و Extra تشکیل شده. بخش Main همیشه نمایش داده می¬شود در حالی که بخش های info و extra بر اساس وضعیت(State) فعلی ممکن است نمایش داده نشوند. این وضعیت بر اساس دو فانکشن setSelected و setActivated قایب تغییر است. البته برای استفاده از این کلاس از آنجایی که DesignLayout تعریف شده ای ندارد، از ImageCardView که مستقیما از BaseCardView ارث می¬¬برد استفاده می¬کنیم. این کلاس همانند کلاس پدرش هست که در بخش Main(قسمتی که همیشه نمایش داده میشود) یک ImageView دارد.
بیاید با مثال و عکس بهتر متوجه این توضیحات شویم. همینطور که گفتم ImageCardView از سه جزء اصلی زیر تشکیل شده:

ساختار ImageCardView

در بخش اصلی (main)، یک عکس قرار میگیره، در بخش info یک عنوان و یک content محتوایی داریم و در بخش badge یک آیکون قابل نمایش هست، که دو بخش آخر ممکن هست وجود داشته باشند یا اصلا استفاده نشوند، و یا مقدار داشته باشند اما با توجه به وضعیت مقدار داده شده به imageCardView، hide باشند.

برای اینکه بجای ItemsPresenter (که در آموزش قبل از آن استفاده کردیم ) در RowsAdapter، از MovieViewPresenter استفاده کنم که قرار هست داخلش ImageCardView ها نمایش داده شوند.
به پروژه ای که ساختیم اول یک کلاس به نام Movie ایجاد می¬کنم که قرار هست نوع داده ای باشد که در سطرهایمان میخواهیم نمایش دهیم. پس کلاس زیر رو ایجاد میکنم:
data class Movie(val description: String, val title: String, val coverUrl: String)

هر فیلم (فعلا) شامل عنوان، توضیحات و لینک عکس کاور هست. در بخش بعدی Presenter ای برای نمایش ImageCardView ها به دو روش ایجاد می¬کنم:


1- پیاده سازی بدون استفاده از ViewHolder:
class MovieViewPresenter(val context: Context): Presenter() {_}

که context هم از بیرون به این کلاس بعنوان ورودی می¬دهیم (و در جلوتر استفاده خواهد شد)
در داخل این کلاس متدهای زیر باید Override شوند:

 override fun onCreateViewHolder(parent: ViewGroup?): ViewHolder {
     val view = ImageCardView(context).apply {
         isFocusable = true
         setMainImageDimensions(300, 150)
     }
     return ViewHolder(view)
 }

در اینجا یک نمونه از کلاس ImageCardView ساختم و با متد setMainImageDimentsions برای این بخش main که شامل یک ImageView هست طول و عرض تعیین می¬کنم.
در نهایت نمونه ای از ViewHolder خود leanBack رو بر¬میگردونم و این view ک ساختم رو به constructor این کلاس پاس می¬دهم.
برای مقدار دهی این CardView اول کتابخانه Glide رو برای لود کردن عکس ها در ImageView به پروژه اضافه میکنم. و البته دسترسی INTERNET برای خوندن url ها نیز در فایل manifest باید اضافه بشه:

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

در نهایت داخل متد onBindViewHolder به ImageCardView مقدار می¬دهم.

override fun onBindViewHolder(viewHolder: ViewHolder?, item: Any?) {
     (item as? Movie)?.let {movie->
         (viewHolder?.view as? ImageCardView)?.apply {
             titleText = movie.title
             contentText = movie.description
             Glide.with(context).load(movie.coverUrl).apply(RequestOptions().centerCrop()).into(this.mainImageView)
         }
     }
}

دو ورودی viewHolder و item برای ما از جنس ImageCardView و Movie هستند و با علم به این قضیه با خیال راحت cast می¬کنیم تا راحت تر مقادیر رو بخونیم. مقادیر زیر در ImageCardView مشخص هستند:
titleText: عنوان در بخش info
contentText: محتوا در بخش info
badgeImage: یک آیکون در زیر بخش info (که در اینجا یک عکس را که به drawable اضافه کردم به آن دادم)
mainImageView: عکس در بخش main که توسط Glide مقدار آن را از coverUrl فیلم می¬گیرم
و مقداردهی😊
من یک سری فیلم با کاور به آداپترم اضافه می¬کنم و برای این کار باید تغییرات زیر را در MainFragment اعمال کنیم:

 private fun makeListItems() {
val rowsAdapter = ArrayObjectAdapter(ListRowPresenter())

val headerItem = HeaderItem(0, "FirstRow")
val headerItem1 = HeaderItem(1, "SecondRow")
val headerItem2 = HeaderItem(2, "ThirdRow")
val movieViewPresenter = MovieViewPresenter(requireContext())
val itemsPresenter = ItemsPresenter()

val itemRowAdapter = ArrayObjectAdapter(itemsPresenter).apply {
    add("ITEM 1")
    add("ITEM 2")
    add("ITEM 3")
}


val movieRowAdapter = ArrayObjectAdapter(movieViewPresenter).apply {
    add(Movie("Sleeping Beauty", "Directed by: Clyde Geronimi", "https://www.filimo.com/public/public/user_data/video_thumb_star/14/27509_27509-m.jpg"))
    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"))
    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")))
    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"))
}
rowsAdapter.add(ListRow(headerItem, itemRowAdapter))
rowsAdapter.add(ListRow(headerItem1, movieRowAdapter))
rowsAdapter.add(ListRow(headerItem2, movieRowAdapter))

adapter = rowsAdapter
}

و اجرا:

اجرای BrowsFragment با استفاده از ImageCardView

همینطور که میبینید آیتم ها به سطرهای من اضافه شده، بصورت پیشفرض در هر ImageCardView فقط بخش عکس نمایش داده می¬شود، و با انتخاب کردن هرکدام از سطرها و فعال شدن ImageCardView، داده های بیشتر (شامل بخش info و badge) نمایش داده می¬شوند.

دو مقداری که با تغییر دادن آن¬ها می¬توانیم رفتار متفاوتی از Card ها ببینیم عبارت هستند از: cardType و infoVisibility داخل Presenter در دسترس هستند:

override fun onCreateViewHolder(parent: ViewGroup?): ViewHolder {
     val view = ImageCardView(context).apply {
         isFocusable = true
         setMainImageDimensions(300, 150)
         badgeImage = ContextCompat.getDrawable(context, R.drawable.ic_play_circle_outline_black_24dp)
         cardType = BaseCardView.CARD_TYPE_INFO_UNDER_WITH_EXTRA
         infoVisibility = BaseCardView.CARD_REGION_VISIBLE_ACTIVATED
     }
     return ViewHolder(view)
 }

در زیر حالت¬های متفاوتی که این مقادیر ایجاد می¬کنند رو توضیح دادم

cardType:
BaseCardView.CARD_TYPE_INFO_OVER: بخش info روی قسمت main قرار می¬گیره

BaseCardView.CARD_TYPE_INFO_OVER

BaseCardView.CARD_TYPE_MAIN_ONLY: فقط بخش عکس نمایش داده می¬شود

BaseCardView.CARD_TYPE_MAIN_ONLY

BaseCardView.CARD_TYPE_INFO_UNDER: بخش info زیر قسمت main قرار میگیره

infoVisibility:
BaseCardView.CARD_REGION_VISIBLE_ALWAYS: بخش info همیشه نمایش داده می¬شود:

BaseCardView.CARD_REGION_VISIBLE_ACTIVATED: زمانی که سطرها فعال باشند، برای تمامی کارت ها بخش info نمایش داده می¬شود.

BaseCardView.CARD_REGION_VISIBLE_SELECTED: هر کارت زمانی که انتخاب شود، بخش info برای فقط آن کارت نمایش داده می¬¬شود:

2- پیاده سازی با استفاده از ViewHolder:
برای اینکه ViewHolder سفارشی داخل presenter ایجاد کنیم، کافی هست کلاس زیر رو داخل Presenter اضافه کنیم و تنظیمات دیفالت imageCardView رو به اینجا منتقل کنیم:

class CardViewHolder(itemView: View): ViewHolder(itemView){
     var imageCardView: ImageCardView = itemView as ImageCardView
init {
    imageCardView.apply {
        isFocusable = true
        cardType = BaseCardView.CARD_TYPE_MAIN_ONLY
        infoVisibility = BaseCardView.CARD_REGION_VISIBLE_ALWAYS
        setMainImageDimensions(300, 150)
        badgeImage = ContextCompat.getDrawable(context, R.drawable.ic_play_circle_outline_black_24dp)
    }
}
}

پس کد نهایی کلاس Presenter به شکل زیر هست:

ass MovieViewPresenter(private val context: Context): Presenter() {
    override fun onCreateViewHolder(parent: ViewGroup?): ViewHolder {
        return CardViewHolder(ImageCardView(context))
    }

    override fun onBindViewHolder(viewHolder: ViewHolder?, item: Any?) {
        (item as? Movie)?.let {movie->
            (viewHolder as? CardViewHolder)?.imageCardView?.apply {
                titleText = movie.title
                contentText = movie.description
                Glide.with(context).load(movie.coverUrl).apply(RequestOptions().centerCrop()).into(this.mainImageView)
            }
        }
    }


    class CardViewHolder(itemView: View): ViewHolder(itemView){
        var imageCardView: ImageCardView = itemView as ImageCardView

        init {
            imageCardView.apply {
                isFocusable = true
                cardType = BaseCardView.CARD_TYPE_MAIN_ONLY
                infoVisibility = BaseCardView.CARD_REGION_VISIBLE_ALWAYS
                setMainImageDimensions(300, 150)
                badgeImage = ContextCompat.getDrawable(context, R.drawable.ic_play_circle_outline_black_24dp)
            }
        }
    }


    override fun onUnbindViewHolder(viewHolder: ViewHolder?) {

    }
}

طبق قول کدها قرار هست داخل گیت قرار بگیرند. پس من داخل ریپوزیتوری گیتهابی که به این پروژه تخصیص دادم، روی برنچ iodroid_a2/add_image_cardview کد این بخش از آموزش رو قرار دادم. (طبیعتا کد نهایی هم روی برنج 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>