美文网首页
Android 获取、移除 View 的 OnClickList

Android 获取、移除 View 的 OnClickList

作者: _发强 | 来源:发表于2018-09-02 11:51 被阅读0次

    之前在代码中设置的通过 View.isClickable 去控制 View 的重复点击,昨天突然发现即使控制了,仍然能够再次触发点击事件,让我很是懵逼。

    后来翻阅一系列的资料之后,发现了 View.setOnClickListener 源码中的这段代码:

        /**
         * Register a callback to be invoked when this view is clicked. If this view is not
         * clickable, it becomes clickable.
         *
         * @param l The callback that will run
         *
         * @see #setClickable(boolean)
         */
        public void setOnClickListener(@Nullable OnClickListener l) {
            if (!isClickable()) {
                setClickable(true);
            }
            getListenerInfo().mOnClickListener = l;
        }
    

    我不知道这里是在哪个版本改的,我只知道,以后不能通过 isClickable 属性去随性的控制点击事件了。 😤

    因为我代码里很多都是通过 isClickable 属性控制,并且是统一控制,不可能去一个个的修改。所以,只能针对于这个问题去解决了。

    通过 View 的源码,我们可以看到 它的 ClickListener 是传递给了 getListenerInfo() 返回值的 mOnClickListener 去处理。我们来看看这个方法返回的类型(在 View 源码的 5989 行)。

        ListenerInfo getListenerInfo() {
            if (mListenerInfo != null) {
                return mListenerInfo;
            }
            mListenerInfo = new ListenerInfo();
            return mListenerInfo;
        }
    

    通过名字我们能猜出个大概,就是一些 监听事件的信息。来看一下对象内容(View.java 4208行):

        static class ListenerInfo {
            /**
             * Listener used to dispatch focus change events.
             * This field should be made private, so it is hidden from the SDK.
             * {@hide}
             */
            protected OnFocusChangeListener mOnFocusChangeListener;
    
            /**
             * Listeners for layout change events.
             */
            private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
    
            protected OnScrollChangeListener mOnScrollChangeListener;
    
            /**
             * Listeners for attach events.
             */
            private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;
    
            /**
             * Listener used to dispatch click events.
             * This field should be made private, so it is hidden from the SDK.
             * {@hide}
             */
            public OnClickListener mOnClickListener;
    
            /**
             * Listener used to dispatch long click events.
             * This field should be made private, so it is hidden from the SDK.
             * {@hide}
             */
            protected OnLongClickListener mOnLongClickListener;
    
            /**
             * Listener used to dispatch context click events. This field should be made private, so it
             * is hidden from the SDK.
             * {@hide}
             */
            protected OnContextClickListener mOnContextClickListener;
    
            /**
             * Listener used to build the context menu.
             * This field should be made private, so it is hidden from the SDK.
             * {@hide}
             */
            protected OnCreateContextMenuListener mOnCreateContextMenuListener;
    
            private OnKeyListener mOnKeyListener;
    
            private OnTouchListener mOnTouchListener;
    
            private OnHoverListener mOnHoverListener;
    
            private OnGenericMotionListener mOnGenericMotionListener;
    
            private OnDragListener mOnDragListener;
    
            private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;
    
            OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
    
            OnCapturedPointerListener mOnCapturedPointerListener;
        }
    
    

    根据刚刚 OnClickListener 事件处理的方式,再看到这个对象中的这么多参数,我们基本可以猜到 View 中的大多数 Listener 都交由到这里来存储控制了。再加上 ListenerInfo 是一个非公开的内部类,我们这里想要获取到 View 的 onClickListener( 或者其他) 事件,只能通过反射去操作了。

    另外,因为 ListenerInfo 是 View 类中的对象,所以我们在获取该对象时,如果你的 操作类型 不是 View 本身,而是其子类(TextView、ImageView等等)的话,那么就需要获取父类中的属性对象或方法。

    这里是获取当前对象 中的 某一个方法,会进行向上查找:

        /**
         * https://blog.csdn.net/zmx729618/article/details/51320688
         *
         *  获取当前类型的 某个方法
         */
        fun getDeclaredMethod(obj: Any, methodName: String, vararg parameterTypes: Class<*>): Method? {
            var method: Method? = null
            var clazz: Class<*> = obj.javaClass
            while (clazz != Any::class.java) {
                try {
                    method = clazz.getDeclaredMethod(methodName, *parameterTypes)
    //                method?.isAccessible = true
                    return method
                } catch (e: Exception) {
                    //这里甚么都不要做!并且这里的异常必须这样写,不能抛出去。
                    //如果这里的异常打印或者往外抛,则就不会执行clazz = clazz.getSuperclass(),最后就不会进入到父类中了
                }
                clazz = clazz.superclass
            }
            return null
        }
    

    通过上面方法,我们可以获取到 ListenerInfo 对象:

        fun getListenerInfo(view: View): Any? {
            val method = getDeclaredMethod(view, "getListenerInfo")
            method?.isAccessible = true
            val info = method?.invoke(view)
            return info
        }
    

    接下来就是获取其对应的属性参数了,(我这里是调试的 OnClickListener ,可以根据自己需要获取相关属性)

        fun getOnClickListener(view: View): View.OnClickListener? {
            val info = getListenerInfo(view)
            info?.let {
                val m = getFieldValue(it, "mOnClickListener") as View.OnClickListener?
                return m
            }
            return null
        }
    

    我这边的需求是暂时屏蔽掉 OnClickListener , 那么不能通过 isClickable 控制了,我就只能先把它设置为 null 了。

        fun removeOnClickListener(view: View) {
            val info = getListenerInfo(view)
            info?.let {
                setFieldValue(it, "mOnClickListener", null)
            }
        }
    

    在实际应用场景中,操作流程大概是当 某个按钮点击之后,进行数据提交,在提交过程中,不允许该按钮再次触发事件。

    下面是演示代码:

            vc_tv_click.setOnClickListener {
                Log.i("TAG", "ViewClick")
            }
    
            val click = getOnClickListener(vc_tv_click)
    
            vc_btn_cancel.setOnClickListener {
                removeOnClickListener(vc_tv_click)
            }
            vc_btn_reset.setOnClickListener {
                vc_tv_click.setOnClickListener(click)
            }
    

    到此,基本该操作就完成了。
    示例代码

    在本篇博客中,我们既能获取到 OnClickListener (用于工具方法中,保存临时点击事件, 事件操作完之后,用来恢复点击事件) ,又能暂时移除 OnClickListener。
    这种方式,我们也可以用来控制 Android 中重复点击的问题。
    个人感觉这个应该比那些控制间隔时间 1s 、2s 内不触发点击事件要优雅许多。并且是从根源上处理 触发事件。

    相关文章

      网友评论

          本文标题:Android 获取、移除 View 的 OnClickList

          本文链接:https://www.haomeiwen.com/subject/zwzpwftx.html