1. 關於 DEFAULT_KEYS_SHORTCUT 的 API文檔介紹
Use with setDefaultKeyMode(int) to execute a menu shortcut in default key handling.
That is, the user does not need to hold down the menu key to execute menu shortcuts.
從字面上看,其含義是指,將預設的按鍵輸入作為菜單快速鍵進行處理。
也就是說,使用者不需要按下menu按鍵,就可以處理菜單快速鍵,聽起來非常神奇,究竟是不是這樣呢?
--------------------------------------------------------------------------------
2.編寫樣本程式
我們編寫一個程式驗證一下其功能,首先建立一個工程,並設定預設按鍵模式為 DEFAULT_KEYS_SHORTCUT
[java]
package com.silenceburn;
import android.app.Activity;
import android.os.Bundle;
public class MenuShortCutTester extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT);
}
}
為預設的main.xml中的TextView增加一個id屬性,之後我們會用菜單選項控制這行字的顏色
[xhtml]
http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
android:id="@+id/myText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
使用findViewById擷取上一步中定義了id的文字物件,將其引用儲存在成員變數b中。
重寫onPrepareOptionsMenu方法,增加我們自己的功能表項目,並註冊快速鍵,同時增加菜單點擊的響應事件。
[java]
package com.silenceburn;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.widget.TextView;
public class MenuShortCutTester extends Activity {
/** Called when the activity is first created. */
TextView b;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
b = (TextView) this.findViewById(R.id.myText);
setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
// TODO Auto-generated method stub
super.onPrepareOptionsMenu(menu);
menu.removeItem(0);
menu.removeItem(1);
menu.add( 0, 0, 0, "One").setShortcut('0', '0').setOnMenuItemClickListener(new OnMenuItemClickListener(){
@Override
public boolean onMenuItemClick(MenuItem item) {
// TODO Auto-generated method stub
b.setBackgroundColor(android.graphics.Color.RED);
return true;
}});
menu.add( 0, 1, 0, "Two").setShortcut('1', '1').setOnMenuItemClickListener(new OnMenuItemClickListener(){
@Override
public boolean onMenuItemClick(MenuItem item) {
// TODO Auto-generated method stub
b.setBackgroundColor(android.graphics.Color.GREEN);
return true;
}});
return true;
}
}
注意我們一共註冊了兩個功能表項目,
一個叫“One”,點擊時將文字物件 b 的背景顏色改為紅色,同時定義其快速鍵為0
一個叫“Two”,點擊時將文字物件 b 的背景顏色改為綠色,同時定義其快速鍵為1
至此樣本程式完成。
--------------------------------------------------------------------------------
3.驗證使用樣本程式
啟動AVD,運行上述程式,程式啟動後,我們應當看到是黑底灰字,點擊menu按鈕,可以看到One和Two兩個菜單選項。
如所示:
目前Menu是開啟狀態,
點擊One ,將把“helloworld...”字樣的背景色變為紅色,
點擊Two ,將把“helloworld...”字樣的背景色變為紅綠色。
或者我們點設定好的快速鍵 0 和 1,發現可以直接調用菜單選項控制顏色變化。
到目前為止一切都很正常,不過,神奇的現在來了!
我們首先關閉菜單,
然後直接點鍵盤鍵"0“,看看會發生什麼。再直接點鍵盤鍵"1" ,看看會發生什麼。
哈哈,在沒有啟用菜單的情況下,功能表項目快速鍵被直接調用了!根本不需要開啟菜單,就可以用啟用菜單快速鍵!
什嗎?有位同學說快速鍵就應該是這樣子把,那好,請你把 onCreate 裡面的
setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT); 改為 setDefaultKeyMode(DEFAULT_KEYS_DISABLE);
然後再運行試試,在不開啟菜單的情況下,你就是把 0 和 1 按壞,系統也不會理你的呵呵
--------------------------------------------------------------------------------
4.淺析實現原理
那麼這神奇的功能是如何?的呢?我們試著通過分析android源碼找到答案。
首先順藤摸瓜,我們找一找系統是如何處理 DEFAULT_KEYS_SHORTCUT 關鍵字的,
在Activity.java中可以找到如下程式碼片段:
[java]
if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) {
return false;
} else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) {
if (getWindow().performPanelShortcut(Window.FEATURE_OPTIONS_PANEL,
keyCode, event, Menu.FLAG_ALWAYS_PERFORM_CLOSE)) {
return true;
}
return false;
}
由此可知,當系統檢測到 DEFAULT_KEYS_SHORTCUT 關鍵字時,實際調用了
getWindow().performPanelShortcut(Window.FEATURE_OPTIONS_PANEL,
keyCode, event,Menu.FLAG_ALWAYS_PERFORM_CLOSE)
我們繼續追尋,但是這裡會遇到一個困難,就是查閱API文檔你會發現,performPanelShortcut函數是個純虛函數!
接下來該怎麼辦呢?既然功能順利執行了,那麼這個純虛函數一定會有一個實現的。這個實作類別必然是window類的子類。
所以我們在OnCreate裡面加上一行代碼 Window w = this.getWindow();
然後通過Eclipse的調試器,利用RTTI查看其實作類別,結果如:
可以看的很清楚,實作類別是 PhoneWindow ,
這樣我們就可以到 PhoneWindow 的源碼中去尋找performPanelShortcut的實現了。
在PhoneWindow.java中我們可以看到如下程式碼片段:
[java]
// Only try to perform menu shortcuts if preparePanel returned true (possible false
// return value from application not wanting to show the menu).
if ((st.isPrepared || preparePanel(st, event)) && st.menu != null) {
// The menu is prepared now, perform the shortcut on it
handled = st.menu.performShortcut(keyCode, event, flags);
}
終於看到menu字樣了,這裡我們可以看到 if 裡面描述的調用條件,
首先當前panel必須已經準備好了(你可以用 onPreparePanel 截獲到準備請求),
其次,當前panel必須是有Menu的!(st.menu != null),
從這裡我們可以明白DEFAULT_KEYS_SHORTCUT對於沒有menu的應用是沒有任何效果的。
而且在另一處代碼我們會看到還要進行 isShortCut 的判斷,所以對於沒有快速鍵的菜單也是沒有任何效果的。
那麼我們再看看 preparePanel 裡面是如何?的,在其實現中可以找到如下程式碼片段:
[java]
// Callback and return if the callback does not want to show the menu
if (!cb.onPreparePanel(st.featureId, st.createdPanelView, st.menu)) {
return false;
}
至此,就完全明白了!代碼在這裡回調了 onPreparePanel ,而 onPreparePanel 中會回調 onPrepareOptionsMenu ,
而onPrepareOptionsMenu ,就是我們自己寫實現自訂菜單的地方了。
為了驗證上述推導,我們在onPrepareOptionsMenu 中放入斷點,然後在菜單關閉的情況下,輸入快速鍵,
運行到斷點後查看呼叫堆疊,入所示:
堆棧調用順序可以很清楚的看出我們的推導過程是正確的。至此 DEFAULT_KEYS_SHORTCUT 的實現分析完畢。