起因
最近发现一个快速点击的问题,纵使有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);
补充更新
在使用的过程中,发现有一些View不需要防快速点击,得过滤掉这一部分View
解决:为View设置tag,用来区分这些View
xml写法:
java代码写法:
踩坑回来
1.当跨模块使用的时候,tag在不同的资源文件里id的名字取一个就行,名字一样取出来的id也一样
2.id名字一样的View,ViewId也一样。这样在同一activity,都叫iv_delete
的两个View。会被认为是一个View,得自行处理(加上tag、手动调用等)
完整代码
public class ViewHook {
private static final String TAG = "ViewHook";
IProxyClickListener mInnerClickProxy;
Field sHookField;
Method sHookMethod;
//是否已经设置了代理
final int mPrivateTagKey = R.id.view_tag_proxy_click;
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 && view.getTag(R.id.view_tag_not_proxy_click) == null) {
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;
}
}