در این بخش با ساختار 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 موجود هست)
برای دسترسی به این شاخه از پروژه گیتهاب کلیک کنید 🙂

ساختار اپلیکیشن های تلوزیون اندروید(Android Tv)، هیچ تفاوتی با اپلیکیشن های موبایل نداره. یعنی شما با همون تجربه ای که در حال حاضر از توسعه اپلیکیشن موبایل دارید، می تونید برای تلوزیون های دارای سیستم عامل اندروید هم کد بزنید و اپ منتشر کنید.
اما خود اندروید، ساختاری با تجربه کاربری مناسب برای صفحه نمایش های بزرگ تلوزیون پیشنهاد میده، که کاربر علاوه بر اینکه بتونه از فاصله چند متری به خوبی با صفحات اپلیکیشن ارتباط برقرار کنه، امکان کنترل کردن اپلیکیشن با دستگاه کنترل تلوزیون رو هم داشته باشه.

اگر میخواهید از سایت خود توسعه دهنگان اندروید برای پیدا کردن دید بهتر نسبت به Android Tv استفاده کنین، مقاله مرتبط رو در این سایت بهتون پیشنهاد میکنم.
همینطور برای آشنایی بیشتر با Design guidline تلوزیون اندروید، سایت withgoogle.com توضیحات خوبی قرار داده.

برای شروع باید اول با کتابخانه LeanBack آشنا بشید که شامل کلاس هایی برای توسعه واسط کاربری مناسب تلوزیون هست که در این آموزش هم از این کتابخانه استفاده خواهیم کرد.
1) ایجاد پروژه
اولین قدم مانند شروع هر پروژه اندروید دیگه، ایجاد یک New project در اندروید استادیو هست. فقط این بار به جای Phone and Tablet گزینه TV رو انتخاب کنید. از بین دو گزینه برای ایجاد اکتیویتی، Add No Activity را انتخاب کنید، برای اینکه گزینه ی Android Tv Activity، فایل های زیادی رو به پروژه اضافه خواهد کرد که در حین اینکه مرجع خوبی هست، یادگیری رو پیچیده می کنه.

پروژه اضافه خواهد کرد که در حین اینکه مرجع خوبی هست، یادگیری رو پیچیده می کنه.

2) ایجاد اولین صفحه
بعد از Build شدن پروژه، روی پکیج راست کلیک کنید و یک Blank Activity به اسم MainActivity ایجاد کنید.

در بخش بعدی باید برای این اکتیویتی یک فرگمنت ایجاد کنیم. پس دوباره با راست کلیک روی پکیج، یک Blank Fragment ایجاد می کنم با عنوان MainFragment :

و البته تیک گزینه های مربوط به ایجاد پیشفرض های فرگمنت رو هم بردارید چون نیازی به فایل ها و متدهای دیفالت ایجاد شده توسط اندروید استادیو نداریم.
و اولین جایی که با کتابخانه leanBack مواجه می شویم، همینجاست! که بجای فرگمنت اندروید، parent را به BrowseSupportFragment از کتابخانه لین بک تغییر می دهیم:

در نهایت تکه کد زیر را به activity_main.xml (لیوت ایجاد شده برای MainActivity) اضافه کنید:

<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_fragment_container"
    android:name="ir.iodroid.androidtvsample.MainFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

3) تغییر تم اپلیکیشن:
تم اکتیویتی ایجاد شده را در manifest به android:theme=”@style/Theme.Leanback” تغییر بدهید. و البته intent-filter های مربوط به لانچر کردن اکتیویتی هم فراموش نکنید!

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/Theme.Leanback">
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>

            <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
        </intent-filter>
    </activity>
</application>

در ادامه هم از آنجایی که این تم action bar ندارد، جهت جلوگیری از خطای زیر تم اکتیویتی را به تغییر دهید.

java.lang.RuntimeException: Unable to start activity ComponentInfo{...} :
java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this
activity.
(خطای احتمالی بدون تغییر تم)

