Coding Story

在黑暗中寫故事 👻

0%

Android 特效:動態變更標籤元件(TabLayout)

前言故事

電腦前,小唯不斷地調整程式碼,重複編譯,檢查模擬器上的結果,一整個上午小唯一直在試一個 Android 標籤元件的特效(Tab 特效)- 動態新增新的標籤頁,及移除現有的標籤頁。這問題已經困擾小唯一段時間了,原本以為是個很簡單的效果,一個上午絕對可以搞定,但卻在最後一步,怎麼試效果都不正確,標籤頁雖然可以新增或移除,但標籤頁的內容卻怪怪地。

簡單、或不簡單

小唯要求的規格很簡單,準備一個標籤元件,透過程式的方式,可隨意添加新標籤頁,或移除現有標籤頁(除非只剩下一個標籤頁)。規格聽起來很簡單,小唯心裡盤點著:「需要 TabLayout、ViewPager、FragmentPagerAdapter…」。團隊以往使用標籤元件,常常都是固定數量的標籤頁,只需透過 FragmentPagerAdapter 定義標籤頁數量、標籤名稱、內容等,就能顯示預期的效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class UpdatableTabAdapter(fm: FragmentManager): FragmentPagerAdapter(
fm,
BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
) {

...

override fun getCount(): Int {
return 3 // 三個標籤頁
}

override fun getItem(position: Int): Fragment {
// 回傳指定位置的標籤頁 Fragment
}

...
}

但如果要能支援新增刪除,就稍微複雜一點,但只要把固定標籤頁的寫法,改為透過 MutableList 來控制標籤頁的數量,應該也不會太複雜吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class UpdatableTabAdapter(fm: FragmentManager): FragmentPagerAdapter(
fm,
BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
) {

private val tabs: MutableList<Int> = mutableListOf(1, 2, 3)

override fun getCount(): Int {
return tabs.size
}

override fun getItem(position: Int): Fragment {
// 回傳指定位置的標籤頁 Fragment
}

override fun getPageTitle(position: Int): CharSequence? {
return tabs[position].toString()
}

override fun getItemId(position: Int): Long {
return tabs[position].toLong()
}
}

結果卻不如預期 😫:


意想不到的解決方案

上述做法是,只要在標籤頁上點選了新增符號,就會在右邊加一個新的標籤頁。反之,點選移除符號則移除最左邊的標籤頁,但怎知移除標籤頁後,卻出現了標籤混亂的狀態,這是因為 FragmentPagerAdapter 保存了標籤頁,並沒有在每次新增移除後都重建標籤頁,即使使用了 notifyDataSetChanged() 結果也是一樣。

經過一個上午的努力,小唯終於找到了關鍵原因,就是在 PagerAdapter 內的 getItemPosition,以下是 PagerAdapter 的內容及說明文件:

PagerAdapter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Called when the host view is attempting to determine if an item's position
* has changed. Returns {@link #POSITION_UNCHANGED} if the position of the given
* item has not changed or {@link #POSITION_NONE} if the item is no longer present
* in the adapter.
*
* <p>The default implementation assumes that items will never
* change position and always returns {@link #POSITION_UNCHANGED}.
*
* @param object Object representing an item, previously returned by a call to
* {@link #instantiateItem(View, int)}.
* @return object's new position index from [0, {@link #getCount()}),
* {@link #POSITION_UNCHANGED} if the object's position has not changed,
* or {@link #POSITION_NONE} if the item is no longer present.
*/
public int getItemPosition(@NonNull Object object) {
return POSITION_UNCHANGED;
}

原來 PagerAdapter 的預設是 POSITION_UNCHANGED,這也是告訴自己,這個位置上的內容沒有變喔。小唯立刻在 UpdatableTabAdapter class 中覆蓋這個 function:

1
2
3
override fun getItemPosition(`object`: Any): Int {
return PagerAdapter.POSITION_NONE
}

重新編譯,檢查模擬器!結果真如預期,任務達成~ 🎉🎉🎉

相關連結: Demo 專案

------------- 本文结束 下次改用 ViewPager2 吧~ -------------