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;
}
}
转载请注明出处