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

news2024/10/7 12:26:38

在这里插入图片描述

平台

测试平台:

  • RK3288 + Android8.1
  • RK3588 + Android 12

问题

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

分析

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

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

  @Override
	public 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
                    // transparent
                    int nextFrame = (int) now;
                    int framesCount = 0;
    
                    Interpolator interpolator = scrollBarInterpolator;
    
                    // Start opaque
                    interpolator.setKeyFrame(framesCount++, nextFrame, OPAQUE);
    
                    // End transparent
                    nextFrame += scrollBarFadeDuration;
                    interpolator.setKeyFrame(framesCount, nextFrame, TRANSPARENT);
    
                    state = FADING;
    
                    // Kick off the fade animation
                    host.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.coloradmin.cn/o/1426263.html

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

相关文章

AD24-固定孔放置

1、固定孔放置的一般距离&#xff0c;分为金属和非金属 2、固定孔通过焊盘完成&#xff0c;放置焊盘&#xff0c;并将层修改为Multi Layer 焊盘与固定孔的等大小的 3、金属与非金属的区别 ①非金属 ②金属 4、设置固定孔放置的距离 5、通过复制粘贴即可完成其他孔的放置 6、导…

时间序列预测模型实战案例(二)(Holt-Winter)(Python)结合K-折交叉验证进行时间序列预测实现企业级预测精度(包括运行代码以及代码讲解)

目录 引言 数据格式 运行代码 Holt-Winters模型主体 程序入口 参数讲解 开始训练 预测结果 引言 话不多说上来先上预测精度分析图,其中MAE的误差大概在0.11,以下数据均是预测未知数据&#xff0c;而不是训练数据的预测图。 开始之前我们先来简单了解一下Holt-Winters…

数据结构+算法(第02篇):玩扫雷就是优化算法

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 学习必须往深处挖&…

网安渗透攻击作业(2)

sql注入第一关 一、部署 1、环境安装 &#xff08;1&#xff09;下载phpstudy&#xff0c;下载链接&#xff1a;小皮面板(phpstudy) - 让天下没有难配的服务器环境&#xff01; &#xff0c;安装过后打开软件进入如下界面&#xff0c;接着我们开启nginx和mysql 注意&#x…

Linux--Shell基础

学习笔记&#xff0c;记录以下课程中关于Linux的Shell基础知识。 黑马程序员linux入门到精通&#xff08;下部分&#xff09;_哔哩哔哩_bilibili 目录 1.编写规范 2.变量 2.1 变量的含义 2.2 变量的定义和使用 2.3 只读变量&#xff08;了解&#xff09; 2.4 接收用户输入…

Linux 命令 —— top

Linux 命令 —— top 相对于 ps 是选取一个时间点的进程状态&#xff0c;top 则可以持续检测进程运行的状态。使用方式如下&#xff1a; 用法&#xff1a; top [-d secs] | [-p pid] 选项与参数&#xff1a; -d secs&#xff1a;整个进程界面更新 secs 秒。默认是 5 5 5 秒。…

遇到ubuntu设置交叉编译环境的问题

今天交叉编译器一直没安装成功&#xff0c;环境变量也配置了还是不对&#xff0c;最后发现Ubuntu是64位的要装 然后就好了 另外在进行嵌入式Linux开发的时候&#xff0c;要把主机、虚拟机、以及开发板设置在同一网段下&#xff0c;虚拟机一般设成临时的就可以&#xff0c;但是…

力扣之2648.生成 斐波那契数列(yield)

