در این بخش با ساختار BrowsSupportFragment آشنا خواهیم شد و یاد خواهیم گرفت چطور لیستی از item ها در صفحه اپلیکیشن خود نشان دهیم. پس ابتدا باید با ساختار BrowsSupportFragment آشنا بشیم
پیش نیاز: AndroidTv – از ساخت تا اولین اجرا

شکل بالا نمایی از چیدمان یک فرگمنت هست که از BrowsSupportFragment ارث برده. با توجه به شماره ها میخوایم ببینیم چه آِیتم¬هایی در اختیار داریم در این فرگمنت و چه کارهایی میتونیم انجام بدیم داخلش.
1- همانطور که گفتیم، فرگمنت ما از BrowsSupportFragment ارث برده.
تعریفی که داخل سایت توسعه دهندگان اندروید از این کلاس گذاشته شده :
A fragment for creating Leanback browse screens. It is composed of a RowsSupportFragment and a HeadersSupportFragment.
در مجموع کار این فرگمنت این هست که مثل تصویر، مجموعه ای از سطرها(Row) رو داخل خودش نشون بده. هر سطر یک عنوان و تعدادی آیتم داره و احتمالا قرار هست داخل اپلیکیشن ما چنین شمایی رو برای ما ایجاد کنه:

2- RowsFragment: داخل BrowsSupportFragment قرار داره و عملا جایی هست که سطر آیتم¬ها داخلش قرار میگیره.
3- HeadersFragment: : داخل BrowsSupportFragment قرار داره و عنوان سطرهای محتوای فرگمنت در اینجا قرار می گیرند.
برای تغییر رنگ بک گراند این صفحه از متد setBrandColor داخل فرگمنت استفاده می¬کنیم.
4- برای این فرگمنت می توانیم عنوان یا عکس لوگو تعریف کنیم که یکی از دو متد برای اینکار استفاده می شود:
setBadgeDrawable
setTitle

5- HeaderItem: نوع داده ای هست که داخل HeadersFragment بعنوان عنوان سطرها نشان میدهیم
6- Object: آیتم هایی که در سطرها نشان می¬دهیم که می¬توانند هر نوع View ای باشند اما LeanBack کلاس¬های View استاندارد خودش را هم دارد.
7- سطرها که همانطور که گفتم شامل یک سری Object هستند، برای مقداردهی به سطرها باید از Adapter استفاده کنند که parent این آداپتر باید کلاسی به نام Presenter باشد. (مثل RecyclerView.Adapter در برنامه نویسی اندروید اپلیکیشن عادی اما به Position رفرنسی نداره). در نهایت برای اینکه این آداپتر را ست کنیم به RowsFragment باید از ArrayObjectAdapter استفاده کنیم تا از Presenter یک آداپتر ساخته و بتواند View های سطر را توسط آن بسازد. من نام نهاییش رو RowAdapter می گذارم که راحت تر باهم درموردش صحبت کنیم 😊
RowAdapter = ArrayObjectAdapter(ObjectsPresenter)
8- ListRow که مجموعه HeaderItem و RowAdapter هست برای اینکه یک سطر کامل از فرگمنت رو پوشش بده
ListRow = HeaderItem + RowAdapter

9- کل صفحه شامل یک آداپتر اصلی هست که مجموعه سطرها (ListRowها) ما رو در بر میگیره. خود LeanBack برای چیدن این سطرها Presenter خاص خودش رو داره به اسم ListRowPresenter. من اسم این آداپتر رو که توسط ListRowPresenter ساخته میشه RowsAdapter میگذارم:
RowsAdapter = ArrayObjectAdapter(ListRowPresenter())
کل ساختار BrowsSupportFragment که معمولا مهمترین صفحه اپلیکیشن تلویزیون ما رو ایجاد می کنه همین بود. و اگر با ساختارش آشنا بشید عملا مهمترین قسمت در توسعه Android Tv Application ها رو یاد گرفتید.

در ادامه سعی می کنیم خیلی سریع لیست این صفحه رو پیاده کنیم.

1) ساختن ItemsPresenter:
همانطور که بالاتر اشاره کردم، این کلاس قرار هست که آیتم های RowsFragment رو بسازه و باید از کلاس Presenter از کتابخانه LeanBack ارث بری کنه. پس کلاس زیر رو ایجاد می کنم:

