文本输入自动校验工具(二) 焦点自动释放

        上一个版本的焦点自动释放工具有一个明显的缺陷:那就是在初始化时为每一个布局都设置了一个新的OnTouchListener,这样会覆盖掉用户或其他框架设置的OnTouchListener,为了不影响原先的触摸逻辑,需要对初始化方法进行优化。

        如何保障原先的触摸逻辑不受影响呢?因为View.OnTouchListener是一个接口,可以使用动态代理来拓展原先触摸监听器的逻辑(类似于AOP,面向切片编程),首先需要获取到控件的触摸监听器。View的所有监听器是保存在mListenerInfo里的,mListenerInfo保存了几乎所有常用的监听器(如点击监听器(OnClickListener)、布局变化监听器(OnLayoutChangeListener)、聚焦变化监听器(OnFocusChangeListener)),同样也包括触摸监听器。因为View类没有暴露OnTouchListener这个属性,所以需要用反射获取。

        同样ListenerInfo的触摸监听器也是私有的(private),并且没有提供get方法,所以需要第二层反射。下面这个方法(getOnTouchListener)用于获取View的触摸监听器。

/**
     * get onTouchListener from view
     * 通过反射获取View的触摸监听器
     * @param childAt   view
     * @return view's onTouchListener
     */
    private View.OnTouchListener getOnTouchListener(View childAt) {
        View.OnTouchListener onTouchListener = null;
        try {	//通过反射获取控件的mListenerInfo
            Field _fListenerInfo = View.class.getDeclaredField("mListenerInfo"); 
            _fListenerInfo.setAccessible(true);
            Object listenerInfo = _fListenerInfo.get(childAt);   //mListenerInfo
            if(DBG) Log.e(TAG,"_fListenerInfo->"+_fListenerInfo+",listenerInfo->"+listenerInfo);
            if(listenerInfo!=null){	//listenerInfo为空表示此控件没有设置任何监听器
                Field _fOnTouchListener = listenerInfo.getClass().getDeclaredField("mOnTouchListener"); 
                _fOnTouchListener.setAccessible(true);
                Object obj =  _fOnTouchListener.get(listenerInfo);
                if(DBG) Log.e(TAG,"onTouchListener->"+obj);
                if(obj instanceof View.OnTouchListener){	//获取触摸监听器
                    onTouchListener = (View.OnTouchListener) obj;
                }
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        } finally {
            if(onTouchListener == null) onTouchListener = (v, event) -> false;
        }
        return onTouchListener;
    }

为了使用动态代理拓展View原先的触摸逻辑,需要创建一个InvocationHandler。

class ExternalTouchHandler implements InvocationHandler {
        View.OnTouchListener onTouchListener;

        ExternalTouchHandler(View.OnTouchListener onTouchListener){
            this.onTouchListener = onTouchListener;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //因为OnTouchListener接口只有一个方法,不需要考虑方法区分。
            Boolean bool = (Boolean) method.invoke(onTouchListener,args);
            detectClick();	//先执行原先的触摸逻辑,然后检测焦点。
            return bool;
        }
    }

然后是创建触摸代理方法。

private View.OnTouchListener generateTouchProxy(View.OnTouchListener onTouchListener){
        return (View.OnTouchListener) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{View.OnTouchListener.class},new ExternalTouchHandler(onTouchListener));
    }

最后优化初始化方法。

/**
* 初始化点击探测器
* @param root 拦截点击事件的根布局
*/
@SuppressLint("ClickableViewAccessibility")
public void init(ViewGroup root){
        if(imm==null){
            imm = (InputMethodManager) root.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        }
        for(int i=0;i<root.getChildCount();i++){
            if(root.getChildAt(i) instanceof ViewGroup){ root.getChildAt(i).setOnTouchListener(generateTouchProxy(getOnTouchListener(root.getChildAt(i))));
                init((ViewGroup) root.getChildAt(i));	//递归初始化内层布局
     }
   }
}

完整代码:

https://github.com/IdleFishEngineer/ValueInputHelper/blob/main/library/src/main/java/com/klaus/library/valueinputhelper/util/EditFocusHelper.java

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值