/*** return {Generator<number>}*/ var fibGenerator function*() {let a 0,b 1;yield 0; // 返回 0&#xff0c;并暂停执行yield 1; // 返回 1&#xff0c;并暂停执行while(true) {yield a b; // 返回 a b&#xff0c;并暂停执行[a, b] [b, a b]; // 更新 a 和 …

使用Pycharm在本地调用chatgpt的接口

目录 1.安装环境 2.建立多轮对话的完整代码&#xff08;根据自己使用的不同代理需要修改端口&#xff08;port&#xff09;&#xff09; 3.修改代码在自己的Pycharm上访问chagpt的api并实现多轮对话&#xff0c;如果不修改是无法成功运行的。需要确定秘钥和端口以保证正常访…

使用pygame建立一个简单的使用键盘方向键移动的方块小游戏

import pygame import sys# 初始化pygame pygame.init()# 设置窗口大小 screen_size (640, 480) # 创建窗口 screen pygame.display.set_mode(screen_size) # 设置窗口标题 pygame.display.set_caption("使用键盘方向键移动的方块的简单小游戏")# 设置颜色 bg_colo…

CHS_08.2.3.6_1+生产者-消费者问题

CHS_08.2.3.6_1生产者-消费者问题 问题描述问题分析思考&#xff1a;能否改变相邻P、V操作的顺序&#xff1f;知识回顾 在这个小节中 我们会学习一个经典的进程同步互斥的问题 问题描述 并且尝试用上个小节学习的p v操作 也就是信号量机制来解决这个生产者消费者问题 问题的描…

基于GAN-CNN-CNN的鲁棒笔迹识别方法(三)

上一篇文章提出了一个用于笔迹识别(鉴别)的三段式模型,同时也提出了一个新数据集HTID_1,本文主要针对模型的最后一部分--笔迹识别,在HTID_1上进行实验. 数据集 实验前先介绍一下HTID_1. HTID_1是用于笔迹识别的数据集,是基于本文提出的模型制作而成的.将互联网上收集的740人笔…

Unity Shader 滚动进度条效果

Unity Shader 滚动进度条效果 前言项目场景布置导入图片修改场景设置修改图片尺寸即可调整进度 ASE连线 前言 UI要实现一个滚动进度&#xff0c;于是使用Shader制作一个。 项目 场景布置 导入图片 修改一下导入图片的格式&#xff0c;这样才能循环起来 WrapMode改为Repea…

2024/2/1学习记录

echarts 为柱条添加背景色&#xff1a; 若想设置折线图的点的样式&#xff0c;设置 series.itemStyle 指定填充颜色就好了&#xff0c;设置线的样式设置 lineStyle 就好了。 在折线图中倘若要设置空数据&#xff0c;用 - 表示即可&#xff0c;这对于其他系列的数据也是 适用的…

Windows11 用 HyperV 安装 Ubuntu-16.04 虚拟机

Windows11 用 HyperV 安装 Ubuntu-16.04 虚拟机 1. 确保已经开启HyperV2. 准备Ubuntu16.04镜像&#xff08;推荐64位的&#xff09;3. HyperV ->快速创建 -> 更改安装源 选刚刚下载的镜像&#xff08;.iso&#xff09;文件就好 -> 创建虚拟机[^1] 前提&#xff1a;VMw…

(硬核中的硬核)链路追踪落地过程中的挑战与解决方案

&#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是蓝胖子&#x1f947; ☁️博客首页&#xff1a;CSDN主页蓝胖子的编程梦 &#x1f304;每日一句&#xff1a;编程中我们会遇到多少挫折&#xff1f;表放弃&#xff0c;沙漠尽头必是绿洲。 大家好&#xff0c;我是蓝胖子…

【Qt加密播放器】登录窗口功能补充

输入框小设计 目的&#xff1a;实现鼠标点击输入框时的聚焦效果。 首先在LoginForm构造函数中为账号和密码输入框添加事件过滤器。关于事件过滤器的具体介绍可以参考这篇博文&#xff1a;Qt消息机制和事件 ui->nameEdit->installEventFilter(this); ui->pwdEdit->…

uniapp+微信小程序+nodejs问题记录

一、前言 通过uniapp进行微信小程序调试。服务端使用NodeJs。 二、报错统计 1、本地调试时微信小程序报错&#xff1a;request:Cannot send network request to localhost 解决方法&#xff1a; 【微信小程序开发平台】-【本地设置】-勾选“不校验合法域名、web-view、TLS版本…

Python完善APC netbotz 250报告功能实现主动式运维。

首先介绍一下APC netbotz 250, 这是施耐德推出的一款机架式监控主机&#xff0c;能够对所有IT环境进行经济有效而且灵活的监控&#xff0c;号称APC史上性价比最高的环境监测方案&#xff0c;这可不是我吹的&#xff0c;是APC官网的介绍&#xff0c;可参考下面的官网截图。 我们…

Visual Studio 2022编译错误 Error MSB8041-此项目需要MFC库解决方案

Visual Studio 2022编译错误 Error MSB8041-此项目需要MFC库 错误原因&#xff1a; Visual Studio 2022安装的MFC库不够。 解决方案&#xff1a; 安装Visual Studio 2022 需要的MFC 库就可以了。 安装方法&#xff1a; 第一步&#xff0c;打开vs2022 工具 ->获取工具和功…