Spinner 弹出后进行按钮事件监听以及关闭监听( TV app开发)

Spinner 弹出后进行按钮事件监听以及关闭监听( TV app开发)

弹出后进行按钮事件监听

页面中,使用了 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;
    }
}

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://zwc365.com/2021/08/17/spinner-key-event

Buy me a cup of coffee ☕.