class ItemsPresenter : Presenter() {
     override fun onCreateViewHolder(parent: ViewGroup?): ViewHolder {
         val view = TextView(parent?.context).apply {
             layoutParams = ViewGroup.LayoutParams(300, 100)
             //make the item focusable while using TV's remote control
             isFocusable = true
             setBackgroundColor(ContextCompat.getColor(parent?.context!!, R.color.primary))
             setTextColor(Color.WHITE)
             gravity = Gravity.CENTER
         }    
return ViewHolder(view)
}

override fun onBindViewHolder(viewHolder: ViewHolder?, item: Any?) {
    (viewHolder?.view as TextView).text = item as String
}

override fun onUnbindViewHolder(p0: ViewHolder?) {}
}

در قدم اول من میخوام لیست ها فقط یک TextView نمایش بدهند. از ViewHolder خود اندروید استفاده می¬کنم (یک کلاس سفارشی فعلا براش ایجاد نکردم). تنها کاری که باید انجام بدیم موقع ایجاد TextView این هست که isFocusable رو برایش فعال کنیم تا موقع استفاده از کنترل تلوزیون کاربر بتونه روی این TextView ها حرکت کنه.
در نهایت از آنجایی که می¬دونم نوع داده که قرار هست به Presenter ارسال کنم String هست، پس داخل متد onBindViewHolder مقدار item رو به String تبدیل کرده و داخل TextView نمایش می¬دهم.

2) داخل MainFragment ابتدا تنظیمات اولیه¬ی فرگمنت رو انجام می¬دهم و بعد آیتم¬های لیست رو ایجاد می¬کنم. پس اول داخل onActivityCreated مقادیر زیر رو ایجاد می¬کنم:

override fun onActivityCreated(savedInstanceState: Bundle?) {
     super.onActivityCreated(savedInstanceState)
headersState = HEADERS_ENABLED
showTitle(true)
badgeDrawable = ContextCompat.getDrawable(requireContext(), R.drawable.banner)
brandColor = ContextCompat.getColor(requireContext(), R.color.headers_background)

makeListItems()
}

مقدار headerState بیانگر این هست که HeaderFragment نمایش داده بشود یا نه، پس در صورت لزوم خیلی راحت می¬تونیم این فرگمنت رو غیرفعال کنیم.
3) داخل متد makeListItem که خودم ایجادش کردم به ترتیب زیر پیش میرویم:

  • آداپتری برای مجموعه سطرها ایجاد می¬کنم:
    val rowsAdapter = ArrayObjectAdapter(ListRowPresenter())
  • عنوان سطرها رو با استفاده از کلاس HeaderItem ایجاد می¬کنم که دو ورودی میگیرد، id سطر و متن نمایشی:
    val headerItem = HeaderItem(0, "FirstRow")
    val headerItem1 = HeaderItem(1, "SecondRow")
    val headerItem2 = HeaderItem(2, "ThirdRow")
  • آداپتری که قرار هست یک سطر را ایجاد کند (با کمک ItemsPresenter که قبل تر ایجادش کردیم) رو مقدار دهی می¬کنم(RowAdapter):
    val rowAdapter = ArrayObjectAdapter(itemsPresenter).apply {
    add("ITEM 1")
    add("ITEM 2")
    add("ITEM 3")
    }

از آنجایی که می¬خواهم هر سه سطر من دقیقا همین مقادیر را نشان بدهند برای هر سه از همین rowAdapter استفاده خواهم کرد.

  • برای هر سطر یک ListRow ایجاد می¬کنم و آن را به RowsAdapter اضافه می¬کنم:
    rowsAdapter.add(ListRow(headerItem, rowAdapter))
    rowsAdapter.add(ListRow(headerItem1, rowAdapter))
    rowsAdapter.add(ListRow(headerItem2, rowAdapter))
  • در نهایت rowsAdapter رو به adapter پیش فرض BrowsSupportFragment (فرگمنت فعلی)، assign می¬کنم:
    adapter = rowsAdapter

کد نهایی کلاس MainFragment:

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

    headersState = HEADERS_ENABLED
    showTitle(true)
    badgeDrawable = ContextCompat.getDrawable(requireContext(), R.drawable.banner)
    brandColor = ContextCompat.getColor(requireContext(), R.color.headers_background)

    makeListItems()
}

private fun makeListItems() {

    val rowsAdapter = ArrayObjectAdapter(ListRowPresenter())

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

    val rowAdapter = ArrayObjectAdapter(itemsPresenter).apply {
        add("ITEM 1")
        add("ITEM 2")
        add("ITEM 3")
    }
    rowsAdapter.add(ListRow(headerItem, rowAdapter))
    rowsAdapter.add(ListRow(headerItem1, rowAdapter))
    rowsAdapter.add(ListRow(headerItem2, rowAdapter))

    adapter = rowsAdapter
}
}

و حالا اجرا کنید و لذت ببرید 😊

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