我的编程空间,编程开发者的网络收藏夹
学习永远不晚

android中怎么全局监控click事件

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

android中怎么全局监控click事件

本篇文章给大家分享的是有关android中怎么全局监控click事件,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。

方式一,适配监听接口,预留全局处理接口并作为所有监听器的基类使用

抽象出公共基类监听对象,可预留拦截机制和通用点击处理,简要代码如下:

public abstract class CustClickListener implements View.OnClickListener{  @Override  public void onClick(View view) {    if(!interceptViewClick(view)){      onViewClick(view);    }  }  protected boolean interceptViewClick(View view){    //TODO:这里可做一此通用的处理如打点,或拦截等。    return false;  }  protected abstract void onViewClick(View view);}

使用方式之一匿名对象作为公共监听器

CustClickListener mClickListener = new CustClickListener() {  @Override  protected void onViewClick(View view) {    Toast.makeText(CustActvity.this, view.toString(), Toast.LENGTH_SHORT).show();  }};@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity_login);  findViewById(R.id.button).setOnClickListener(mClickListener);}

这种方式比较简单,无兼容问题,但是需要自始至终都要使用基于基类的监听器对象,对开发者约束比较大。适用于新项目之初就有此使用约定。对于老代码重构工作量比较大,而且如果接入第三方墨盒模块就无能为力了。

方式二,反射代理,适时偷梁换柱开发者无感知,在适配包装器里做通用处理。

以下是代理接口和内置监听适配器,全局的监听接口需要实现IProxyClickListener并设置到内置适配器WrapClickListener里

public interface IProxyClickListener {  boolean onProxyClick(WrapClickListener wrap, View v);    class WrapClickListener implements View.OnClickListener {      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) {        mBaseListener.onClick(v);      }    }  }}

我们需要选择一个时机对所有设置有监听器的 View做监听代理的 hook .这个时机可以对 Activity 的根View添加一个视图变化监听(当然也可选择在 Activity 的 DOWN 事件的分发时机):

rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {   @Override   public void onGlobalLayout() {    hookViews(rootView, 0)      }});

注:以上为了方便匿名注册了监听,实际使用在 Activity 退出时要反注册掉。

在进行代理前先要反射获取View监听器相关的 Method 和 Field 对象如下:

