今天要來玩玩 TopTabNavigation
除了前幾天介紹的 leanback library 幾個快速開發的 Fragment 之外
很多 App 都會把 TabLayout 的功能設計進去來切換類別,例如:愛奇藝、Google Play Store、myVideo、國外的 RedBull TV 等等,都有 TabLayout 的身影在裡面,如下圖
但在早期 leanback library 中是沒有 TabLayout 這個元件的,許多開發者一直向官方敲碗,因為手機版的 TabLayout 是沒有支援 Focus 的,開發者需要自己自定義元件才行
一直到今年的 Google I/O 這個 TopTabNavigation 才終於被納入 androidx 的 leanback library 中
到目前為止還在 alpha 版中,希望可以盡快進入到正式版喔~
那我們來看看如何實作吧
其實這個套件雖然是放在 leanback library 中,但其實它的呼叫方式跟開發 Mobile App 時沒什麼差別
implementation 'androidx.leanback:leanback-tab:1.1.0-alpha05'
這邊要注意的是它的版本是 1.1.0-alpha05 唷,因為這新的套件只有在 alpha 版才有
新增一個繼承 FragmentActivity 的 TopTabNavigationActivity
在 activity_top_tab_navigation.xml 中加入 LeanbackTabLayout 和 LeanbackViewPager
程式碼如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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=".TopTabNavigationActivity">
<androidx.leanback.tab.LeanbackTabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<androidx.leanback.tab.LeanbackViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/tab_layout"/>
</RelativeLayout>
跟先前一樣把資料撈進來吧
fun processData(){
val jsonFileString: String = resources.openRawResource(R.raw.movielist).bufferedReader().use { it.readText() }
if(BuildConfig.DEBUG) Log.d(TAG, "jsonFileString: $jsonFileString")
mMovieList = Gson().fromJson(jsonFileString, MovieList::class.java)
if(BuildConfig.DEBUG) Log.i(TAG, "mMovieList: $mMovieList")
}
這個 ViewPager 的 Adapter 和 Mobile App 在開發時的 Adapter 是一樣的創建和呼叫方式
只是特別要注意的是 leanback library 中的 LeanbackViewPager 是 ViewPager 不是 ViewPager2 喔~
那覆寫的 getItem 回傳的 Fragment 就會是 RowsSupportFragment
完整程式碼如下
class TopTabViewPagerAdapter(fragmentManager: FragmentManager, behavior: Int, var mMovieListData: List<Data>? = null): FragmentStatePagerAdapter(fragmentManager, behavior) {
private val TAG: String = javaClass.simpleName
override fun getItem(position: Int): Fragment {
if(BuildConfig.DEBUG) Log.v(TAG, "===== getItem =====")
mMovieListData?.run {
val category: Data = mMovieListData!![position]
val subCategoryList: List<SubCategory>? = category.sub_categories
if (subCategoryList != null && subCategoryList.isNotEmpty()) {
val rowsSupportFragment: RowsSupportFragment = RowsSupportFragment()
val rowsAdapter: ArrayObjectAdapter = ArrayObjectAdapter(ListRowPresenter())
for ((subCategoryIndex, subCategory) in subCategoryList.withIndex()) {
val subCategoryName: String? = subCategory.sub_category_name
if (BuildConfig.DEBUG) Log.d(TAG, "subCategoryName: $subCategoryName")
val items: List<Item>? = subCategory.items
val listRowAdapter: ArrayObjectAdapter =
ArrayObjectAdapter(CustomCardPresenter())
if (items != null && items.isNotEmpty()) {
for (item in items) {
if (BuildConfig.DEBUG) Log.i(TAG, "movieName: ${item.name}")
listRowAdapter.add(item)
}
val header: HeaderItem = HeaderItem(0, subCategoryName)
rowsAdapter.add(ListRow(header, listRowAdapter))
}
}
rowsSupportFragment.adapter = rowsAdapter
return rowsSupportFragment
}
}
return RowsSupportFragment()
}
override fun getCount(): Int {
if(BuildConfig.DEBUG) Log.v(TAG, "===== getCount =====")
return if(mMovieListData!=null) mMovieListData!!.size else 0
}
override fun getPageTitle(position: Int): CharSequence? {
val category: Data = mMovieListData!![position]
val categoryName: String? = category.category_name
if (BuildConfig.DEBUG) Log.w(TAG, "categoryName: $categoryName")
return categoryName
}
}
fun processView(){
if(mMovieList!=null){
mMovieListData = mMovieList!!.data
if(mMovieListData!=null && mMovieListData!!.isNotEmpty()){
mViewPagerAdapter = TopTabViewPagerAdapter(supportFragmentManager,
FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT, mMovieListData)
}
}
if(mViewPagerAdapter!=null){
view_pager.adapter = mViewPagerAdapter
}
tab_layout.setupWithViewPager(view_pager)
}
燈燈!!!怎麼掛掉了!!!
會掛是正常的不用緊張,從 Logcat 裡可以看到很長一大段的 Error
但最重要的是這段
Caused by: java.lang.IllegalArgumentException: The style on this component requires your app theme to be Theme.AppCompat (or a descendant).
這是在講說 LeanbackTabLayout 和 LeanbackViewPager 這兩個套件是依附在 AppCompat 底下的,所以我們要在 AndroidManifest.xml 裡,TopTabNavigationActivity 的部分加上 android:Theme,如下
<activity android:name=".TopTabNavigationActivity"
android:theme="@style/Theme.AppCompat"/>
最後再跑一次看看
完成了!!
不過畫面有點不像 BrowseSupportFragment 那樣好看
我在研究看看如何修改吧
就降囉~