Android使用ScrollView导致鼠标点击事件无效

在这里插入图片描述

平台

测试平台:

  • RK3288 + Android8.1
  • RK3588 + Android 12

问题

 首先, 这个问题的前提是, 使用的输入设备是**鼠标**, 普通的触摸屏并不会出现这个问题. 大致的流程是APP的UI布局中采用ScrollView作为根容器, 之后添加各类子控件, 在一起准备就绪后, 使用鼠标进行功能测试, 出现无法点击控件触发事件响应.
<ScrollViewxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><LinearLayoutstyle="@style/settingsItems"><TextView style="@style/TV"android:text="XXX"android:layout_weight="1"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="BTN"/></LinearLayout><LinearLayoutstyle="@style/settingsItems"><TextView style="@style/TV"android:text="XXX"android:layout_weight="1"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="BTN"/></LinearLayout><!--可以写多几个--></LinearLayout>
</ScrollView>

分析

最先从onInterceptTouchEvent函数入手, 各个层级的LOG大致分析, 由下往上发现控件BUTTON中根本没有捕获到MotionEvent, 那原因只可能是父控件自己捕获了而不下发. 兜兜转转最终来到了ScrollView.

通过重写onInterceptHoverEvent 判断是否时间已被捕获

  @Overridepublic boolean onInterceptHoverEvent(MotionEvent event) {boolean b = super.onInterceptHoverEvent(event);Logger.d(TAG, "onInterceptHoverEvent " + b);return b;}

从输出的LOG可以看出来, 当使用鼠标的时候, TRUE 和 FALSE 均有可能出现(在后面排查是才发现这和控件处的位置有关), 当TRUE是, 说明事件由ScrollView处理了, 子控件自然就接收不到事件下发.

