Option Menu、Popup Menu 和 Toolbar Menu 我全都要
還在苦惱 Option Menu、Popup Menu 和 Toolbar Menu 如何使用嗎?這篇全都說給你聽!
Android App 中常見「選單」供使用者進行進階的操作,系統預設的圖示會是 triple dots,也可以在 XML 透過 icon
的屬性指定其他圖示。
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/new_game"
android:icon="@drawable/ic_new_game"
android:title="@string/new_game"
android:showAsAction="ifRoom"/>
<item android:id="@+id/help"
android:icon="@drawable/ic_help"
android:title="@string/help" />
</menu>
在 XML 指定好 menu item 後,再來就來認識 Menu 的種類以及如何使用,這篇會介紹 Option Menu、Popup Menu 和 Toolbar Menu,官方的 Menu 還有一種 Context Menu,但還沒用過所以本篇就不獻醜了。
Option Menu
Option Menu 除了在 XML 靜態新增外,也可以透過動態的方式在 onCreateOptionsMenu() 或是 onPrepareOptionsMenu() 的 callback 中新增、移除或是停用 menu item。那如何選擇靜態或是動態的方式管理 menu item 呢?官方建議可以依照 menu item 是否會頻繁異動的情況判定,當 menu item 在同個頁面會依不同觸發條件異動時則應動態設定,反之若不會異動則靜態新增就好。
選擇 menu item 後要進行的動作就交給 onOptionsItemSelected(item: MenuItem)
來!預設的 super.onOptionsItemSelected(item)
會回傳 false,當 Activity 和 Fragment 同時包含 onOptionsItemSelected(item: MenuItem)
時,會優先呼叫 Activity 的 onOptionsItemSelected(),之後才會依 Fragment 新增順序呼叫各 Fragment 的 onOptionsItemSelected(),直到其中一項回傳 true 或是全部的 menu item 都被呼叫完畢。
沒那麼簡單的 Option Menu
原以為已經了解如何新增和管理 menu item,但實作時卻發現沒那麼簡單,原先沒有 menu 的 Fragment 竟然會被有 menu 的 Fragment 給污染,明明只有一個 Fragment 實作了 onCreateOptionsMenu() 的 callback 叫出 menu item,沒想到開啟其他沒複寫 onCreatOptionMenu() 的 Fragment 也跟著出現了前面實作的 menu,而出現的 menu item 正是前一頁實作過的 menu!
上述 Menu 被錯誤 inflate 的原因是 Fragment 對 Menu 只有單向控制力,Fragment 只能對 Menu 呼之即來,卻無法揮之即去,導致一個 Fragment 新增了 Menu 以後,後面開啟的 Fragment 都會跟著出現 Menu。
解法 1 : TargetFragment
如果可以知道 Fragment 間開啟的先後順序,就可以透過 TargetFragment 的方式在先開啟的 FragmentA
新增 menu item 並把 TargetFragment 設定為後開啟的 FragmentB
。在 FragmentB
的 onResume 中加上:
getTargetFragment().setMenuVisibility(false);
在 FragmentA
的 onStop 加上:
getTargetFragment().setMenuVisibility(true);
這個做法的缺點是在有 menu 和沒有 menu 的 Fragment 之間都需要透過 TargetFragment 處理,而兩個有不同 menu 的 Fragment 也需要互相設定 TargetFragment,對於 Fragment 開啟沒有一定順序的情況而言,要處理的情況就好多...。
解法 2 : onPrepareOptionsMenu
在不需使用 menu 的通通 Fragment 加上:
override fun onPrepareOptionsMenu(menu: Menu?) { menu?.clear()}
需要 menu 的 Fragment 則加上:
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
menu?.clear()
inflater?.inflate(R.menu.help_menu, menu)
super.onCreateOptionsMenu(menu, inflater)
}
因為原先拿到的 menu 可能會是別人家的 menu,記得要先清掉以免 inflate 到別人的!缺點當然就是麻煩,連不用 menu 的 Fragment 都要加上 onPrepareOptionsMenu,一不小心就會漏加。
解法 3 : ToolbarMenu
是日救星 Toolbar Menu!讓我們繼續看下去。
Toolbar Menu
Toolbar Menu 顧名思義就是長在 Toolbar 上的 menu,因為每個 Fragment 都會有自定義的 Toolbar,因此在 Toolbar 身上加 Menu 就不用擔心沾染到別人的 menu item 或是我的 menu item 產生側漏的問題,使用 Toolbar Menu 也可以好自在好安心,靠 bar 的使用方法如下:
toolbar.inflateMenu(R.menu.help_menu)
toolbar.setOnMenuItemClickListener {
when (it.itemId) {
R.id.help -> { }
else -> false
}
}
Toolbar Menu 解決了在 Fragment 使用 Option Menu 痛點,也支援 toolbar.menu.add()
的方式動態新增 menu item。至於缺點目前還沒想到,如果有其他靠 bar 後想靠背的朋友歡迎交流XD
Popup Menu
有些需求可能要在 RecyclerView 中新增彈出式選單,這時上述兩種 Menu 都派不上用場,Popup Menu 就決定是你了!不囉唆直接上 code:
val wrapper = ContextThemeWrapper(ApplicationContext(), R.style.PopupMenu)
val popup = PopupMenu(wrapper, anchorView, Gravity.END)
popup.inflate(R.menu.help_menu)
popup.setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.new_game -> { }
R.id.help -> { }
}
false
}
popup.show()
初始化 PopupMenu 需要傳入 Context(wrapper
)、對齊的 view (anchorView
)、對齊方向(Gravity.END
),如果有 menu item 需要顯示或隱藏的話也可以透過 menu.findItem(R.id.help_menu).isVisible = true
控制,Popup Menu 也同樣支援 popup.menu.add()
的方式可以動態新增 menu item,缺點一樣 null。
其實除了 Option Menu 雷一點外其他 Menu 都蠻可人的啊,如果有其他用過上面幾種 Menu 的朋友歡迎留言交流!