各位大大我也是初學者,寫這些主要是爲了對其認識更加清晰,同時也爲了以後忘記時可以快速的回憶起。這是我第一次看源碼在結合其他大大的思想得到的總結,可能會有很多問題,如果你們發現怎麼不對可以告訴我,我很希望能得到你們的評論
在我介紹之前我們先看下菜單相關的類的關係
1.MenuItem:主要保存菜單數據,和提供菜單條目該有的接口。
2.MenuItemImpl:對保存菜單數據接口的實現。
3.Menu:提供菜單還有的功能接口。
4.ContextMenu:對Menu功能的擴充。
5.MenuBuilder:真正的對ContextMenu的實現。
6.ContextMenuBuilder:因爲MenuBuilder是同時面向情景菜單和選項菜單的,所有增加這個類,讓其在基本菜單功能上再增加對情景菜單的功能。該類主要是管理菜單中的數據並提供對這些數據進行操作的方法。
7.MenuDialogHelper:該類主要是管理菜單窗口。
ContextMenu一般是長按某View彈出的菜單,但是也不一定,而已可以是單擊,滑動等。
顯示情景菜單有兩種方式,一種是長按某View,一種是調用OpenContextMenu方法。通過下面的圖我們知道其實本質都是調用showContextMenu()方法,只不過如果是長按觸發要做判斷,判斷長按事件是否已被處理。沒處理才調用showContextMenu()方法。下面我們就通過長按觸發開始分析創建過程吧。
1.觸發長按事件,該事件處理方如下:
public boolean performLongClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLongClickListener != null) {
handled = li.mOnLongClickListener.onLongClick(View.this);
}
//長按事件是否已被處理
if (!handled) {
//如果沒被處理就顯示情景菜單
handled = showContextMenu();
}
if (handled) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
return handled;
}
2.在showContextMenu()方法的內部其實調用的是mParent的showContextMenuForChild()方法,因爲在應用窗口中所有的view的根視圖都是DecorView對象。所有在DecorView中我們看到showContextMenuForChild()方法代碼如下:
public boolean showContextMenuForChild(View originalView) {
//1.得到數據爲空的ContextMenuBuilder對象
if (mContextMenu == null) {
//創建ContextMenuBuilder對象
mContextMenu = new ContextMenuBuilder(getContext());
mContextMenu.setCallback(mContextMenuCallback);
} else {
//清空ContextMenuBuilder中的菜單數據
mContextMenu.clearAll();
}
//2.顯示情景菜單,並將情景菜單管理類對象返回
final MenuDialogHelper helper = mContextMenu.show(originalView,
originalView.getWindowToken());
if (helper != null) {
//這個估計是這是回調對象?
helper.setPresenterCallback(mContextMenuCallback);
} else if (mContextMenuHelper != null) {
//如果沒有新的菜單可以顯示,但是當前有一個菜單顯示就關閉它
mContextMenuHelper.dismiss();
}
//3.更新當前情景菜單管理對象
mContextMenuHelper = helper;
return helper != null;
}
3. 從上面我們可以看出show方法纔是其主要作用的。那麼讓我們看看他是怎麼挑大樑的。有圖有真像,大家請看代碼:
public MenuDialogHelper show(View originalView, IBinder token) {
if (originalView != null) {
// Let relevant views and their populate context listeners populate
// the context menu
//爲菜單添加數據
originalView.createContextMenu(this);
}
if (getVisibleItems().size() > 0) {
EventLog.writeEvent(50001, 1);
//創建對應的菜單管理類
MenuDialogHelper helper = new MenuDialogHelper(this);
//通過token顯示菜單窗口
helper.show(token);
return helper;
}
return null;
}
4.其實上面方法中的的這個createContextMenu方法我想了很久,但是我卻沒法很好的理解這個方法中的意圖。下面是這個方法的代碼:
public void createContextMenu(ContextMenu menu) {
ContextMenuInfo menuInfo = getContextMenuInfo();
// Sets the current menu info so all items added to menu will have
// my extra info set.
((MenuBuilder)menu).setCurrentMenuInfo(menuInfo); // @1
//這個方法是View中的,實際上在源碼中什麼都沒做
onCreateContextMenu(menu);
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnCreateContextMenuListener != null) {
//調用Activity中onCreateContextMenu方法爲Menu添加數據,所以在觸發前要先使用該方法registerForContextMenu爲某View註冊
li.mOnCreateContextMenuListener.onCreateContextMenu(menu, this, menuInfo);
}
// Clear the extra information so subsequent items that aren't mine don't
// have my extra info.
((MenuBuilder)menu).setCurrentMenuInfo(null);//@2
if (mParent != null) {
mParent.createContextMenu(menu);//@3
}
}
在這個代碼這我不清楚爲什麼在@1,@2處 爲什麼這麼做ContextMenuInfo本身就是個空接口,這樣做什麼意義呢?爲以後複寫什麼的準備? 第二我思考了很久都不懂爲什麼還要給父View創建菜單呢? 沒辦法等以後再來看看。
5.在第3步方法中的show(token)方法。這個方法其實創建並顯示在第4步添加數據的菜單的Dialog, 其中這個show(token)方法中參數是爲了綁定創建的Dialog, 這樣做我的理解是:是爲了告訴別人這個Dialog是我的下屬,其實他們是朋友的。
public void show(IBinder windowToken) {
// Many references to mMenu, create local reference
final MenuBuilder menu = mMenu;
// Get the builder for the dialog
final AlertDialog.Builder builder = new AlertDialog.Builder(menu.getContext());
mPresenter = new ListMenuPresenter(builder.getContext(),
com.android.internal.R.layout.list_menu_item_layout);
mPresenter.setCallback(this);
mMenu.addMenuPresenter(mPresenter);
builder.setAdapter(mPresenter.getAdapter(), this);
// Set the title
final View headerView = menu.getHeaderView();
if (headerView != null) {
// Menu's client has given a custom header view, use it
builder.setCustomTitle(headerView);
} else {
// Otherwise use the (text) title and icon
builder.setIcon(menu.getHeaderIcon()).setTitle(menu.getHeaderTitle());
}
// Set the key listener
builder.setOnKeyListener(this);
// Show the menu
mDialog = builder.create();
mDialog.setOnDismissListener(this);
WindowManager.LayoutParams lp = mDialog.getWindow().getAttributes();
lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
if (windowToken != null) {
lp.token = windowToken;
}
lp.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
mDialog.show();
}
在上面的代碼中爲給Dialog設置鍵盤堅挺事件,是因爲在顯示情景菜單中要對“menu”和“back”鍵做一些處理。
總結:從上看情景菜單主要就兩步(1)通過調用複寫的方法onCreateContextMenu給菜單添加數據。(2)創建一個Dialog來顯示菜單。
特別要主要的是選項菜單是屬於Activity的,並且只用在第一次按Menu鍵的時候創建。而情景菜單是屬於註冊的某View的,每次彈出都會創建。