4) و اولین اجرا!
یک نفس عمیق بکشید که وقت اجرا گرفتن از اولین پروژه Android Tv شماست.
برای اجرا گرفتن چند راه دارید:
1) استفاده از تلوزیون اسمارت که سیستم عامل اندروید دارد
2) استفاده از باکس های Android Tv مثل شیائومی
3) استفاده از شبیه ساز
که من در این آموزش از شبیه ساز استفاده میکنم
برای ساخت شبیه ساز تلوزیون اندروید، مثل زمانی که شبیه ساز گوشی اندروید میسازید، وارد AVD Manager بشید و این بار گزینه Android Tv رو انتخاب کنید:

در بخش آخر زمان تخصیص Ram هم تا حد ممکن رم کمتر از 2گیگ برای این شبیه ساز در نظر نگیرید تا از کندی اون جلوگیری کنید.

و حالا وقت زدن دکمه Run هست:

تبریک! شما اولین پروژه خود تلوزیون اندرویدی خودتون رو اجرا گرفتین😊

اگر وارد صفحه اصلی تلوزیون خود شوید (یا شبیه ساز) در بخش اپلیکیشن ها، آیکون اپلیکیشن خود را می بینید.

صفحه اپلیکیشن های تلوزیون

آیکون استاندارد برای Tv به اسم bannerهست که باید بر اساس داکیومنت سایت توسعه دهندگان اندروید گوگل، سایزش 320×180 بوده و داخلش متن نام اپلیکیشن قرار گرفته باشد. و البته اگر اپلیکیشن شما از زبان های مختلفی پشتیبانی می کند باید به ازای هر زبان یک بنر جداگانه با متن مربط به آن زبان داشته باشید.
به همین منظور من بنری با این سایز ایجاد کردم و داخل manifest این بنر رو قرار دادم(بنر ها باید داخل پوشه xhdpi قرار گیرند، مانند سایر resource های تلوزیون اندروید که بعلت سایز دستگاه داخل این پوشه قرار خواهند گرفت)

      <application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:banner="@drawable/banner"
    android:theme="@style/Theme.Leanback">
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>

            <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
        </intent-filter>
    </activity>
</application>

و برای مشاهده تغییر باید احتمالا یک دور اپلیکیشن را از روی دستگاه پاک کرده و بعد دوباره نصب کنید.
در نهایت دو تگ زیر رو به بالای تگ application اضافه می کنم. این دو برای این هست که اعلام کنیم چه دستگاهایی ساپورت می کنن اپلیکیشن مارو. و همینطور داخل گوگل پلی این اپلیکیشن به چه دستگاهایی باید نمایش داده بشه:

<uses-feature
    android:name="android.hardware.touchscreen"
    android:required="false"/>
<!--
 true:  your app runs on only TV
 false: your app runs on phone and TV
-->
<uses-feature
    android:name="android.software.leanback"
    android:required="true"/>

یکی از کتابخانه های خیلی کاربردی برای کار با فایل Json ، کتابخانه Gson هست که توسط خود گوگل منتشر شده.

نحوه کار این کتابخانه به این صورت هست که اول به ازای تمام آبجکت هایی که جیسون مورد نظر ما داره، یک کلاس جاوا تعریف می کنیم و داخلش به ازای تمامی فیلد های جیسون متناظر، یک فیلد جاوا می گذاریم. (مثلا String یا Int یا…)

در نهایت کتابخانه Gson با متدها fromJson و toJson با یک خط کد، جیسون مورد نظر رو به آبجکت جاوای ما تبدیل می کنه و یا برعکس، با مقداری دهی به آبجکت جاوا، از اون یک جیسون می سازه.

لینک این کتابخانه: Github Gson

لینک فایل کد آموزش:

در این آموزش بطور کامل با مفاهیم وب سرویس و API آشنا خواهید شد. ابتدا بررسی می کنیم API چی هست و چه فرقی با وب سرویس داره. بعد یه مروری می کنیم روی متدهای http و به اینجا می رسیم که rest webservice چی هست و چه قوانینی رو باید رعایت کنیم تا وب سرویس ما restfull باشه.

این آموزش پیش نیاز همه آموزش های مرتبط با خواندن داده از اینترنت هست و جزو دسته بندی آموزش هایی هست که هم برای اندروید و هم برای ios بدردتون میخوره.

امیدوارم از این آموزش استفاده کامل بکنید و خودتون رو برای آموزش های بعدی آماده کنید. 🙂