Android 物理按鍵菜單實現流程

寫在前面,轉載者注:

    該文章主要分析了phonewindow中對expanded Menu處理的流程,介紹的較爲詳細,可供參考。代碼是基於4.1的,同樣適用於4.4。



1. PhoneWindow.onKeyDown() 

     1. onKeyDownPanel.

      當Menu鍵按下去之後,會產生一個KeyEvent,是keyDown事件,如果Activity沒有處理這個Menu Down事件,就會由PhonwWindow默認onKeyDown處理。

在onKeyDown中只要就是調用了onKeyDownPanel把事件傳遞給對應的panel。     

  1. protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) {  
  2.       
  3.     switch (keyCode) {  
  4.         ... ...  
  5.         case KeyEvent.KEYCODE_MENU: {  
  6.             onKeyDownPanel((featureId < 0) ? FEATURE_OPTIONS_PANEL : featureId, event);  
  7.             return true;  
  8.         }  
  9.         ... ...  
  10.     }  
  11.   
  12.     return false;  
  13. }  

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。

  1. public final boolean onKeyDownPanel(int featureId, KeyEvent event) {  
  2.     final int keyCode = event.getKeyCode();  
  3.       
  4.     if (event.getRepeatCount() == 0) {  
  5.         // The panel key was pushed, so set the chording key  
  6.         mPanelChordingKey = keyCode;  
  7.   
  8.         PanelFeatureState st = getPanelState(featureId, true);  
  9.         if (!st.isOpen) {  
  10.             return preparePanel(st, event);  
  11.         }  
  12.     }  
  13.   
  14.     return false;  
  15. }  

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

         這個getPanelState也很簡單,如果存在與featureId對應的PanelFeatureState就直接返回,沒有的就new一個新的。也就是如果如果我們再當前Activity中,如果是第一次按menu的話,應該是不存在FEATURE_OPTIONS_PANEL相對應的PanelFeatureState。於是就會new一個新的出來。

  1. private PanelFeatureState getPanelState(int featureId, boolean required,  
  2.         PanelFeatureState convertPanelState) {  
  3.   
  4.     PanelFeatureState[] ar;  
  5.     if ((ar = mPanels) == null || ar.length <= featureId) {  
  6.         PanelFeatureState[] nar = new PanelFeatureState[featureId + 1];  
  7.         if (ar != null) {  
  8.             System.arraycopy(ar, 0, nar, 0, ar.length);  
  9.         }  
  10.         mPanels = ar = nar;  
  11.     }  
  12.   
  13.     PanelFeatureState st = ar[featureId];  
  14.     if (st == null) {  
  15.         ar[featureId] = st = (convertPanelState != null)  
  16.                 ? convertPanelState  
  17.                 : new PanelFeatureState(featureId);  
  18.     }  
  19.     return st;  
  20. }  


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;

  1. public final boolean preparePanel(PanelFeatureState st, KeyEvent event) {  
  2.   
  3.     final Callback cb = getCallback();  
  4.   
  5.     if (cb != null) {  
  6.         st.createdPanelView = cb.onCreatePanelView(st.featureId);  
  7.     }  
  8.   
  9.     if (st.createdPanelView == null) {  
  10.         // Init the panel state's menu--return false if init failed  
  11.         if (st.menu == null || st.refreshMenuContent) {  
  12.             if (st.menu == null) {  
  13.                 if (!initializePanelMenu(st) || (st.menu == null)) {  
  14.                     return false;  
  15.                 }  
  16.             }  
  17.   
  18.             if (mActionBar != null) {  
  19.                 if (mActionMenuPresenterCallback == null) {  
  20.                     mActionMenuPresenterCallback = new ActionMenuPresenterCallback();  
  21.                 }  
  22.                 mActionBar.setMenu(st.menu, mActionMenuPresenterCallback);  
  23.             }  
  24.   
  25.             st.menu.stopDispatchingItemsChanged();  
  26.             st.refreshMenuContent = false;  
  27.         }  
  28.   
  29.         // Callback and return if the callback does not want to show the menu  
  30.   
  31.         // Preparing the panel menu can involve a lot of manipulation;  
  32.         // don't dispatch change events to presenters until we're done.  
  33.         st.menu.stopDispatchingItemsChanged();  
  34.   
  35.   
  36.         if (!cb.onPreparePanel(st.featureId, st.createdPanelView, st.menu)) {  
  37.             if (mActionBar != null) {  
  38.                 // The app didn't want to show the menu for now but it still exists.  
  39.                 // Clear it out of the action bar.  
  40.                 mActionBar.setMenu(null, mActionMenuPresenterCallback);  
  41.             }  
  42.             st.menu.startDispatchingItemsChanged();  
  43.             return false;  
  44.         }  
  45.         ... ...  
  46.     }  
  47.   
  48.     // Set other state  
  49.     st.isPrepared = true;  
  50.     st.isHandled = false;  
  51.     mPreparedPanel = st;  
  52.   
  53.     return true;  
  54. }  