顺着onInterceptHoverEvent往上查:

  • frameworks/base/core/java/android/view/ViewGroup.java

        public boolean onInterceptHoverEvent(MotionEvent event) {if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {final int action = event.getAction();final float x = event.getX();final float y = event.getY();if ((action == MotionEvent.ACTION_HOVER_MOVE|| action == MotionEvent.ACTION_HOVER_ENTER) && isOnScrollbar(x, y)) {return true;}}return false;}
    

    从代码中可以看出, 基本的判断条件都是成立的, 鼠标输入 + MOVE/ENTER时间, 最后一个是isOnScrollbar, 顾名思义输入鼠标的位置在ScrollBar 上?

  • frameworks/base/core/java/android/view/View.java

    boolean isOnScrollbar(float x, float y) {if (mScrollCache == null) {return false;}x += getScrollX();y += getScrollY();if (isVerticalScrollBarEnabled() && !isVerticalScrollBarHidden()) {final Rect touchBounds = mScrollCache.mScrollBarTouchBounds;getVerticalScrollBarBounds(null, touchBounds);if (touchBounds.contains((int) x, (int) y)) {return true;}}if (isHorizontalScrollBarEnabled()) {final Rect touchBounds = mScrollCache.mScrollBarTouchBounds;getHorizontalScrollBarBounds(null, touchBounds);if (touchBounds.contains((int) x, (int) y)) {return true;}}return false;}private void getVerticalScrollBarBounds(@Nullable Rect bounds, @Nullable Rect touchBounds) {if (mRoundScrollbarRenderer == null) {getStraightVerticalScrollBarBounds(bounds, touchBounds);} else {getRoundVerticalScrollBarBounds(bounds != null ? bounds : touchBounds);}}private void getStraightVerticalScrollBarBounds(@Nullable Rect drawBounds,@Nullable Rect touchBounds) {final Rect bounds = drawBounds != null ? drawBounds : touchBounds;if (bounds == null) {return;}final int inside = (mViewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;final int size = getVerticalScrollbarWidth();int verticalScrollbarPosition = mVerticalScrollbarPosition;if (verticalScrollbarPosition == SCROLLBAR_POSITION_DEFAULT) {verticalScrollbarPosition = isLayoutRtl() ?SCROLLBAR_POSITION_LEFT : SCROLLBAR_POSITION_RIGHT;}final int width = mRight - mLeft;final int height = mBottom - mTop;switch (verticalScrollbarPosition) {default:case SCROLLBAR_POSITION_RIGHT:bounds.left = mScrollX + width - size - (mUserPaddingRight & inside);break;case SCROLLBAR_POSITION_LEFT:bounds.left = mScrollX + (mUserPaddingLeft & inside);break;}bounds.top = mScrollY + (mPaddingTop & inside);bounds.right = bounds.left + size;bounds.bottom = mScrollY + height - (mUserPaddingBottom & inside);if (touchBounds == null) {return;}if (touchBounds != bounds) {touchBounds.set(bounds);}final int minTouchTarget = mScrollCache.scrollBarMinTouchTarget;if (touchBounds.width() < minTouchTarget) {final int adjust = (minTouchTarget - touchBounds.width()) / 2;if (verticalScrollbarPosition == SCROLLBAR_POSITION_RIGHT) {touchBounds.right = Math.min(touchBounds.right + adjust, mScrollX + width);touchBounds.left = touchBounds.right - minTouchTarget;} else {touchBounds.left = Math.max(touchBounds.left + adjust, mScrollX);touchBounds.right = touchBounds.left + minTouchTarget;}}if (touchBounds.height() < minTouchTarget) {final int adjust = (minTouchTarget - touchBounds.height()) / 2;touchBounds.top -= adjust;touchBounds.bottom = touchBounds.top + minTouchTarget;}}/*** <p>ScrollabilityCache holds various fields used by a View when scrolling* is supported. This avoids keeping too many unused fields in most* instances of View.</p>*/private static class ScrollabilityCache implements Runnable {/*** Scrollbars are not visible*/public static final int OFF = 0;/*** Scrollbars are visible*/public static final int ON = 1;/*** Scrollbars are fading away*/public static final int FADING = 2;public boolean fadeScrollBars;public int fadingEdgeLength;public int scrollBarDefaultDelayBeforeFade;public int scrollBarFadeDuration;public int scrollBarSize;public int scrollBarMinTouchTarget;public ScrollBarDrawable scrollBar;public float[] interpolatorValues;public View host;public final Paint paint;public final Matrix matrix;public Shader shader;public final Interpolator scrollBarInterpolator = new Interpolator(1, 2);private static final float[] OPAQUE = { 255 };private static final float[] TRANSPARENT = { 0.0f };/*** When fading should start. This time moves into the future every time* a new scroll happens. Measured based on SystemClock.uptimeMillis()*/public long fadeStartTime;/*** The current state of the scrollbars: ON, OFF, or FADING*/public int state = OFF;private int mLastColor;public final Rect mScrollBarBounds = new Rect();public final Rect mScrollBarTouchBounds = new Rect();public static final int NOT_DRAGGING = 0;public static final int DRAGGING_VERTICAL_SCROLL_BAR = 1;public static final int DRAGGING_HORIZONTAL_SCROLL_BAR = 2;public int mScrollBarDraggingState = NOT_DRAGGING;public float mScrollBarDraggingPos = 0;public ScrollabilityCache(ViewConfiguration configuration, View host) {fadingEdgeLength = configuration.getScaledFadingEdgeLength();scrollBarSize = configuration.getScaledScrollBarSize();scrollBarMinTouchTarget = configuration.getScaledMinScrollbarTouchTarget();scrollBarDefaultDelayBeforeFade = ViewConfiguration.getScrollDefaultDelay();scrollBarFadeDuration = ViewConfiguration.getScrollBarFadeDuration();paint = new Paint();matrix = new Matrix();// use use a height of 1, and then wack the matrix each time we// actually use it.shader = new LinearGradient(0, 0, 0, 1, 0xFF000000, 0, Shader.TileMode.CLAMP);paint.setShader(shader);paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));this.host = host;}public void setFadeColor(int color) {if (color != mLastColor) {mLastColor = color;if (color != 0) {shader = new LinearGradient(0, 0, 0, 1, color | 0xFF000000,color & 0x00FFFFFF, Shader.TileMode.CLAMP);paint.setShader(shader);// Restore the default transfer mode (src_over)paint.setXfermode(null);} else {shader = new LinearGradient(0, 0, 0, 1, 0xFF000000, 0, Shader.TileMode.CLAMP);paint.setShader(shader);paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));}}}public void run() {long now = AnimationUtils.currentAnimationTimeMillis();if (now >= fadeStartTime) {// the animation fades the scrollbars out by changing// the opacity (alpha) from fully opaque to fully// transparentint nextFrame = (int) now;int framesCount = 0;Interpolator interpolator = scrollBarInterpolator;// Start opaqueinterpolator.setKeyFrame(framesCount++, nextFrame, OPAQUE);// End transparentnextFrame += scrollBarFadeDuration;interpolator.setKeyFrame(framesCount, nextFrame, TRANSPARENT);state = FADING;// Kick off the fade animationhost.invalidate(true);}}}

View中的代码有点多, 简单的来说, 就是isOnScrollbar 这个函数通过获取ScrollBar的位置大小信息判断输入的事件是否处于其捕获的范围.

通过反射调用getVerticalScrollBarBounds并输出读取的信息: touchRect=[1464,0][1512,674], 基本可以判定是滚动条的位置.


Rect touchRect = new Rect();
getVerticalScrollBarBoundsRe(null, touchRect);
Logger.d(TAG, "touchRect=" + touchRect.toShortString());void getVerticalScrollBarBoundsRe(Rect r, Rect r2){try {@SuppressLint("SoonBlockedPrivateApi")Method getVerticalScrollBarBounds = View.class.getDeclaredMethod("getVerticalScrollBarBounds", Rect.class, Rect.class);getVerticalScrollBarBounds.setAccessible(true);getVerticalScrollBarBounds.invoke(this, r, r2);} catch (NoSuchMethodException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}

计算宽度1512 - 1464 = 48, 这个宽度默认在系统中又定义, 如果是自定义的ScrollBar则大小不一定是48, 根据configuration.getScaledMinScrollbarTouchTarget();查到源码的定义如下:

  • frameworks/base/core/java/android/view/ViewConfiguration.java

    private static final int MIN_SCROLLBAR_TOUCH_TARGET = 48;/*** @return the minimum size of the scrollbar thumb's touch target in pixels* @hide*/
    public int getScaledMinScrollbarTouchTarget() {return mMinScrollbarTouchTarget;
    }
    

问题的根源如下图所示, 红色滚动条的宽度为48:

在这里插入图片描述

PS: 上图中的滚动条默认情况下并没有显示出来.

解决方法

  1. 修改XML中ScrollView的属性android:scrollbars="none”
  2. 避免需要输入的控件显示在ScrollBar的下方, 考虑给子控件加个padding或margin
  3. 自定义ScrollView, 优化onInterceptHoverEvent函数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/662934.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

国产隔离芯片的质量控制与发展趋势

随着电子技术的飞速发展&#xff0c;国产隔离芯片在电力电子、通信设备等领域中扮演着重要角色。然而&#xff0c;随之而来的是对于其质量控制的迫切需求。本文将从结构、制造工艺、测试手段等方面对国产隔离芯片的质量控制进行分析&#xff0c;并展望其未来的发展趋势。 一、国…

element-ui link 组件源码分享

link 组件的 api 涉及的内容不是很多&#xff0c;源码部分的内容也相对较简单&#xff0c;下面从以下这三个方面来讲解&#xff1a; 一、组件结构 1.1 组件结构如下图&#xff1a; 二、组件属性 2.1 组件主要有 type、underline、disabled、href、icon 这些属性&#xff0c;…

KVM-安装-使用-迁移

一. KVM安装 1. 基础安装 # 下载源 curl -o /etc/yum.repos.d/Centos-7.repo http://mirrors.aliyun.com/repo/Centos-7.repo# 安装基础软件 yum -y install tree vim wget bash-completion bash-completion-extras lrzsz net-tools sysstat iotop iftop htop unzip nc nmap …

批量修改文件后缀名

需要将/opt/module/test/路径下的txt文件后缀修改为cpp&#xff0c;并且以年份结尾 代码如下&#xff1a; #!/bin/bashyear2020 directory"/opt/module/test/"cd "$directory" || exit 1for name in *.txt; donew_name"${name%.txt}_${year}.cpp&qu…

SpringBoot security 安全认证(一)——登录验证

本节内容&#xff1a;使用springboot自动security模块实现用户登录验证功能&#xff1b; 登录过程如下图&#xff1a; AuthenticationManager内容实现用户账号密码验证&#xff0c;还可以对用户状态&#xff08;启用/禁用&#xff09;&#xff0c;逻辑删除&#xff0c;账号是否…

LeetCode.189. 轮转数组

题目 题目链接 分析 首先能想到的就是可以用一个新数组&#xff0c;先保存原数组的后 k 个元素&#xff0c;再保存原数组的前 n−k 个元素。但题目要求不使用额外的数组空间&#xff0c;那么就需要在原数组上做操作。 我们可以先把整个数组翻转一下&#xff0c;这样后半段元…

虚幻UE5Matehuman定制自己的虚拟人,从相机拍照到UE5制作全流程

开启自己的元宇宙,照片扫描真实的人类,生成虚拟形象,保姆级教程,欢迎大家指正。 需要的软件: 制作流程: 一.拍照。 围绕自己拍照,大概20多张图就差不多了,把脑门漏出来,无需拍后脑勺。 拍照方式 例如,拍照时尽量不要在脸上体现出明显的光源方向。

07. 【Linux教程】远程登录

Linux 远程登录 前面介绍了如何安装 Linux 终端工具&#xff0c;本小节介绍本地电脑如何使用 ssh 命令远程登录、Linux 终端工具远程登录的方式&#xff0c;这两种登录方式都是基于 ssh 网络安全协议的&#xff0c;学会使用远程登录 Linux 服务器&#xff0c;会让你对 Linux 系…

Postman(接口测试工具),什么是Postman接口

目录 一.基本介绍 Postman 是什么Postman 快速入门快速入门需求说明 二.Postman 完成 Controller 层测试 需要的代码&#xff1a; Java类request.jspsuccess.jsp1. 完成请求2. 完成请求3. 完成请求4. 完成请求5. 完成请求 三.发送join 目录 一.基本介绍 Postman 是什么 …

使用 Dockerfile 定制镜像详解

使用 Dockerfile 定制镜像详解 1.DockerfileFROM 指定基础镜像RUN 执行命令构建镜像 2.COPY 复制文件3.ADD 更高级的复制文件4.CMD 容器启动命令5.ENTRYPOINT 入口点6.ENV 设置环境变量7.ARG 构建参数8.VOLUME 定义匿名卷9.EXPOSE 暴露端口10.WORKDIR 指定工作目录11.USER 指定…

通过Netbackup恢复Oracle备份实操手册

1、系统环境描述 1 2、恢复前数据备份 2 2.1 在NBU上执行一次完整的备份 2 2.2 查看ORACLE的备份集 3 2.2.1在备份客户端上查看备份集 3 2.2.2在备份服务器netbackup上查看客户端备份集 4 3、本机恢复方法 5 3.1丢失SPFILE文件恢复方法 5 3.2丢失CONTROLFILE文件恢复方…

51单片机编程应用(C语言):模块化编程

下面我们模块化几个函数&#xff1a; Delay.c //延时子函数 void Delay(unsigned int xms) {unsigned char i, j;while(xms--){i 2;j 239;do{while (--j);} while (--i);} } Delay.h #ifndef __DELAY_H__ #define __DELAY_H__void Delay(unsigned int xms);#endifNixie.h …

js 设置、获取、删除标签属性以及H5自定义属性

1. 设置标签属性 使用setAttribute()(‘属性名’, ‘属性值’)方法可以添加、修改、删除属性。   下面的demo是为input添加、修改、删除value属性&#xff1a; 1.1. HTML <input type"text" class"input"> <input type"text" class…

有没有合适写毕业论文的AI工具?

最近挺多同学在忙着写毕业论文&#xff0c;不断在“提交-打回-修改-提交”过程里循环着&#xff0c;好不容易写完了&#xff0c;还得考虑论文查重的问题&#xff01;基哥作为一名曾经的毕业生&#xff0c;当然也体验过这种痛苦了。 但是&#xff0c;大人&#xff0c;时代变了&…

无法在 word 中登录 Grammarly

目录 1. 情况描述 2. 解决方法 3. 原因分析 1. 情况描述 在浏览器中可以登录 Grammarly&#xff0c;但是在 word 中登录失败&#xff0c;大致如下图所示&#xff1a; 我自己没有截图&#xff0c;这是网上别人的图&#xff0c;但差不多都长这个样子。 2. 解决方法 我点击了…

推荐一个好用的人脑解剖结构及功能注释3D图谱——3D Brain

大脑是一个非凡的结构&#xff0c;它定义了我们是谁&#xff0c;以及我们如何体验世界。神经成像技术的最新进展使我们能够看到大脑内部&#xff0c;让我们能够看到构成大脑的组成部分并了解它们对应的功能。大脑的大体结构对大多数人来说都很熟悉。 前脑的外层构成了我们熟悉…

shell - 正则表达式和grep命令和sed命令

一.正则表达式概述 1.正则表达式定义 1.1 定义 使用字符串描述、匹配一系列符合某个规则的字符串 1.2 了解 普通字符&#xff1a; 大小写字母、数字、标点符号及一些其它符号元字符&#xff1a; 在正则表达式中具有特殊意义的专用字符 1.3 层次分类 基础正则表达式扩展正…

MySQL 备份恢复

1.1 MySQL日志管理 在数据库保存数据时&#xff0c;有时候不可避免会出现数据丢失或者被破坏&#xff0c;这样情况下&#xff0c;我们必须保证数据的安全性和完整性&#xff0c;就需要使用日志来查看或者恢复数据了。 数据库中数据丢失或被破坏可能原因&#xff1a; 误删除数…

机器学习2-简单的二分类问题

需求&#xff1a; 假设现在需要对数据进行二分类&#xff0c;小于0.5的&#xff0c;打上0的标记&#xff0c;大于0.5的&#xff0c;打上1的标记&#xff0c;怎么做&#xff1f; 分析&#xff1a; 这是一个简单的二分类问题&#xff0c;使用逻辑回归模型。 代码&#xff1a; …

【PostgresSQL系列】 ltree简介及基于SpringBoot实现 ltree数据增删改查

本文将对PostgresSQL中的ltree进行相关概念介绍&#xff0c;并以示例代码讲解ltree数据增删改查功能的实现。 作者&#xff1a;后端小肥肠 目录 1.前言 2. 基础概念 2.1. ltree 2.2. lquery 2.3. ltxtquery 2.4. ltree函数及操作符 2.4.1. ltree函数 2.4.2. ltree操作符…