Android ViewPager使用预加载机制导致出现页面穿透问题

缘由

在应用中使用ViewPager,并且设置预加载页面。结果出现了一些异常的现象。
我们有4个页面,分别是4个Fragment,暂且称为FragmentA、FragmentB、FragmentC、FragmentD,ViewPager在MainActivity中,切换时,FragmentB会将FragmentA覆盖,这个时候,在FragmentB中点击某一处,如果该处的FragmentA也有控件,会出现异常的情况,事件会响应到FragmentA中的控件中去,也就是事件穿透了。

原因分析:

​这个问题在使用 ViewPagerPageTransformer 时确实比较常见。根本原因在于 ViewPager 的工作机制以及 PageTransformer 如何影响页面视图。

  1. ViewPager 预加载机制: ViewPager 为了实现平滑的滑动效果,默认会预加载当前页面左右两侧的页面(数量由 setOffscreenPageLimit(int limit) 控制,默认为1)。这意味着即使某个页面在视觉上已经部分或完全移出屏幕,它的视图 (View) 仍然存在于 ViewPager 的视图层级 (View Hierarchy) 中,并且可能仍然是活动的。
  2. PageTransformer 的作用: PageTransformer 主要通过修改页面的绘制属性(如 translationX, translationY, scaleX, scaleY, alpha, rotation 等)来实现切换动画。它改变的是页面的 视觉 位置和外观,但通常 不改变 页面视图在视图层级中的存在状态或其接收触摸事件的能力。
  3. 事件分发机制: 当你在屏幕上点击时,Android 的触摸事件会从父视图(这里是 ViewPager)分发到子视图。即使某个页面(比如前一个页面)通过 PageTransformer 被平移到了当前页面的后面或者屏幕外,如果它的视图边界(在进行变换之前的原始边界,或者变换后的边界仍然与点击位置有重叠,并且它没有被设置为不可交互)仍然能够响应触摸事件,那么点击事件就可能被它拦截并处理。尤其是当 PageTransformer 只是平移 (translation) 而没有处理视图的 Z 轴顺序 (elevation) 或可点击状态时,这个问题更容易发生。默认情况下,后添加的页面(索引更大的页面)会绘制在先添加的页面之上,但事件分发可能不会严格遵守这个视觉顺序,特别是对于经过变换的视图。

总结来说: 前一个页面虽然在视觉上被移开了(或者被当前页面遮挡了),但它的 View 实例仍然存在于内存和视图树中,并且默认情况下是可交互的。当你点击的位置恰好也落在了这个“隐藏”页面的可交互控件的原始或变换后的区域内时,事件可能就会被它错误地接收。

解决方案:

最有效的解决办法是在你的 PageTransformertransformPage(View page, float position) 方法中,根据页面的 position 来动态地管理页面的可交互状态。

  • position 的含义:
    • 0: 当前完全可见的页面。
    • (-1, 0): 左侧相邻的页面,正在移入或移出屏幕。
    • (0, 1): 右侧相邻的页面,正在移入或移出屏幕。
    • <= -1: 完全移出屏幕左侧的页面。
    • >= 1: 完全移出屏幕右侧的页面。

你需要确保只有当前页面(或者你希望可以交互的过渡页面)是可点击的,而那些视觉上处于非活动状态(尤其是被当前页面遮挡的页面)应该被禁用交互。

具体实现方法:

在你的 PageTransformertransformPage 方法中添加逻辑:

方法一:直接设置 clickableenabled (简单但不一定完全解决子视图问题)

