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.pswp.cn/bicheng/77129.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/77129.shtml
英文地址,請注明出處:http://en.pswp.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…