public void init() {  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");    }  }}

只有保证了sHookMethod和sHookField成功获取才能进入下一步递归去设置监听代理偷梁换柱。以下为具体实现递归设置代理监听的过程。其中mInnerClickProxy为外部传入的的全局处理点击事件的代理接口。

private 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 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");    }  }}

以上深度优先从 Activity 的根 View 进行递归设置监听。只会对原来的 View 本身有点击的事件监听器的进行设置,成功设置后还会对操作的 View 设置一个 tag 标志表明已经设置了代理,避免每次变化重复设置。这个 tag 具有一定的含意,记录该 View 相对可能存在的可回收容器的层级数。因为对于像AbsListView或RecyclerView的直接子 View 是需要强制重新绑定代理的,因为它们的复用机制可能被重新设置了监听。

此方式实现实现稍微复杂,但是实现效果比较好,对开发者无感知进行监听器的hook代理。反射效率上也可以接受速度比较快无影响。对任何设置了监听器的 View都有效。 然而AbsListView的Item点击无效,因为它的点击事件不是通过 onClick 实现的,除非不是用 setItemOnClick 而是自己绑定 click 事件。

方式三,通过AccessibilityDelegate捕获点击事件。

分析View的源码在处理点击事件的回调时调用了 View.performClick 方法,内部调用了sendAccessibilityEvent而此方法有个托管接口mAccessibilityDelegate可以由外部处理所有的 AccessibilityEvent. 正好此托管接口的设置也是开放的setAccessibilityDelegate,如以下 View 源码关键片段。

public boolean performClick() {  final boolean result;  final ListenerInfo li = mListenerInfo;  if (li != null && li.mOnClickListener != null) {    playSoundEffect(SoundEffectConstants.CLICK);    li.mOnClickListener.onClick(this);    result = true;  } else {    result = false;  }  sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  return result;}public void sendAccessibilityEvent(int eventType) {  if (mAccessibilityDelegate != null) {    mAccessibilityDelegate.sendAccessibilityEvent(this, eventType);  } else {    sendAccessibilityEventInternal(eventType);  }}public void setAccessibilityDelegate(@Nullable AccessibilityDelegate delegate) {  mAccessibilityDelegate = delegate;}

基于此原理我们可在某个时机给所有的 View 注册我们自己的AccessibilityDelegate去监听系统行为事件,简要实现代码如下。

public class ViewClickTracker extends View.AccessibilityDelegate {  boolean mInstalled = false;  WeakReference<View> mRootView = null;  ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener = null;  public ViewClickTracker(View rootView) {    if (rootView != null && rootView.getViewTreeObserver() != null) {      mRootView = new WeakReference(rootView);      mOnGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {        @Override        public void onGlobalLayout() {          View root = mRootView == null ? null : mRootView.get();          boolean install = ;          if (root != null && root.getViewTreeObserver() != null && root.getViewTreeObserver().isAlive()) {            try {              installAccessibilityDelegate(root);              if (!mInstalled) {                mInstalled = true;              }            } catch (Exception e) {              e.printStackTrace();            }          } else {            destroyInner(false);          }        }      };      rootView.getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);    }  }  private void installAccessibilityDelegate(View view) {    if (view != null) {      view.setAccessibilityDelegate(ViewClickTracker.this);      if (view instanceof ViewGroup) {        ViewGroup parent = (ViewGroup) view;        int count = parent.getChildCount();        for (int i = 0; i < count; i++) {          View child = parent.getChildAt(i);          if (child.getVisibility() != View.GONE) {            installAccessibilityDelegate(child);          }        }      }    }  }  @Override  public void sendAccessibilityEvent(View host, int eventType) {    super.sendAccessibilityEvent(host, eventType);    if (AccessibilityEvent.TYPE_VIEW_CLICKED == eventType && host != null) {     //TODO 这里处理通用的点击事件,host 即为相应被点击的 View.    }  }}

以上实现比较巧妙,在监测到window上全局视图树发生变化后递归的给所有的View安装AccessibilityDelegate。经测试大多数厂商的机型和版本都是可以的,然而部分机型无法成功捕获监控到点击事件,所以不推荐使用。

方式四,通过分析 Activity 的 dispatchTouchEvent 事件并查找事件接受的目标 View。

这个方式初看有点匪夷所思,但是一系列触屏事件发生后总归要有一个组件消耗了它,查看ViewGroup关键源码如下:

// First touch target in the linked list of touch targets.private TouchTarget mFirstTouchTarget;public boolean dispatchTouchEvent(MotionEvent ev) {  ......  if (newTouchTarget == null && childrenCount != 0) {    for (int i = childrenCount - 1; i >= 0; i--) {      if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {        newTouchTarget = addTouchTarget(child, idBitsToAssign);        alreadyDispatchedToNewTouchTarget = true;        break;      }    }  }  ......  // Dispatch to touch targets.  if (mFirstTouchTarget == null) {    // No touch targets so treat this as an ordinary view.    handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);  } else {    // Dispatch to touch targets, excluding the new touch target if we already    // dispatched to it. Cancel touch targets if necessary.    TouchTarget predecessor = null;    TouchTarget target = mFirstTouchTarget;    while (target != null) {      final TouchTarget next = target.next;      if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {        handled = true;      } else {        final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;        ......        if (cancelChild) {          if (predecessor == null) {            mFirstTouchTarget = next;          } else {            predecessor.next = next;          }          target.recycle();          target = next;          continue;        }      }      predecessor = target;      target = next;    }  }}

这里发现意愿接受 touch 事件的 直接子View 都会被添加到mFirstTouchTarget这个链式对象里,且链经过调整后 next 几乎总是 null. 这就给我们一个突破口。可以从mFirstTouchTarget.child 得到当前接受事件的直接子 View , 然后按此方法递归去查找直至mFirstTouchTarget.child 为 null。我们就算是找到了最终 touch 事件的接受者。这个查找最好的时机应该是在ACTION_UP 或 ACTION_CANCEL 。

通过以上原理我们可以有法获取一系列 Touch 事件最终接受处理的目标 View,再根据我们记录的按下位置和松开位置及偏移偏量可判断是否为可能的点击动作。为了加强判断是否为真正的 click 事件,可进一步分析目标 View 是否安装了点击监听器(原理可参考上面讲的方式二。以下获取和分析事件时机都是在 Activity 的 dispatchTouchEvent 方法中进行的。

记录 down 和 up 事件后,以下为实现判断是否为可能的点击判断

//whether it could be a click actionpublic boolean isClickPossible(float slop) {  if (mCancel || mDownId == -1 || mUpId == -1 || mDownTime == 0 || mUpTime == 0) {    return false;  } else {    return Math.abs(mDownX - mUpX) < slop && Math.abs(mDownY - mUpY) < slop;  }}

在 up 事件发生后立即查找目标 View.首先要保证反射 mFirstTouchTarge 相关的准备工作。

private boolean ensureTargetField() {  if (sTouchTargetField == null) {    try {      Class viewClass = Class.forName("android.view.ViewGroup");      if (viewClass != null) {        sTouchTargetField = viewClass.getDeclaredField("mFirstTouchTarget");        sTouchTargetField.setAccessible(true);      }    } catch (Exception e) {      e.printStackTrace();    }    try {      if (sTouchTargetField != null) {        sTouchTargetChildField = sTouchTargetField.getType().getDeclaredField("child");        sTouchTargetChildField.setAccessible(true);      }    } catch (Exception e) {      e.printStackTrace();    }  }  return sTouchTargetField != null && sTouchTargetChildField != null;}

然后从 Activity 的 DecorView 去递归查找目标 View .

// find the target view who is interest in the touch event. null if not findprivate View findTargetView() {  View nextTarget, target = null;  if (ensureTargetField() && mRootView != null) {    nextTarget = findTargetView(mRootView);    do {      target = nextTarget;      nextTarget = null;      if (target instanceof ViewGroup) {        nextTarget = findTargetView((ViewGroup) target);      }    } while (nextTarget != null);  }  return target;}//reflect to find the TouchTarget child view,null if not found .private View findTargetView(ViewGroup parent) {  try {    Object target = sTouchTargetField.get(parent);    if (target != null) {      Object view = sTouchTargetChildField.get(target);      if (view instanceof View) {        return (View) view;      }    }  } catch (Exception e) {    e.printStackTrace();  }  return null;}

以上就是android中怎么全局监控click事件,小编相信有部分知识点可能是我们日常工作会见到或用到的。希望你能通过这篇文章学到更多知识。更多详情敬请关注编程网行业资讯频道。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

android中怎么全局监控click事件

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

android中怎么全局监控click事件

本篇文章给大家分享的是有关android中怎么全局监控click事件,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。方式一,适配监听接口,预留全局处理接口并作为所有监听器的基类使
2023-05-30

VUE中的click事件怎么解析

今天就跟大家聊聊有关VUE中的click事件怎么解析,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。1. 概述老话说的好:努力帮别人解决难题,你的难题也就不难解决了。言归正传,今天我们
2023-06-26

怎么监控Linux文件系统事件

这篇文章将为大家详细讲解有关怎么监控Linux文件系统事件,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。历史简介在 inotify 之前有 dnotify。不幸的是,dnotify 有局限性,用户需要更好
2023-06-16

Linux监控文件事件怎么配置

要监控Linux文件事件,您可以使用inotify工具。inotify是Linux内核提供的一个机制,用于监视文件系统中发生的事件。以下是配置Linux监控文件事件的步骤:检查inotify是否已安装:在终端中运行以下命令检查inotif
2023-10-26

Android中怎么利用组合控件复用布局

本篇文章为大家展示了Android中怎么利用组合控件复用布局,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。首先,我们需要写出布局文件layout_custom_titlebar.xml。
2023-05-30

如何在Android中使用hover组件监控鼠标移动事件

如何在Android中使用hover组件监控鼠标移动事件?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。Android之前对于鼠标光标事件的监控非常少,4.0之后
2023-05-31

Android中的监听触摸事件怎么在Fragment中实现

本篇文章为大家展示了Android中的监听触摸事件怎么在Fragment中实现,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。activity的触摸事件 @Override public boolea
2023-05-31

Python怎么实现多线程的事件监控

这篇文章主要介绍“Python怎么实现多线程的事件监控”,在日常操作中,相信很多人在Python怎么实现多线程的事件监控问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Python怎么实现多线程的事件监控”的疑
2023-06-15

Android中怎么利用NavigationView头部设置监听事件

今天就跟大家聊聊有关Android中怎么利用NavigationView头部设置监听事件,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。1、将XML里的静态引入删除:
2023-05-30

怎么在Android中给布局、控件添加阴影效果

怎么在Android中给布局、控件添加阴影效果?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。1,直接使用属性: android:elevation="4dp"这样一句代码,
2023-05-30

Vue组件的自定义事件和全局事件总线怎么使用

这篇“Vue组件的自定义事件和全局事件总线怎么使用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Vue组件的自定义事件和全局
2023-07-05

怎么处理WinForm中的控件事件

在WinForm中处理控件事件通常需要以下步骤:打开窗体设计器,选择要处理事件的控件,例如按钮、文本框等。双击控件,会自动创建一个事件处理方法,并打开代码视图。在事件处理方法中编写处理事件的代码,例如处理按钮点击事件时可以编写相应的逻辑
怎么处理WinForm中的控件事件
2024-04-08

Android事件分发中事件是怎么来的

本文小编为大家详细介绍“Android事件分发中事件是怎么来的”,内容详细,步骤清晰,细节处理妥当,希望这篇“Android事件分发中事件是怎么来的”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。Andriod事件
2023-07-05

Android中SeekBar控件怎么使用

在Android中,SeekBar是一个可拖动的滑动条控件,可以用来选择一个数值范围。下面是在Android中使用SeekBar的一般步骤:在XML布局文件中添加SeekBar控件,例如:这个例子创建了一个SeekBar控件,并设置了最大值
2023-10-23

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录