2. PhoneWindow.onKeyUp()

     跟KeyDown對應,按鍵起來之後會有一個up事件。而Menu的up事件也是又PhoneWindow來處理。

     1. onKeyUpPanel

  1. protected boolean onKeyUp(int featureId, int keyCode, KeyEvent event) {  
  2.         ... ...  
  3.         case KeyEvent.KEYCODE_MENU: {  
  4.             onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId,  
  5.                     event);  
  6.             return true;  
  7.         }  
  8.         ... ...  
  9. }  

2.1 PhoneWindow.onKeyUpPanel(int featureId, KeyEvent event)

     1.  如果panel在前面的down事件中已經prepare成功了,調用  openPanel(st, event);去打開option menu。並且把    playSoundEffect 設爲 true;

          PS: 會打出Event log。

     2. 

  1. public final void onKeyUpPanel(int featureId, KeyEvent event) {  
  2.   
  3.     // The panel key was released, so clear the chording key  
  4.     if (mPanelChordingKey != 0) {  
  5.         ... ...              
  6.         boolean playSoundEffect = false;  
  7.         final PanelFeatureState st = getPanelState(featureId, true);  
  8.         if (featureId == FEATURE_OPTIONS_PANEL && mActionBar != null &&  
  9.                 mActionBar.isOverflowReserved()) {  
  10.             if (mActionBar.getVisibility() == View.VISIBLE) {  
  11.                 if (!mActionBar.isOverflowMenuShowing()) {  
  12.                     if (!isDestroyed() && preparePanel(st, event)) {  
  13.                         playSoundEffect = mActionBar.showOverflowMenu();  
  14.                     }  
  15.                 } else {  
  16.                     playSoundEffect = mActionBar.hideOverflowMenu();  
  17.                 }  
  18.             }  
  19.         } else {  
  20.             if (st.isOpen || st.isHandled) {  
  21.   
  22.                 // Play the sound effect if the user closed an open menu (and not if  
  23.                 // they just released a menu shortcut)  
  24.                 playSoundEffect = st.isOpen;  
  25.   
  26.                 // Close menu  
  27.                 closePanel(st, true);  
  28.   
  29.             } else if (st.isPrepared) {  
  30.                 boolean show = true;  
  31.                 if (st.refreshMenuContent) {  
  32.                     // Something may have invalidated the menu since we prepared it.  
  33.                     // Re-prepare it to refresh.  
  34.                     st.isPrepared = false;  
  35.                     show = preparePanel(st, event);  
  36.                 }  
  37.   
  38.                 if (show) {  
  39.                     // Write 'menu opened' to event log  
  40.                     EventLog.writeEvent(500010);  
  41.   
  42.                     // Show menu  
  43.                     openPanel(st, event);  
  44.   
  45.                     playSoundEffect = true;  
  46.                 }  
  47.             }  
  48.         }  
  49.   
  50.         if (playSoundEffect) {  
  51.             AudioManager audioManager = (AudioManager) getContext().getSystemService(  
  52.                     Context.AUDIO_SERVICE);  
  53.             if (audioManager != null) {  
  54.                 audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);  
  55.             } else {  
  56.                 Log.w(TAG, "Couldn't get audio manager");  
  57.             }  
  58.         }  
  59.     }  
  60. }  

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就顯示出來了。

  1. private void openPanel(PanelFeatureState st, KeyEvent event) {  
  2.   
  3.      Callback cb = getCallback();  
  4.      if ((cb != null) && (!cb.onMenuOpened(st.featureId, st.menu))) {  
  5.          // Callback doesn't want the menu to open, reset any state  
  6.          closePanel(st, true);  
  7.          return;  
  8.      }  
  9.   
  10.      final WindowManager wm = getWindowManager();  
  11.   
  12.      int width = WRAP_CONTENT;  
  13.      if (st.decorView == null || st.refreshDecorView) {  
  14.          if (st.decorView == null) {  
  15.              // Initialize the panel decor, this will populate st.decorView  
  16.              if (!initializePanelDecor(st) || (st.decorView == null))  
  17.                  return;  
  18.          } else if (st.refreshDecorView && (st.decorView.getChildCount() > 0)) {  
  19.              // Decor needs refreshing, so remove its views  
  20.              st.decorView.removeAllViews();  
  21.          }  
  22.   
  23.          // This will populate st.shownPanelView  
  24.          if (!initializePanelContent(st) || !st.hasPanelItems()) {  
  25.              return;  
  26.          }  
  27.   
  28.          ViewGroup.LayoutParams lp = st.shownPanelView.getLayoutParams();  
  29.          if (lp == null) {  
  30.              lp = new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);  
  31.          }  
  32.   
  33.          int backgroundResId;  
  34.          if (lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {  
  35.              // If the contents is fill parent for the width, set the  
  36.              // corresponding background  
  37.              backgroundResId = st.fullBackground;  
  38.              width = MATCH_PARENT;  
  39.          } else {  
  40.              // Otherwise, set the normal panel background  
  41.              backgroundResId = st.background;  
  42.          }  
  43.          st.decorView.setWindowBackground(getContext().getResources().getDrawable(  
  44.                  backgroundResId));  
  45.   
  46.          ViewParent shownPanelParent = st.shownPanelView.getParent();  
  47.          if (shownPanelParent != null && shownPanelParent instanceof ViewGroup) {  
  48.              ((ViewGroup) shownPanelParent).removeView(st.shownPanelView);  
  49.          }  
  50.          st.decorView.addView(st.shownPanelView, lp);  
  51.   
  52.          /* 
  53.           * Give focus to the view, if it or one of its children does not 
  54.           * already have it. 
  55.           */  
  56.          if (!st.shownPanelView.hasFocus()) {  
  57.              st.shownPanelView.requestFocus();  
  58.          }  
  59.      } else if (!st.isInListMode()) {  
  60.          width = MATCH_PARENT;  
  61.      } else if (st.createdPanelView != null) {  
  62.          // If we already had a panel view, carry width=MATCH_PARENT through  
  63.          // as we did above when it was created.  
  64.          ViewGroup.LayoutParams lp = st.createdPanelView.getLayoutParams();  
  65.          if (lp != null && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {  
  66.              width = MATCH_PARENT;  
  67.          }  
  68.      }  
  69.   
  70.      st.isOpen = true;  
  71.      st.isHandled = false;  
  72.   
  73.      WindowManager.LayoutParams lp = new WindowManager.LayoutParams(  
  74.              width, WRAP_CONTENT,  
  75.              st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG,  
  76.              WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM  
  77.              | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,  
  78.              st.decorView.mDefaultOpacity);  
  79.   
  80.      if (st.isCompact) {  
  81.          lp.gravity = getOptionsPanelGravity();  
  82.          sRotationWatcher.addWindow(this);  
  83.      } else {  
  84.          lp.gravity = st.gravity;  
  85.      }  
  86.   
  87.      lp.windowAnimations = st.windowAnimations;  
  88.        
  89.      wm.addView(st.decorView, lp);  
  90.      // Log.v(TAG, "Adding main menu to window manager.");  
  91.  }  

2.1.1.1     PhoneWindow.initializePanelDecor(PanelFeatureState st) 

        new出一個DecorView並且賦值給st.decorView。

  1. protected boolean initializePanelDecor(PanelFeatureState st) {  
  2.     st.decorView = new DecorView(getContext(), st.featureId);  
  3.     st.gravity = Gravity.CENTER | Gravity.BOTTOM;  
  4.     st.setStyle(getContext());  
  5.   
  6.     return true;  
  7. }  

2.1.1.2 PhoneWindow. initializePanelContent(PanelFeatureState st) 

     //初始化Panel的MenuView,並賦給st.shownPanelView;

  1. protected boolean initializePanelContent(PanelFeatureState st) {  
  2.     if (st.createdPanelView != null) {  
  3.         st.shownPanelView = st.createdPanelView;  
  4.         return true;  
  5.     }  
  6.   
  7.     if (st.menu == null) {  
  8.         return false;  
  9.     }  
  10.   
  11.     if (mPanelMenuPresenterCallback == null) {  
  12.         mPanelMenuPresenterCallback = new PanelMenuPresenterCallback();  
  13.     }  
  14.   
  15.     MenuView menuView = st.isInListMode()  
  16.             ? st.getListMenuView(getContext(), mPanelMenuPresenterCallback)  
  17.             : st.getIconMenuView(getContext(), mPanelMenuPresenterCallback);  
  18.   
  19.     st.shownPanelView = (View) menuView;  
  20.   
  21.     if (st.shownPanelView != null) {  
  22.         // Use the menu View's default animations if it has any  
  23.         final int defaultAnimations = menuView.getWindowAnimations();  
  24.         if (defaultAnimations != 0) {  
  25.             st.windowAnimations = defaultAnimations;  
  26.         }  
  27.         return true;  
  28.     } else {  
  29.         return false;  
  30.     }  
  31. }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章