1. PhoneWindow.onKeyDown()
1. onKeyDownPanel.
当Menu键按下去之后,会产生一个KeyEvent,是keyDown事件,如果Activity没有处理这个Menu Down事件,就会由PhonwWindow默认onKeyDown处理。
在onKeyDown中只要就是调用了onKeyDownPanel把事件传递给对应的panel。
protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) { switch (keyCode) { ... ... case KeyEvent.KEYCODE_MENU: { onKeyDownPanel((featureId < 0) ? FEATURE_OPTIONS_PANEL : featureId, event); return true; } ... ... } return false; }
1. getPanelState()
根据featureId获得对应的PanelFeatureState,PanelFeatureState用来保存一个panel对象,比如option menu是对应一个PanelFeatureState 和 featureId。
2. preparePanel()
由于是第一次得到PanelFeatureState,st.isOpen返回false,所以就去preparePanel。
public final boolean onKeyDownPanel(int featureId, KeyEvent event) { final int keyCode = event.getKeyCode(); if (event.getRepeatCount() == 0) { // The panel key was pushed, so set the chording key mPanelChordingKey = keyCode; PanelFeatureState st = getPanelState(featureId, true); if (!st.isOpen) { return preparePanel(st, event); } } return false; }
这个getPanelState也很简单,如果存在与featureId对应的PanelFeatureState就直接返回,没有的就new一个新的。也就是如果如果我们再当前Activity中,如果是第一次按menu的话,应该是不存在FEATURE_OPTIONS_PANEL相对应的PanelFeatureState。于是就会new一个新的出来。
private PanelFeatureState getPanelState(int featureId, boolean required, PanelFeatureState convertPanelState) { PanelFeatureState[] ar; if ((ar = mPanels) == null || ar.length <= featureId) { PanelFeatureState[] nar = new PanelFeatureState[featureId + 1]; if (ar != null) { System.arraycopy(ar, 0, nar, 0, ar.length); } mPanels = ar = nar; } PanelFeatureState st = ar[featureId]; if (st == null) { ar[featureId] = st = (convertPanelState != null) ? convertPanelState : new PanelFeatureState(featureId); } return st; }
1. 通过Callback(Activity)的onCreatePanelView去创建一个panel,用户可以复写这个方法, 如果用户没有复写,则st.createPanelView = null。
2. initializePanelMenu(), 初始化st中的MenuBuilder,用于以后创建Menu。
3. 调用cb.onPreparePanel,就会调用Activity的onPreaparePanel方法,而在onPreparePanel中会调用onPrepareOptionsMenu去完成 option menu的prepare工作。完成之后把st中的状态设置一下。 st.isPrepared = true; st.isHandled = false; mPreparedPanel = st;
public final boolean preparePanel(PanelFeatureState st, KeyEvent event) { final Callback cb = getCallback(); if (cb != null) { st.createdPanelView = cb.onCreatePanelView(st.featureId); } 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 (mActionBar != null) { if (mActionMenuPresenterCallback == null) { mActionMenuPresenterCallback = new ActionMenuPresenterCallback(); } mActionBar.setMenu(st.menu, mActionMenuPresenterCallback); } st.menu.stopDispatchingItemsChanged(); st.refreshMenuContent = false; } // Callback and return if the callback does not want to show the menu // Preparing the panel menu can involve a lot of manipulation; // don't dispatch change events to presenters until we're done. st.menu.stopDispatchingItemsChanged(); if (!cb.onPreparePanel(st.featureId, st.createdPanelView, st.menu)) { if (mActionBar != null) { // The app didn't want to show the menu for now but it still exists. // Clear it out of the action bar. mActionBar.setMenu(null, mActionMenuPresenterCallback); } st.menu.startDispatchingItemsChanged(); return false; } ... ... } // Set other state st.isPrepared = true; st.isHandled = false; mPreparedPanel = st; return true; }
跟KeyDown对应,按键起来之后会有一个up事件。而Menu的up事件也是又PhoneWindow来处理。
1. onKeyUpPanel
protected boolean onKeyUp(int featureId, int keyCode, KeyEvent event) { ... ... case KeyEvent.KEYCODE_MENU: { onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId, event); return true; } ... ... }
1. 如果panel在前面的down事件中已经prepare成功了,调用 openPanel(st, event);去打开option menu。并且把 playSoundEffect 设为 true;
PS: 会打出Event log。
2.
public final void onKeyUpPanel(int featureId, KeyEvent event) { // The panel key was released, so clear the chording key if (mPanelChordingKey != 0) { ... ... boolean playSoundEffect = false; final PanelFeatureState st = getPanelState(featureId, true); if (featureId == FEATURE_OPTIONS_PANEL && mActionBar != null && mActionBar.isOverflowReserved()) { if (mActionBar.getVisibility() == View.VISIBLE) { if (!mActionBar.isOverflowMenuShowing()) { if (!isDestroyed() && preparePanel(st, event)) { playSoundEffect = mActionBar.showOverflowMenu(); } } else { playSoundEffect = mActionBar.hideOverflowMenu(); } } } else { if (st.isOpen || st.isHandled) { // Play the sound effect if the user closed an open menu (and not if // they just released a menu shortcut) playSoundEffect = st.isOpen; // Close menu closePanel(st, true); } else if (st.isPrepared) { boolean show = true; if (st.refreshMenuContent) { // Something may have invalidated the menu since we prepared it. // Re-prepare it to refresh. st.isPrepared = false; show = preparePanel(st, event); } if (show) { // Write 'menu opened' to event log EventLog.writeEvent(50001, 0); // Show menu openPanel(st, event); playSoundEffect = true; } } } if (playSoundEffect) { AudioManager audioManager = (AudioManager) getContext().getSystemService( Context.AUDIO_SERVICE); if (audioManager != null) { audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK); } else { Log.w(TAG, "Couldn't get audio manager"); } } } }
在打开panel之前,会对平台的屏幕进行检测,如果发现不是手机,就直接返回。
1. initializePanelDecor(PanelFeatureState st) //初始化对应panel的DecorView; 如果menu没有item的话,就直接return
2. initializePanelContent(PanelFeatureState st), //初始化Panel的MenuView,并赋给st.shownPanelView;
3. 把st.shownPanelView寄到decorView中,然后通过wm.addView(st.decorView, lp);把新建出来的option menu的decorView加到WMS中。
这样option menu就显示出来了。
private void openPanel(PanelFeatureState st, KeyEvent event) { Callback cb = getCallback(); if ((cb != null) && (!cb.onMenuOpened(st.featureId, st.menu))) { // Callback doesn't want the menu to open, reset any state closePanel(st, true); return; } final WindowManager wm = getWindowManager(); int width = WRAP_CONTENT; if (st.decorView == null || st.refreshDecorView) { if (st.decorView == null) { // Initialize the panel decor, this will populate st.decorView if (!initializePanelDecor(st) || (st.decorView == null)) return; } else if (st.refreshDecorView && (st.decorView.getChildCount() > 0)) { // Decor needs refreshing, so remove its views st.decorView.removeAllViews(); } // This will populate st.shownPanelView if (!initializePanelContent(st) || !st.hasPanelItems()) { return; } ViewGroup.LayoutParams lp = st.shownPanelView.getLayoutParams(); if (lp == null) { lp = new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); } int backgroundResId; if (lp.width == ViewGroup.LayoutParams.MATCH_PARENT) { // If the contents is fill parent for the width, set the // corresponding background backgroundResId = st.fullBackground; width = MATCH_PARENT; } else { // Otherwise, set the normal panel background backgroundResId = st.background; } st.decorView.setWindowBackground(getContext().getResources().getDrawable( backgroundResId)); ViewParent shownPanelParent = st.shownPanelView.getParent(); if (shownPanelParent != null && shownPanelParent instanceof ViewGroup) { ((ViewGroup) shownPanelParent).removeView(st.shownPanelView); } st.decorView.addView(st.shownPanelView, lp); /* * Give focus to the view, if it or one of its children does not * already have it. */ if (!st.shownPanelView.hasFocus()) { st.shownPanelView.requestFocus(); } } else if (!st.isInListMode()) { width = MATCH_PARENT; } else if (st.createdPanelView != null) { // If we already had a panel view, carry width=MATCH_PARENT through // as we did above when it was created. ViewGroup.LayoutParams lp = st.createdPanelView.getLayoutParams(); if (lp != null && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) { width = MATCH_PARENT; } } st.isOpen = true; st.isHandled = false; WindowManager.LayoutParams lp = new WindowManager.LayoutParams( width, WRAP_CONTENT, st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, st.decorView.mDefaultOpacity); if (st.isCompact) { lp.gravity = getOptionsPanelGravity(); sRotationWatcher.addWindow(this); } else { lp.gravity = st.gravity; } lp.windowAnimations = st.windowAnimations; wm.addView(st.decorView, lp); // Log.v(TAG, "Adding main menu to window manager."); }
2.1.1.1 PhoneWindow.initializePanelDecor(PanelFeatureState st)
new出一个DecorView并且赋值给st.decorView。
protected boolean initializePanelDecor(PanelFeatureState st) { st.decorView = new DecorView(getContext(), st.featureId); st.gravity = Gravity.CENTER | Gravity.BOTTOM; st.setStyle(getContext()); return true; }
//初始化Panel的MenuView,并赋给st.shownPanelView;
protected boolean initializePanelContent(PanelFeatureState st) { if (st.createdPanelView != null) { st.shownPanelView = st.createdPanelView; return true; } if (st.menu == null) { return false; } if (mPanelMenuPresenterCallback == null) { mPanelMenuPresenterCallback = new PanelMenuPresenterCallback(); } MenuView menuView = st.isInListMode() ? st.getListMenuView(getContext(), mPanelMenuPresenterCallback) : st.getIconMenuView(getContext(), mPanelMenuPresenterCallback); st.shownPanelView = (View) menuView; if (st.shownPanelView != null) { // Use the menu View's default animations if it has any final int defaultAnimations = menuView.getWindowAnimations(); if (defaultAnimations != 0) { st.windowAnimations = defaultAnimations; } return true; } else { return false; } }