PopupMenu彈出位置的控制

PopupMenu作爲彈出菜單是很好用的,但是默認只能彈出在view的下方,而實際中這樣的彈出位置可能無法滿足需求,比如自定義的canvasView,要在canvasView長按的位置彈出菜單,PopupMenu只有一個show的方法,沒有可以設置位置的方法,但當我們跟進源碼去看時發現了這樣的一段代碼:

PopupMenu.class

public void show() {
        this.mPopup.show();
    }

再對mPopup.show跟蹤時進入到了MenuPopupHelper,又有如下的代碼

  public void show() {
        if (!this.tryShow()) {
            throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
        }
    }

    public void show(int x, int y) {
        if (!this.tryShow(x, y)) {
            throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
        }
    }

    public boolean tryShow() {
        if (this.isShowing()) {
            return true;
        } else if (this.mAnchorView == null) {
            return false;
        } else {
            this.showPopup(0, 0, false, false);
            return true;
        }
    }

this.mPopup.show調用的是tryShow,而tryShow又調用的是showPopup,在showPopup的參數中有傳入xOffset和yOffset,這說明裏面是有傳偏移量的,再仔細看MenuPopupHelper的show函數發現有show(x,y)的重載,如果我們能調用show(x,y),可能就能滿足需求。

但MenuPopupHelper又沒辦法直接得到,是包裝到PopupMenu中的,於是我們採用反射的方式來獲取,代碼如下

  Field field = popupMenu.getClass().getDeclaredField("mPopup");
            field.setAccessible(true);
            MenuPopupHelper helper = (MenuPopupHelper) field.get(popupMenu);
            helper.show(x, y);

通過反射是得到了MenuPopupHelper,但是會提示錯誤MenuPopupHelper.show can only be called from within the same library group (groupId=com.android.support),跟入到MenuPopupHelper類裏面,可以看到有如下圖的限定

爲此,我們需要將剛剛反射的部分特別的封裝到一個方法中,並在方法上加入@SuppressLint("RestrictedApi"),這樣就可以正常運行了,代碼如下

public class MenuWorker implements PopupMenu.OnMenuItemClickListener
{
    @SuppressLint("RestrictedApi")
    private void showPopupMenu(int x, int y) {
        if (!_drawActivity.isEditing()) {
            return;
        }
        //創建彈出式菜單對象(最低版本11)
        PopupMenu popupMenu = new PopupMenu(_drawActivity, _view);//第二個參數是綁定的那個view,菜單彈出時默認是顯示該view下方的。
        initMenu(popupMenu.getMenu());
        //綁定菜單項的點擊事件
        popupMenu.setOnMenuItemClickListener(this);
        //顯示
        //popupMenu.show();//默認顯示在view的下方,如果要控制具體顯示位置,需要使用反射來實現。

        try {
            Field field = popupMenu.getClass().getDeclaredField("mPopup");
            field.setAccessible(true);
            MenuPopupHelper helper = (MenuPopupHelper) field.get(popupMenu);
            y = y - _view.getHeight();//如果y取的是觸摸點的位置,可能需要作此處理,經測試android5.1的設備會彈窗在屏幕之外
            helper.show(x, y);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }


    /**
     * 初始化菜單
     *
     * @param menu
     */
    public void initMenu(Menu menu) {
        menu.clear();
        
        menu.add(1, 10001, 0, "添加");
        menu.add(1, 10002, 1, "刪除");
        menu.add(1, 10003, 2, "切換");
        
    }

 /**
     * 菜單點擊
     *
     * @param menuItem
     * @return
     */
    @Override
    public boolean onMenuItemClick(MenuItem menuItem) {
        switch (menuItem.getItemId()) {
            case 10001: {
                //添加
                break;
            }
            case 10002: {
                //刪除
                break;
            }
            case 10003: {
                //切換;
                break;
            }
            default:
                break;
        }
        return false;
    }
}

轉載請註明出處

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