每日一问:Android 滑动冲突,你们都是怎样处理的
堅持原創日更,短平快的 Android 進階系列,敬請直接在微信公眾號搜索:nanchen,直接關注并設為星標,精彩不容錯過。
在 Android 開發中,滑動沖突總是我們一個無法避免的話題。而對于解決方案卻是眾說紛紜。比如 RecyclerView 嵌套 RecyclerView,直接通過相關方法禁掉內部 RecyclerView 的滑動;ScrollView 嵌套 RecyclerView 直接把 ScrollView 替換為 NestedScrollView 等等。但我們今天要說的是在自定義 View 中遇到滑動沖突時,我們又應該如何處理呢?
當然,今天的話題需要 View 的事件分發機制做理論前提,還不了解 View 的事件分發機制的小伙伴可以移步我之前面試系列的一篇文章:面試系列:講講 Android 的事件分發機制。
簡單介紹 View 的事件分發機制
當然,這里也可以簡單地提一下,基本的流程就是下面的偽代碼。
public boolean dispatchTouchEvent(MotionEvent ev) {boolean consume = false;if (onInterceptTouchEvent(ev)) {consume = onTouchEvent(ev);}else{consume = child.dispatchTouchEvent(ev);}return consume; }當一個 ViewGroup 接收到一個事件的時候,首先會調用 dispatchTouchEvent() 方法進行事件分發,如果 onInterceptTouchEvent() 返回 true,則代表當前 View 會攔截事件,則直接回調 onTouchEvent() 方法進行事件處理。如果不攔截,則直接回調子 View 的 dispatchTouchEvent() 方法,如此反復,一直到最里面的子 View。
當一個點擊事件產生后,它的傳遞過程遵循以下順序:Activity => Window => View,即事件總是先傳遞給 Activity,Activity 再傳遞給 Window,最后 Window 再傳遞給頂層 DecorView,然后遵循上面的方式一直在最里層 View。
而處理事件則從最里層 View 不斷回傳給自己的外層 View,如果一直沒有 View 進行處理,則直接會回傳到 Activity 中。
onTouchEvent() 返回 true 代表自己要處理。
既然都提了這么一點,也就突然想給出一些結論,參考自 Android 開發藝術探索:
一不小心發現還是挺多的,當然這些都是結論,具體可以跟著 面試系列:講講 Android 的事件分發機制 進行源碼流程探討,你會發現上面的結論很容易得到。
處理自定義 View 中的滑動沖突
對于大多數 Android 開發來說,處理滑動沖突好像很難,但實戰一下又發現,好像也挺簡單,因為這個實際上是有套路可循的。基本就兩種方案:外部攔截法 && 內部攔截法。
外部攔截法
所謂外部攔截法,顧名思義,就是直接在父容器中直接攔截掉我們的滑動事件,讓其不能進入到子元素中,這似乎和我們 RecyclerView 嵌套 RecyclerView 時禁用內部 RecyclerView 滑動有那么一絲相似之處,就是內部不處理就完事兒了。但細細品來又完全不一樣,這里的外部攔截法會讓內部元素根本就收不到滑動事件。
這種方法明顯非常適合我們上面講的事件分發機制。我們在接收 ACTION_MOVE 事件的時候,直接通過使 onInterceptTouchEvent() 方法返回 true 來直接攔截掉事件就可以了,偽代碼想必大家也知道了:
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {ev?.run { if (action == MotionEvent.ACTION_MOVE && 父容器需要點擊事件){return true}}return super.onInterceptTouchEvent(ev) }代碼很簡單,我們僅僅需要在事件 ACTION_MOVE 時去處理我們的邏輯就好了,當滿足我們的邏輯的時候,就攔截掉 ACTION_MOVE 事件給自己處理。
至于為什么不去攔截 ACTION_DOWN 和 ACTION_UP,想必大家也清楚了。上面說了,如果攔截了 ACTION_DOWN 事件,那后續的 ACTION_MOVE、ACTION_UP 等其它事件均不會在調用 onInterceptTouchEvent() 方法,會直接交給當前容器處理。而如果我們攔截掉 ACTION_UP 的話,肯定會導致子元素的點擊事件無法被處理,因為大家肯定都知道一個點擊事件從 ACTION_DOWN 開始,從 ACTION_UP 結束,二者缺一不可。
內部攔截法
內部攔截法相對外部攔截法會復雜一些,所以我們通常來說,都更加推薦用外部攔截法進行處理。不過,內部攔截法依然有著它非常重要的地位,具體情況有可能會遇到。
內部攔截法的話,需要 requestDisallowInterceptTouchEvent() 方法的支持,這個方法是干什么的呢?顧名思義,請求是否不允許攔截事件,其接收一個 boolean 參數,表示是否不允許攔截。
我們直接重寫子元素的 dispatchTouchEvent() 方法,得到偽代碼如下:
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {ev?.run { when(action){MotionEvent.ACTION_DOWN -> parent.requestDisallowInterceptTouchEvent(true)MotionEvent.ACTION_MOVE ->{if(滿足需要讓外部容器攔截事件){parent.requestDisallowInterceptTouchEvent(false)}}}}return super.dispatchTouchEvent(ev) }想必代碼也是非常簡單易懂的,我們給父容器的 requestDisallowInterceptTouchEvent() 傳遞的參數代表是否不允許其攔截事件,當參數為 true 的時候代表不允許攔截,為 false 的時候代表攔截。所以看起來和外部攔截法也就如出一轍了。
不過僅僅有這點修改還不夠,我們通過前面的理論基礎知道,當我們的父容器攔截掉 ACTION_DOWN 事件的時候,所有的事件都無法再傳遞到子元素中,自然也就不會調用上面我們寫的 dispatchTouchEvent() 方法了。所以我們在內部攔截法的時候還需要重寫父容器的 onInterceptTouchEvent() 方法。
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {ev?.run { if (action == MotionEvent.ACTION_DOWN){return false}}return super.onInterceptTouchEvent(ev) }至此,基本介紹了兩種處理滑動沖突的解決方案,在自定義 View 的時候結合實際場景也就可以得心應手了。
除了滑動沖突,滑動處理也是一項非常有意思的工作,感興趣的可以可以參考 NestedScrollingParent2 和 NestedScrollingChild2 喲。
文章參考自:《Android 開發藝術探索》
轉載于:https://www.cnblogs.com/liushilin/p/11197376.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的每日一问:Android 滑动冲突,你们都是怎样处理的的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 快对app怎么删除浏览记录
- 下一篇: 荔枝app如何上传录好的声音