情景菜單ContextMenu的創建

各位大大我也是初學者,寫這些主要是爲了對其認識更加清晰,同時也爲了以後忘記時可以快速的回憶起。這是我第一次看源碼在結合其他大大的思想得到的總結,可能會有很多問題,如果你們發現怎麼不對可以告訴我,我很希望能得到你們的評論


在我介紹之前我們先看下菜單相關的類的關係


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的,每次彈出都會創建。













發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章