星期六, 1月 18, 2014

程式設計師 vs. 新聞記者

Android 的 PopupMenu 不支援顯示 icon。可是很多時候你會想放個 icon 給他... 那怎麼辦呢?

深挖 PopupMenu 的程式碼以後,發現他實際是透過 MenuPopupHelper 這個 class 來顯示實際的 PopupWindow,而該 class 有個 setForceShowIcon() 函式,可以用來強制顯示 icon。

可是... 這個 mPopup 在 PopupMenu 裡是 private field 啊... client code access 不到它... 怎麼辦咧?

在這裡有兩個選擇...

  1. 搞肛的辦法...:把 PopupMenu.java 跟所有用到的 internal class 實作複製一份過來,多新增一個 setForceShowIcon() 方法。
  2. 偷吃步:透過 Java 的 Reflection 機制來偷偷存取該 private field。

當然,我不會在這裡講搞肛的辦法,因為我是怠惰的程式設計師,能偷吃步一定要偷吃步的(怠惰的程式設計師施工原則:自己要維護的程式碼越少越好)。

所以,本來這樣的東西:

PopupMenu menu = ...;
...
menu.mPopup.setForceShowIcon(true);

就變成了...:

public static void hackPopupMenuToShowIcon(PopupMenu popup, boolean showIcon)
{
    try
    {
        // 從所有 field 中找出名稱為 mPopup 的 field。
        Field[] fields = popup.getClass().getDeclaredFields();
        for (Field field : fields)
        {
            if ("mPopup".equals(field.getName()))
            {
                // 設定存取權(本來是 private field 不能存取)
                field.setAccessible(true);
                // 取得 popup 這個 instance 的 mPopup
                Object menuPopupHelper = field.get(popup);
                // 取得 mPopup 的 class
                Class classPopupHelper = Class.forName(menuPopupHelper.getClass().getName());
                // 取得 mPopup 的 class 的 setForceShowIcon() 這個 method
                Method setForceIcons = classPopupHelper.getMethod("setForceShowIcon", boolean.class);
                // invoke 該 method...
                setForceIcons.invoke(menuPopupHelper, showIcon);
                break;
            }
        }
    catch (Exception e)
    {
        e.printStackTrace();
    }
}

// 使用時變成
PopupMenu menu = ...;
...
hackPopupMenuToShowIcon(menu, true);

感覺就像台灣的新聞記者......

  • 馬總統正在公園慢跑。

硬要寫成

  • 馬總統英九先生目前正在一個被稱作公園的地方執行一個慢跑的動作。

沒有留言: