Android4.1 Open Menu的过程



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.1  PhoneWindow.onKeyDownPanel(int featureId, KeyEvent event)

      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;
    }

1.1.1 PhoneWindow. getPanelState(int featureId, boolean required, PanelFeatureState convertPanelState)

         这个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.1.2 PhoneWindow.preparePanel(PanelFeatureState st, KeyEvent event)

        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;
    }


2. PhoneWindow.onKeyUp()

     跟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;
            }
            ... ...
    }

2.1 PhoneWindow.onKeyUpPanel(int featureId, KeyEvent event)

     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");
                }
            }
        }
    }

2.1.1 PhoneWindow.openPanel(PanelFeatureState st, KeyEvent event)

      在打开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;
    }

2.1.1.2 PhoneWindow. initializePanelContent(PanelFeatureState st) 

     //初始化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;
        }
    }




你可能感兴趣的:(Android4.1 Open Menu的过程)