弹出后进行按钮事件监听
页面中,使用了 Spinner
控件,在控件弹出后,需要通过遥控器或者手柄等设备对 Spinner 的选中进行监听。
但是在 Spinner 弹出后,在 Activity 或者 Window 中都无法再监听到 dispatchKeyEvent
事件。
查阅源码,发现 Spinner 在点击后,会弹出一个 Dialog, 而 Dialog
是在界面上创建一个独立的 Window
,故而无法在原有的 Activity 中监听到事件
为了继续监听到点击事件,可以使用反射获取新的 窗口控件:
反射获取 Spinner 的 PopupWindow
private ListPopupWindow invokeGetSpinnerWindow(AppCompatSpinner settingSpinner) {
if (settingSpinner == null) {
return null;
}
Class<?> spinnerCla = settingSpinner.getClass();
try {
Field field = spinnerCla.getDeclaredField("mPopup");
field.setAccessible(true);
Object obj = field.get(settingSpinner);
if (obj == null) { // 如果 AppCompatSpinner 的 popup 为空,则向其父类查找
spinnerCla = spinnerCla.getSuperclass();
field = spinnerCla.getDeclaredField("mPopup");
field.setAccessible(true);
obj = field.get(settingSpinner);
}
// spinnerMode 为 dropdown
if (obj instanceof ListPopupWindow) {
return (ListPopupWindow) obj;
}
if(obj instanceof Dialog){
LogUtil.i("设置了 Spinner spinnerMode 为 Dialog");
}
} catch (NoSuchFieldException | IllegalAccessException e) {
LogUtil.w("hook settingSpinner pop window faild: " + LogUtil.objToString(e));
}
return null;
}
上方是具体的反射方法。如果调用成功,获取到了 PopupWindow 对象之后,可以为其设置点击事件,这样即可自行处理:
ListPopupWindow popupWindow = invokeGetSpinnerWindow(settingSpinner);
if (popupWindow == null) {
LogUtil.w("invoke settingSpinner window faild");
} else {
LogUtil.d(popupWindow);
popupWindow.getListView().setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
LogUtil.v("onKey: " + keyCode);
return false;
}
});
}
代码调用关闭 Spinner
基于上方的反射代码:
ListPopupWindow popupWindow = invokeGetSpinnerWindow(settingSpinner);
成功获取对象后,调用它的 dismiss 即可:
popupWindow.dismiss();
弹出 Spinner 后,监听关闭事件
Spinner 内部的 window 关闭时,想要获取它已经关闭,然后处理界面上的控件。但是接口并没有直接提供设置的方法,只能通过反射,来监听它的弹出状态
try {
PopupWindow window = (PopupWindow) InvokeTool.invokeGet(popupWindow, "mPopup");
if (window != null) {
PopupWindow.OnDismissListener dismissListener =
(PopupWindow.OnDismissListener) InvokeTool.invokeGet(window, "mOnDismissListener");
if (dismissListener != null) {
popupWindow.setOnDismissListener(() -> {
LogUtil.d("OnDismissListener ...");
dismissListener.onDismiss();
if (this.menuOpenListener != null) {
this.menuOpenListener.menuState(SettingItemView.this, false);
}
// 将关闭监听对象重新设置进去
popupWindow.setOnDismissListener(dismissListener);
});
} else {
LogUtil.d("dismissListener is null");
}
} else {
LogUtil.d("window is null: " + LogUtil.objToString(popupWindow));
}
} catch (Exception ignore) {
LogUtil.d("error : " + LogUtil.objToString(ignore));
}
其中使用到的 InvokeTool 代码如下:
public class InvokeTool {
public static Object invokeGet(Object objv, String name) {
return invokeGet(objv, name, true);
}
/**
* 反射获取对象
*
* @param objv
* @param name
* @param checkSuper
* @return
*/
public static Object invokeGet(Object objv, String name, boolean checkSuper) {
if (objv == null || TextUtils.isEmpty(name)) {
LogUtil.d(" param null ");
return null;
}
Object retObj = null;
Field field = null;
try {
field = objv.getClass().getDeclaredField(name);
field.setAccessible(true);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
if (field != null) {
try {
retObj = field.get(objv);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
if (retObj == null && checkSuper) {
Class<?> superCla = objv.getClass().getSuperclass();
if (superCla != null) {
try {
field = superCla.getDeclaredField(name);
field.setAccessible(true);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
if (field != null) {
try {
retObj = field.get(objv);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
return retObj;
}
}