// Java
import androidx.viewpager.widget.ViewPager;
import android.view.View;public class MyPageTransformer implements ViewPager.PageTransformer {@Overridepublic void transformPage(View page, float position) {// ... 你原有的变换逻辑 (translation, scale, alpha etc.)// 关键:根据 position 决定页面是否可以交互// 通常我们只希望当前页面 (-1 < position < 1 范围内的页面,特别是 position 接近 0 的) 可以交互// 对于完全移出屏幕或在后面的页面 (position <= -1 或 position >= 1,尤其是 position < 0 时) 禁用交互if (position < -1f || position > 1f) {// 完全不可见page.setAlpha(0f); // 视觉上隐藏 (可选)page.setEnabled(false);page.setClickable(false);} else {// 在屏幕内或正在过渡page.setEnabled(true);page.setClickable(true);// 这里可以根据 position 的具体值调整 alpha 或其他视觉效果if (position == 0) {// 当前页面,确保完全可见和可交互page.setAlpha(1f);} else {// 过渡页面,可以根据需要设置透明度等// 例如,让远离中心的页面更透明float scaleFactor = Math.max(0.85f, 1 - Math.abs(position));page.setScaleX(scaleFactor);page.setScaleY(scaleFactor);page.setAlpha(1 - Math.abs(position)); // 渐变效果}}// 可能需要递归禁用子视图,如果 setEnabled(false) 对父视图不够的话// setChildrenClickable(page, position > -1f && position < 1f);}// 辅助方法,递归设置子视图的可点击性 (如果需要)private void setChildrenClickable(View view, boolean clickable) {if (view instanceof ViewGroup) {ViewGroup viewGroup = (ViewGroup) view;for (int i = 0; i < viewGroup.getChildCount(); i++) {setChildrenClickable(viewGroup.getChildAt(i), clickable);}}view.setClickable(clickable);}
}
// Kotlin
import androidx.viewpager.widget.ViewPager
import android.view.View
import android.view.ViewGroupclass MyPageTransformer : ViewPager.PageTransformer {override fun transformPage(page: View, position: Float) {// ... 你原有的变换逻辑 (translation, scale, alpha etc.)// 关键:根据 position 决定页面是否可以交互val isVisible = position >= -1f && position <= 1fpage.isEnabled = isVisiblepage.isClickable = isVisible // 设置根视图的可点击性if (!isVisible) {page.alpha = 0f // 完全不可见时隐藏 (可选)} else {// 在屏幕内或正在过渡// 可以根据 position 调整 alpha 等视觉效果if (position == 0f) {// 当前页面page.alpha = 1f} else {// 过渡页面val scaleFactor = maxOf(0.85f, 1 - kotlin.math.abs(position))page.scaleX = scaleFactorpage.scaleY = scaleFactorpage.alpha = 1 - kotlin.math.abs(position) // 渐变效果}}// 如果 page.isEnabled = false 不足以阻止子视图响应点击,// 你可能需要递归地禁用子视图。// setChildrenClickable(page, isVisible)}// 辅助方法,递归设置子视图的可点击性 (如果需要)private fun setChildrenClickable(view: View, clickable: Boolean) {view.isClickable = clickableif (view is ViewGroup) {for (i in 0 until view.childCount) {setChildrenClickable(view.getChildAt(i), clickable)}}}
}

方法二:使用 Elevation (API 21+)

如果你的应用最低支持 API 21 (Lollipop),你可以利用 elevation 属性。通常,Z 轴更高的视图会接收触摸事件。确保当前页面的 elevation 最高。

// Java (API 21+)
import androidx.viewpager.widget.ViewPager;
import android.view.View;
import android.os.Build;public class MyPageTransformer implements ViewPager.PageTransformer {private static final float MAX_ELEVATION = 8f; // dp, or just a relative value@Overridepublic void transformPage(View page, float position) {// ... 你原有的变换逻辑 ...if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {// 让当前页面及其邻近页面的 Elevation 根据 position 变化// 当前页面 (position=0) Elevation 最高// 越远的页面 Elevation 越低float absPos = Math.abs(position);float elevation = (1 - absPos) * MAX_ELEVATION;page.setElevation(elevation);}// 你仍然可能需要结合方法一中的 setClickable/setEnabled// 特别是对于完全移出屏幕的页面 (absPos >= 1)if (position < -1f || position > 1f) {page.setAlpha(0f);page.setEnabled(false);page.setClickable(false);} else {page.setEnabled(true);page.setClickable(true);// 根据需要设置 alpha 等page.setAlpha(1 - Math.abs(position)); // 示例}}
}
// Kotlin (API 21+)
import androidx.viewpager.widget.ViewPager
import android.view.View
import android.os.Buildclass MyPageTransformer : ViewPager.PageTransformer {companion object {private const val MAX_ELEVATION = 8f // dp, or just a relative value}override fun transformPage(page: View, position: Float) {// ... 你原有的变换逻辑 ...if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {val absPos = kotlin.math.abs(position)// 越靠近中间 (position=0), elevation 越高page.elevation = (1 - absPos) * MAX_ELEVATION}// 结合管理 clickable/enabled 状态val isVisible = position >= -1f && position <= 1fpage.isEnabled = isVisiblepage.isClickable = isVisibleif (!isVisible) {page.alpha = 0f} else {page.alpha = 1 - kotlin.math.abs(position) // 示例}}
}

总结建议:

  • 首选方法一,在 transformPage 中根据 position 明确设置 page.setEnabled(false) 和/或 page.setClickable(false) 对于那些不应响应交互的页面(特别是 position < 0 且视觉上在后面的页面)。如果页面根布局设置 clickable=false 不足以阻止其子控件响应事件,你可能需要递归地禁用子控件的可点击状态,或者直接将整个页面的 setEnabled(false)
  • 如果你的 minSdkVersion >= 21,可以结合方法二使用 elevation 来辅助控制 Z 轴顺序,这有助于系统更正确地进行事件分发。
  • 测试:仔细测试你的 PageTransformer 在各种滑动状态下的表现,确保只有期望的页面可以响应点击。

通过在 PageTransformer 中主动管理页面的交互状态,能够解决这个点击穿透的问题。

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

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

相关文章

apt3.0和apt2.0的区别

一&#xff0c;简单区别 更新方式 apt2.0&#xff1a;一次性更新所有内容&#xff0c;没有分阶段更新功能。apt3.0&#xff1a;引入分阶段更新功能&#xff0c;可分批推送更新包。 界面显示 apt2.0&#xff1a;界面简单&#xff0c;输出信息较为杂乱&#xff0c;没有彩色高亮和…

过电压保护器与传统的保护方式对比

过电压保护器主要用于保护电气设备免受大气过电压&#xff08;如雷击&#xff09;和操作过电压&#xff08;开关动作等引发&#xff09;的侵害。它通常由非线性电阻片等元件组成&#xff0c;利用其独特的伏安特性工作。正常电压下&#xff0c;保护器呈现高阻态&#xff0c;几乎…

机器学习(3)——决策树

文章目录 1. 决策树基本原理1.1. 什么是决策树&#xff1f;1.2. 决策树的基本构成&#xff1a;1.3. 核心思想 2. 决策树的构建过程2.1. 特征选择2.1.1. 信息增益&#xff08;ID3&#xff09;2.1.2. 基尼不纯度&#xff08;CART&#xff09;2.1.3. 均方误差&#xff08;MSE&…

充电桩领域垂直行业大模型分布式推理与训练平台建设方案 - 慧知开源充电桩平台

没有任何广告&#xff01; 充电桩领域垂直行业大模型分布式推理与训练平台建设方案 一、平台定位与核心价值 行业首个垂直化AI平台 专为充电桩运营场景设计的分布式大模型训练与推理基础设施&#xff0c;实现"算力-算法-场景"三位一体闭环管理。 核心价值主张&am…

NLP高频面试题(四十五)——PPO 算法在 RLHF 中的原理与实现详解

近端策略优化(Proximal Policy Optimization, PPO)算法是强化学习领域的一种新颖且高效的策略优化方法,在近年大规模语言模型的人类反馈强化学习(Reinforcement Learning with Human Feedback, RLHF)中发挥了关键作用。本文将以学术严谨的风格,详细阐述 PPO 算法的原理及…

C++指针和引用之区别(The Difference between C++Pointers and References)

面试题&#xff1a;C指针和引用有什么区 C指针和引用有什么区别&#xff1f; 在 C 中&#xff0c;指针和引用都是用来访问其他变量的值的方式&#xff0c;但它们之间存在一些重要的区别。了解这些区别有助于更好地理解和使用这两种工具。 01 指针 指针&#xff08;Pointer…

LWIP学习笔记

TCP/ip协议结构分层 传输层简记 TCP&#xff1a;可靠性强&#xff0c;有重传机制 UDP&#xff1a;单传机制&#xff0c;不可靠 UDP在ip层分片 TCP在传输层分包 应用层传输层网络层&#xff0c;构成LWIP内核程序&#xff1a; 链路层&#xff1b;由mac内核STM芯片的片上外设…

【经验记录贴】活用shell,提高工作效率

背景 最近在做测试的时候&#xff0c;需要手动kill服务的进程&#xff0c;然后通过命令重启服务&#xff0c;再进行测试。每次重启都会涉及到下面三个命令的执行&#xff1a; 1&#xff09;检索进程ID $ ps -eLf | grep programname root 1123 112 1234 0 0 0 0:00…

MacOS 系统下 Git 的详细安装步骤与基础设置指南

MacOS 系统下 Git 的详细安装步骤与基础设置指南—目录 一、安装 Git方法 1&#xff1a;通过 Homebrew 安装&#xff08;推荐&#xff09;方法 2&#xff1a;通过 Xcode Command Line Tools 安装方法 3&#xff1a;手动下载安装包 二、基础配置1. 设置全局用户名和邮箱2. 配置 …

一文读懂 AI

2022年11月30日&#xff0c;OpenAI发布了ChatGPT&#xff0c;2023年3月15日&#xff0c;GPT-4引发全球轰动&#xff0c;让世界上很多人认识了ai这个词。如今已过去快两年半&#xff0c;AI产品层出不穷&#xff0c;如GPT-4、DeepSeek、Cursor、自动驾驶等&#xff0c;但很多人仍…

【教程】检查RDMA网卡状态和测试带宽 | 附测试脚本

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 检查硬件和驱动状态 测试RDMA通信 报错修复 对于交换机的配置&#xff0c;可以看这篇&#xff1a; 【教程】详解配置多台主机通过交换机实现互…

计算机网络 - TCP协议

通过一些问题来讨论 TCP 协议 什么是 TCP &#xff1f;举几个应用了 TCP 协议的例子TCP协议如何保证可靠性&#xff1f;tcp如何保证不会接受重复的报文&#xff1f;Tcp粘包拆包问题了解吗&#xff1f;介绍一下&#xff0c;如何解决&#xff1f;TCP拥塞控制与流量控制区别&…

Fiddler 进行断点测试:调试网络请求

目录 一、什么是断点测试&#xff1f; 二、Fiddler 的断点功能 三、如何在 Fiddler 中设置断点&#xff1f; 步骤 1&#xff1a;启动 Fiddler 步骤 2&#xff1a;启用断点 步骤 3&#xff1a;捕获请求 步骤 4&#xff1a;修改请求或响应 四、案例&#xff1a;模拟登录失…

OpenCv高阶(三)——图像的直方图、图像直方图的均衡化

目录 一、直方图 1、计算并显示直方图 2、使用matplotlib方法绘制直方图&#xff08;不划分小的子区间&#xff09; 3、使用opencv的方法绘制直方图 &#xff08;划分16个小的子亮度区间&#xff09; 4、绘制彩色图像的直方图&#xff0c;将各个通道的直方图值都画出来 二、…

Flutter 与原生通信

Flutter 与原生之间的通信主要基于通道机制&#xff0c;包括 MethodChannel、EventChannel 和 BasicMessageChannel。 MethodChannel&#xff1a;用于 Flutter 与原生之间的方法调用&#xff0c;实现双向通信&#xff0c;适合一次性的方法调用并获取返回值&#xff0c;如 Flut…

前端面试-Vue篇

核心概念 Vue 3的响应式原理与Vue 2有何本质区别&#xff1f;Vue中虚拟DOM的diff算法优化策略有哪些&#xff1f;Vue组件间通信方式有哪些&#xff1f;适用场景分别是什么&#xff1f;Vue的生命周期钩子在Composition API中如何替代&#xff1f;Vue的模板编译过程是怎样的&…

光刻机研发与市场现状分析报告

1. 引言 光刻机&#xff08;Lithography Machine&#xff09;是半导体制造的核心设备&#xff0c;其技术水平和市场供应能力直接影响全球芯片产业的发展。随着人工智能&#xff08;AI&#xff09;、5G、高性能计算&#xff08;HPC&#xff09;和自动驾驶等技术的兴起&#xff0…

Missashe考研日记-day21

Missashe考研日记-day21 1 专业课408 学习时间&#xff1a;4h学习内容&#xff1a; 今天先把昨天学的内容的课后习题做了&#xff0c;整整75道啊&#xff0c;然后学了OS第二章关于CPU调度部分的内容&#xff0c;这第二章太重要了&#xff0c;以至于每一小节的内容都比较多&am…

【玩转全栈】—— Django+vue3+讯飞星火API 实现前端页面实时AI答复

技术栈&#xff1a;vue3 element-plus axios pinia router Django5 websocket 讯飞星火API 本文将实现一个 AI 聊天对话功能&#xff0c;将前端用户输入问题以及之前对话发送给后端&#xff0c;通过 api 访问大模型&#xff0c;返回前端实时对话数据。 调用 讯飞星火API…

广东广州一家IPO资产重组疑点重重,信息披露真实性存疑

作者&#xff1a;Eric 来源&#xff1a;IPO魔女 4月18日&#xff0c;广州瑞立科密汽车电子股份有限公司&#xff08;简称“瑞立科密”&#xff09;将接受深交所主板IPO上会审核。公司保荐机构为中信证券&#xff0c;拟募集资金为15.2162亿元。 瑞立科密过往资产重组疑点重重&a…