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;
}
}
轉載請註明出處