ActionBar Option Menu创建流程

Option Menu

Option Menu,从翻译来看是“选择菜单”的意思,然而某些专业名词就不应该给它翻译,大家简单认为它是在ActionBar上面的一种菜单即可。在Android中,菜单还有其它几种,例如上下文菜单——Context Menu,弹出菜单——PopupMenu。其中Option Menu和Context Menu都是通过Activity中的回调方法来创建的。

相信大家也很清楚Option Menu如何使用。在Activity里的回调方法onCreateOptionsMenu方法中添加菜单项到Menu中,并返回true即可:

1
2
3
4
5
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}

然后在方法onOptionsItemSelected对菜单项的点击事件进行监听:

1
2
3
4
5
6
7
8
9
10
11
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action1 :
return true;
case android.R.id.home :
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}

Option Menu创建流程

使用hierarchyviewer工具观察带有Option Menu的窗口,可以知道这些菜单的按钮是在Action Bar上面的。

入口

我前面的文章 《Android中窗口添加ActionBar》 中,PhoneWindow创建一套ActionBar的layout添加到当前窗口中。就是在installDecor方法中,创建了布局后,post一个任务(menu创建也算比较复杂,post应该是为了不要拖慢启动速度)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
if (decorContentParent != null) {
....
// Invalidate if the panel menu hasn't been created before this.
// Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
// being called in the middle of onCreate or similar.
// A pending invalidation will typically be resolved before the posted message
// would run normally in order to satisfy instance state restoration.
PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
if (!isDestroyed() && (st == null || st.menu == null) && !mIsStartingWindow) {
//触发刷新菜单
invalidatePanelMenu(FEATURE_ACTION_BAR);
}
} else {
...
}
}
}
@Override
public void invalidatePanelMenu(int featureId) {
mInvalidatePanelMenuFeatures |= 1 << featureId;
if (!mInvalidatePanelMenuPosted && mDecor != null) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
mInvalidatePanelMenuPosted = true;
}
}

开始创建

上面post一个任务来进行菜单的创建,从Runnable的实现知道是从PhoneWindowdoInvalidatePanelMenu方法开始,随即会进入preparePanel开始菜单的详细创建流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public final boolean preparePanel(PanelFeatureState st, KeyEvent event) {
...
//对应Activity的窗口,这个Callback就是Activity。Activity实现了这个接口,并在attach方法中对Window设置了这个Callback
final Callback cb = getCallback();
//这个回调是提供给应用自定义菜单面板的,一般不会重写这个方法
if (cb != null) {
st.createdPanelView = cb.onCreatePanelView(st.featureId);
}
final boolean isActionBarMenu =
(st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR);
...
//下面这段就是设置option menu的。可以看到,如果上面的onCreatePanelView
if (st.createdPanelView == null) {
// Init the panel state's menu--return false if init failed
if (st.menu == null || st.refreshMenuContent) {
if (st.menu == null) {
if (!initializePanelMenu(st) || (st.menu == null)) {
return false;
}
}
if (isActionBarMenu && mDecorContentParent != null) {
if (mActionMenuPresenterCallback == null) {
mActionMenuPresenterCallback = new ActionMenuPresenterCallback();
}
mDecorContentParent.setMenu(st.menu, mActionMenuPresenterCallback);
}
// Call callback, and return if it doesn't want to display menu.
// Creating the panel menu will involve a lot of manipulation;
// don't dispatch change events to presenters until we're done.
st.menu.stopDispatchingItemsChanged();
if ((cb == null) || !cb.onCreatePanelMenu(st.featureId, st.menu)) {
// Ditch the menu created above
st.setMenu(null);
if (isActionBarMenu && mDecorContentParent != null) {
// Don't show it in the action bar either
mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
}
return false;
}
st.refreshMenuContent = false;
}
...
if (!cb.onPreparePanel(st.featureId, st.createdPanelView, st.menu)) {
if (isActionBarMenu && mDecorContentParent != null) {
// The app didn't want to show the menu for now but it still exists.
// Clear it out of the action bar.
mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
}
st.menu.startDispatchingItemsChanged();
return false;
}
...
st.menu.startDispatchingItemsChanged();
}
...
return true;
}

未完待续

Comments