全局点击事件监听拦截
起因
最近发现一个快速点击的问题,纵使有RxView.clicks
。或者其他的方法,但是每次要在setOnClickListener
里处理,而且老是忘加。得有一个全局的懒人神器来解决。
思路
首先肯定是放在BaseActivity里的,用反射去获取setOnClickListener
的事件,并且(动态)代理这个接口。
自定义注解+反射?那还是得每个都添注解,不符合要求。
1.对DecorView
的视图树遍历,这样就能得到,页面所有的View。
2.根据View获取到View.OnClickListener
3.利用反射 将View的mOnClickListener,设置成我们自己的代理mOnClickListener(做一层拦截)
,Field.set()
实现
观察
setOnClickListener
其实就是将new的OnClickListener赋值给了getListenerInfo()的mOnClickListener
获取View,可以通过遍历根布局,获取当前页的所有View,也就是遍历DecorView
RecyclerView 的回收复用机制,会清空item,导致没法这样操作,要过滤掉
为OnClickListener设置代理
想要mOnClickListener
得取到ListenerInfo
--> 也就是getListenerInfo()
mOnClickListener是ListenerInfo的一个成员变量。
ListenerInfo是View的一个内部类,也就是getListenerInfo()。
所以
这时候已经获取到了View.OnClickListener
,得为他设置代理,然后中间插入我们的代码。
补充:View设置过了,就为他设置一个int的tag,标记,防止重复的设置
view.setTag(mPrivateTagKey, recycledContainerDeep);
完整代码
public class ViewHook { IProxyClickListener mInnerClickProxy; Field sHookField; Method sHookMethod; //是否已经设置了代理 final int mPrivateTagKey = 424; public void initHookClick() { if (sHookMethod == null) { try { Class viewClass = Class.forName("android.view.View"); if (viewClass != null) { sHookMethod = viewClass.getDeclaredMethod("getListenerInfo"); if (sHookMethod != null) { sHookMethod.setAccessible(true); } } } catch (Exception e) { reportError(e, "init"); } } if (sHookField == null) { try { Class listenerInfoClass = Class.forName("android.view.View$ListenerInfo"); if (listenerInfoClass != null) { sHookField = listenerInfoClass.getDeclaredField("mOnClickListener"); if (sHookField != null) { sHookField.setAccessible(true); } } } catch (Exception e) { reportError(e, "init"); } } } private void hookClickListener(View view, int recycledContainerDeep, boolean forceHook) { boolean needHook = forceHook; if (!needHook) { needHook = view.isClickable(); if (needHook && recycledContainerDeep == 0) { needHook = view.getTag(mPrivateTagKey) == null; } } if (needHook) { try { Object getListenerInfo = sHookMethod.invoke(view); View.OnClickListener baseClickListener = getListenerInfo == null ? null : (View.OnClickListener) sHookField.get(getListenerInfo);//获取已设置过的监听器 if ((baseClickListener != null && !(baseClickListener instanceof IProxyClickListener.WrapClickListener))) { sHookField.set(getListenerInfo, new IProxyClickListener.WrapClickListener(baseClickListener, mInnerClickProxy)); view.setTag(mPrivateTagKey, recycledContainerDeep); } } catch (Exception e) { reportError(e, "hook"); } } } public void hookViews(View view, int recycledContainerDeep) { if (view.getVisibility() == View.VISIBLE) { boolean forceHook = recycledContainerDeep == 1; if (view instanceof ViewGroup) { boolean existAncestorRecycle = recycledContainerDeep > 0; ViewGroup p = (ViewGroup) view; if (!(p instanceof AbsListView || p instanceof RecyclerView) || existAncestorRecycle) { hookClickListener(view, recycledContainerDeep, forceHook); if (existAncestorRecycle) { recycledContainerDeep++; } } else { recycledContainerDeep = 1; } int childCount = p.getChildCount(); for (int i = 0; i < childCount; i++) { View child = p.getChildAt(i); hookViews(child, recycledContainerDeep); } } else { hookClickListener(view, recycledContainerDeep, forceHook); } } } private void reportError(Exception e, String init) { Log.e(TAG, init); } public void onDestroy() { sHookField = null; sHookMethod = null; mInnerClickProxy = null; } } 复制代码
public interface IProxyClickListener { boolean onProxyClick(WrapClickListener wrap, View v); class WrapClickListener implements View.OnClickListener { public static int oldId = -1; public static long lastClickTime; IProxyClickListener mProxyListener; View.OnClickListener mBaseListener; public WrapClickListener(View.OnClickListener l, IProxyClickListener proxyListener) { mBaseListener = l; mProxyListener = proxyListener; } @Override public void onClick(View v) { boolean handled = mProxyListener == null ? false : mProxyListener.onProxyClick(WrapClickListener.this, v); if (!handled && mBaseListener != null) { if (avoidDoubleClick(v)){ mBaseListener.onClick(v); } } } /** * 快速点击拦截 */ public boolean avoidDoubleClick(View v) { int viewId = v.getId(); if (oldId == -1) { lastClickTime = SystemClock.elapsedRealtime(); oldId = viewId; } else if (viewId == oldId) { long time = SystemClock.elapsedRealtime(); if (time - lastClickTime < 1000) { return false; } lastClickTime = SystemClock.elapsedRealtime(); } else { lastClickTime = SystemClock.elapsedRealtime(); oldId = viewId; } return true; } } } 复制代码
public abstract class BaseActivity extends AppCompatActivity { ViewHook viewHook; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getLayoutId()); init(); viewHook = new ViewHook(); viewHook.initHookClick(); getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { viewHook.hookViews(getWindow().getDecorView(), 0); } }); } abstract public int getLayoutId(); abstract public void init(); @Override protected void onDestroy() { super.onDestroy(); viewHook.onDestroy(); viewHook = null; } }
作者:铁头娃wawa
链接:https://juejin.cn/post/7021056193439203335