最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

Android 圖解 View 事件分發(fā)機(jī)制,看這一篇就夠了

2023-02-21 16:57 作者:程序員徐師兄  | 我要投稿


本文首發(fā)我的微信公眾號(hào):徐公,想成為一名優(yōu)秀的 Android 開(kāi)發(fā)者,需要一份完備的?知識(shí)體系,在這里,讓我們一起成長(zhǎng),變得更好~。

在 Android 開(kāi)發(fā)當(dāng)中,View 的事件分發(fā)機(jī)制是一塊很重要的知識(shí)。不僅在開(kāi)發(fā)當(dāng)中經(jīng)常需要用到,面試的時(shí)候也經(jīng)常被問(wèn)到。

如果你在面試的時(shí)候,能把這塊講清楚,對(duì)于校招生或者實(shí)習(xí)生來(lái)說(shuō),算是一塊不錯(cuò)的加分項(xiàng)。對(duì)于工作幾年的我們來(lái)說(shuō),這是必須掌握的,講不明白,那你回去等通知吧,哈哈。

目錄大概如下:

  1. View 事件分發(fā)機(jī)制簡(jiǎn)介

  2. View 常見(jiàn)滑動(dòng)沖突解決

  3. View 雙擊,多擊事件是怎么實(shí)現(xiàn)的

  4. 手勢(shì)識(shí)別

  5. 小結(jié)

View 事件分發(fā)機(jī)制簡(jiǎn)介

View 觸摸事件

對(duì)于屏幕的點(diǎn)擊,滑動(dòng),抬起等一系的動(dòng)作,其實(shí)都是由一個(gè)一個(gè)MotionEvent對(duì)象組成的。根據(jù)不同動(dòng)作,主要有以下三種事件類型:

1.ACTION_DOWN:手指剛接觸屏幕,按下去的那一瞬間產(chǎn)生該事件 2.ACTION_MOVE:手指在屏幕上移動(dòng)時(shí)候產(chǎn)生該事件 3.ACTION_UP:手指從屏幕上松開(kāi)的瞬間產(chǎn)生該事件 4.ACTION_CANCEL 當(dāng)前 View 的手勢(shì)被打斷,后續(xù)不會(huì)再收到任何事件

從 ACTION_DOWN 開(kāi)始到 ACTION_UP/ACTION_CANCEL 結(jié)束我們稱為一個(gè)事件序列

正常情況下,無(wú)論你手指在屏幕上有多么騷的操作,最終呈現(xiàn)在 MotionEvent 上來(lái)講無(wú)外乎下面 3 種 case。

  1. 點(diǎn)擊后抬起,也就是單擊操作:ACTION_DOWN -> ACTION_UP

  2. 點(diǎn)擊后再風(fēng)騷的滑動(dòng)一段距離,再抬起:ACTION_DOWN -> ACTION_MOVE -> … -> ACTION_MOVE -> ACTION_UP

  3. 某些情況下,我們可能會(huì)沒(méi)有收到 ACTION_UP 事件,是收到 ACTION_CANCEL 事件。

ACTION_CANCEL 一般是指 ChildView 原先擁有事件處理權(quán),后面由于某些原因,該處理權(quán)需要交回給上層去處理,ChildView便會(huì)收到 ACTION_CANCEL 事件。對(duì)于一些復(fù)位或者重置操作,我們應(yīng)該在 ACTION_UP 和 ACTION_CANCEL 里面同時(shí)進(jìn)行處理。

代碼邏輯上是:上層判斷之前交給ChildView的事件處理權(quán)需要收回來(lái)了,便會(huì)做事件的攔截處理,攔截時(shí)給ChildView發(fā)一個(gè)ACTION_CANCEL事件

幾個(gè)主要方法

我們知道,View 的事件分發(fā)機(jī)制主要涉及到以下幾個(gè)方法

  • dispatchTouchEvent ,這個(gè)方法主要是用來(lái)分發(fā)事件的

  • onInterceptTouchEvent,這個(gè)方法主要是用來(lái)攔截事件的(需要注意的是 ViewGroup 才有這個(gè)方法,View 沒(méi)有 onInterceptTouchEvent 這個(gè)方法)

  • onTouchEvent 這個(gè)方法主要是用來(lái)處理事件的

  • requestDisallowInterceptTouchEvent(true),這個(gè)方法能夠影響父View是否攔截事件,true 表示父 View 不攔截事件,false 表示父 View 攔截事件

我們先來(lái)看一張圖。

以下內(nèi)容參考圖解 Android 事件分發(fā)機(jī)制這一篇博客

  • 仔細(xì)看的話,圖分為3層,從上往下依次是Activity、ViewGroup、View

  • 事件從左上角那個(gè)白色箭頭開(kāi)始,由 Activity 的 dispatchTouchEvent 進(jìn)行分發(fā)

  • 箭頭的上面字代表方法返回值,(return true、return false、return super.xxxxx(),super 的意思是調(diào)用父類實(shí)現(xiàn)。)

  • dispatchTouchEvent和 onTouchEvent的框里有個(gè)【true---->消費(fèi)】的字,表示的意思是如果方法返回true,那么代表事件就此消費(fèi),不會(huì)繼續(xù)往別的地方傳了,事件終止。

  • 目前所有的圖的事件是針對(duì)ACTION_DOWN的,對(duì)于ACTION_MOVE和ACTION_UP我們最后做分析。

當(dāng)觸摸事件發(fā)生時(shí),首先 Activity 將 TouchEvent 傳遞給最頂層的 View,TouchEvent最先到達(dá)最頂層 view 的 dispatchTouchEvent ,然后由 dispatchTouchEvent 方法進(jìn)行分發(fā),

如果dispatchTouchEvent返回true 消費(fèi)事件,事件終結(jié)。

如果dispatchTouchEvent返回 false ,則回傳給父View的onTouchEvent事件處理;

如果dispatchTouchEvent返回super的話,默認(rèn)會(huì)調(diào)用自己的onInterceptTouchEvent方法。

  • 默認(rèn)的情況下onInterceptTouchEvent回調(diào)用super方法,super方法默認(rèn)返回false,所以會(huì)交給子View的onDispatchTouchEvent方法處理

  • 如果 interceptTouchEvent 返回 true ,也就是攔截掉了,則交給它的 onTouchEvent 來(lái)處理,

  • 如果 interceptTouchEvent 返回 false ,那么就傳遞給子 view ,由子 view 的 dispatchTouchEvent 再來(lái)開(kāi)始這個(gè)事件的分發(fā)。

關(guān)于更多詳細(xì)分析,請(qǐng)查看原博客圖解 Android 事件分發(fā)機(jī)制,真心推薦,寫得很好。

View 滑動(dòng)事件沖突

在開(kāi)發(fā)當(dāng)中,View 的滑動(dòng)沖突時(shí)經(jīng)常遇到的,比如 ViewPager 嵌套 ViewPager,ScrollView 嵌套 ViewPager。下面讓我們一起來(lái)看看怎么解決。

常見(jiàn)的三種情況

第一種情況,滑動(dòng)方向不同

第二種情況,滑動(dòng)方向相同

第三種情況,上述兩種情況的嵌套

解決思路

看了上面三種情況,我們知道他們的共同特點(diǎn)是父View 和子View都想爭(zhēng)著響應(yīng)我們的觸摸事件,但遺憾的是我們的觸摸事件 同一時(shí)刻只能被某一個(gè)View或者ViewGroup攔截消費(fèi),所以就產(chǎn)生了滑動(dòng)沖突。

那既然同一時(shí)刻只能由某一個(gè) View 或者 ViewGroup 消費(fèi)攔截,那我們就只需要 決定在某個(gè)時(shí)刻由這個(gè) View 或者 ViewGroup 攔截事件,另外的 某個(gè)時(shí)刻由 另外一個(gè) View 或者 ViewGroup 攔截事件,不就 OK了嗎?

綜上,正如 在?《Android開(kāi)發(fā)藝術(shù)》?一書提出的,總共 有兩種解決方案

以下解決思路來(lái)自于?《Android開(kāi)發(fā)藝術(shù)》?書籍

下面的兩種方法針對(duì)第一種情況(滑動(dòng)方向不同),父View是上下滑動(dòng),子View是左右滑動(dòng)的情況。

外部解決法

從父View著手,重寫onInterceptTouchEvent方法,在父View需要攔截的時(shí)候攔截,不要的時(shí)候返回false,為代碼大概 如下

@Override
public?boolean?onInterceptTouchEvent(MotionEvent?ev)?{
????final?float?x?=?ev.getX();
????final?float?y?=?ev.getY();

????final?int?action?=?ev.getAction();
????switch?(action)?{
????????case?MotionEvent.ACTION_DOWN:
????????????mDownPosX?=?x;
????????????mDownPosY?=?y;

????????????break;
????????case?MotionEvent.ACTION_MOVE:
????????????final?float?deltaX?=?Math.abs(x?-?mDownPosX);
????????????final?float?deltaY?=?Math.abs(y?-?mDownPosY);
????????????//?這里是夠攔截的判斷依據(jù)是左右滑動(dòng),讀者可根據(jù)自己的邏輯進(jìn)行是否攔截
????????????if?(deltaX?>?deltaY)?{
????????????????return?false;
????????????}
????}

????return?super.onInterceptTouchEvent(ev);
}

內(nèi)部解決法

從子View著手,父View先不要攔截任何事件,所有的事件傳遞給 子View,如果子View需要此事件就消費(fèi)掉,不需要此事件的話就交給 父View處理。

實(shí)現(xiàn)思路 如下,重寫子 View的dispatchTouchEvent方法,在Action_down 動(dòng)作中通過(guò)方法 requestDisallowInterceptTouchEvent(true) 先請(qǐng)求 父 View不要攔截事件,這樣保證子 View 能夠接受到 Action_move 事件,再在 Action_move 動(dòng)作中根據(jù)自己的邏輯是否要攔截事件,不需要攔截事件的話再交給 父 View 處理。

@Override
public?boolean?dispatchTouchEvent(MotionEvent?ev)?{
????int?x?=?(int)?ev.getRawX();
????int?y?=?(int)?ev.getRawY();
????int?dealtX?=?0;
????int?dealtY?=?0;

????switch?(ev.getAction())?{
????????case?MotionEvent.ACTION_DOWN:
????????????dealtX?=?0;
????????????dealtY?=?0;
????????????//?保證子View能夠接收到Action_move事件
????????????getParent().requestDisallowInterceptTouchEvent(true);
????????????break;
????????case?MotionEvent.ACTION_MOVE:
????????????dealtX?+=?Math.abs(x?-?lastX);
????????????dealtY?+=?Math.abs(y?-?lastY);
????????????Log.i(TAG,?"dealtX:="?+?dealtX);
????????????Log.i(TAG,?"dealtY:="?+?dealtY);
????????????//?這里是夠攔截的判斷依據(jù)是左右滑動(dòng),讀者可根據(jù)自己的邏輯進(jìn)行是否攔截
????????????if?(dealtX?>=?dealtY)?{
????????????????getParent().requestDisallowInterceptTouchEvent(true);
????????????}?else?{
????????????????getParent().requestDisallowInterceptTouchEvent(false);
????????????}
????????????lastX?=?x;
????????????lastY?=?y;
????????????break;
????????case?MotionEvent.ACTION_CANCEL:
????????????break;
????????case?MotionEvent.ACTION_UP:
????????????break;

????}
????return?super.dispatchTouchEvent(ev);
}

更多細(xì)節(jié),可以查看我的這篇文章,里面有詳細(xì)介紹哦?ViewPager,ScrollView 嵌套ViewPager滑動(dòng)沖突解決

View 雙擊,多擊事件是怎么實(shí)現(xiàn)的

實(shí)現(xiàn)之前,我們首先來(lái)闡述一下思路,怎樣實(shí)現(xiàn)雙擊事件,正所謂,授人以魚不如授人以漁。

單擊:用戶點(diǎn)擊一次之后,一段時(shí)間之內(nèi)不再點(diǎn)擊

雙擊;用戶點(diǎn)擊一次之后,一段時(shí)間之內(nèi)再次點(diǎn)擊

實(shí)現(xiàn)思路

  1. 我們監(jiān)聽(tīng) onTouch 事件,在 ACTION_DOWN 的時(shí)候,點(diǎn)擊次數(shù) clickCount +1;

  2. 同時(shí),在 ACTION_DOWN 的時(shí)候,延時(shí)一段時(shí)間,執(zhí)行相應(yīng)的 Runnable 任務(wù),這里我們用 handler 的 postDelayed 實(shí)現(xiàn)

  3. 在延時(shí)任務(wù)執(zhí)行的時(shí)候,我們根據(jù)點(diǎn)擊的次數(shù),進(jìn)行單擊或者多級(jí)的回調(diào),最后,記得重置點(diǎn)擊次數(shù),以及移除延時(shí)任務(wù)

open?class?MyDoubleTouchListener(private?val?myClickCallBack:?MyClickCallBack)?:?OnTouchListener?{

????private?var?clickCount?=?0?//記錄連續(xù)點(diǎn)擊次數(shù)
????private?val?handler:?Handler?=?Handler()

????interface?MyClickCallBack?{
????????fun?oneClick()?//點(diǎn)擊一次的回調(diào)
????????fun?doubleClick()?//連續(xù)點(diǎn)擊兩次的回調(diào)
????}

????override?fun?onTouch(v:?View,?event:?MotionEvent):?Boolean?{
????????if?(event.action?==?MotionEvent.ACTION_DOWN)?{
????????????clickCount++
????????????handler.postDelayed({
????????????????if?(clickCount?==?1)?{
????????????????????myClickCallBack.oneClick()
????????????????}?else?if?(clickCount?==?2)?{
????????????????????myClickCallBack.doubleClick()
????????????????}
????????????????handler.removeCallbacksAndMessages(null)
????????????????//清空handler延時(shí),并防內(nèi)存泄漏
????????????????clickCount?=?0?//計(jì)數(shù)清零
????????????},?timeout.toLong())?//延時(shí)timeout后執(zhí)行run方法中的代碼
????????}
????????return?false?//讓點(diǎn)擊事件繼續(xù)傳播,方便再給View添加其他事件監(jiān)聽(tīng)
????}

????companion?object?{
????????private?const?val?TAG?=?"MyClickListener"
????????private?val?timeout?=?ViewConfiguration.getDoubleTapTimeout()?//雙擊間四百毫秒延時(shí)

????????init?{
????????????Log.i(TAG,?"timeout?is?$timeout?")
????????}
????}

}

三擊事件

三級(jí)事件呢,其實(shí)也很簡(jiǎn)單,我們直接判斷在指定時(shí)間間隔內(nèi)點(diǎn)擊的次數(shù)即可

open?class?MyMultiTouchListener(private?val?myClickCallBack:?MyClickCallBack)?:?OnTouchListener?{

????private?var?clickCount?=?0?//記錄連續(xù)點(diǎn)擊次數(shù)
????private?val?handler:?Handler?=?Handler()

????interface?MyClickCallBack?{
????????fun?oneClick()?//點(diǎn)擊一次的回調(diào)
????????fun?doubleClick()?//連續(xù)點(diǎn)擊兩次的回調(diào)
????????fun?threeClick()?//?連續(xù)點(diǎn)擊三次的回調(diào)
????}

????override?fun?onTouch(v:?View,?event:?MotionEvent):?Boolean?{
????????if?(event.action?==?MotionEvent.ACTION_DOWN)?{
????????????clickCount++
????????????handler.postDelayed({
????????????????if?(clickCount?==?1)?{
????????????????????myClickCallBack.oneClick()
????????????????}?else?if?(clickCount?==?2)?{
????????????????????myClickCallBack.doubleClick()
????????????????}?else?if?(clickCount?==?3)?{
????????????????????myClickCallBack.threeClick()
????????????????}
????????????????handler.removeCallbacksAndMessages(null)
????????????????//清空handler延時(shí),并防內(nèi)存泄漏
????????????????clickCount?=?0?//計(jì)數(shù)清零
????????????},?timeout.toLong())?//延時(shí)timeout后執(zhí)行run方法中的代碼
????????}
????????return?false?//讓點(diǎn)擊事件繼續(xù)傳播,方便再給View添加其他事件監(jiān)聽(tīng)
????}

????companion?object?{
????????private?const?val?TAG?=?"MyClickListener"
????????private?val?timeout?=?600?//雙擊間四百毫秒延時(shí)

????????init?{
????????????Log.i(TAG,?"timeout?is?$timeout?")
????????}
????}
}

手勢(shì)識(shí)別

在 Android 開(kāi)發(fā)當(dāng)中,幾乎所有的事件都會(huì)與用戶進(jìn)行交互,而我們用得的最多的就是手勢(shì)了。

我們知道當(dāng)我們觸摸屏幕的時(shí)候,會(huì)產(chǎn)生很多事件,比如 down,move,up, fling 事件等等。一些簡(jiǎn)單的處理,我們可以直接重寫 View 的 onTouchEvent 方法,根據(jù) View 的 MotionEvent 事件進(jìn)行處理。

而 Google 為了方便開(kāi)發(fā)者方便接入,提供了幾個(gè)默認(rèn)處理類,那就是 GestureDetector 和 ScaleGestureDetector。

GestureDetector這個(gè)類對(duì)外提供了兩個(gè)接口和一個(gè)外部類 。 接口:OnGestureListener,OnDoubleTapListener

內(nèi)部類:SimpleOnGestureListener,同時(shí)實(shí)現(xiàn)了 OnGestureListener,OnDoubleTapListener 接口,如果只想使用接口里面的某個(gè)方法,可以直接使用它,方便快捷。

講解之前,我們向來(lái)看一下怎么使用

GestureDetector(Context context, GestureDetector.OnGestureListener listener)

GestureDetector 基本使用

第一步,初始化?GestureDetector?對(duì)象

?mDetector?=?GestureDetectorCompat(this,?MyGestureListener())

可以看到有兩個(gè)參數(shù),第一個(gè)參數(shù) context,第二個(gè)參數(shù) OnGestureListener,我們可以直接實(shí)現(xiàn) OnGestureListener 接口,也可以直接使用?GestureDetector.SimpleOnGestureListener

????private?class?MyGestureListener?:?GestureDetector.OnGestureListener?{

????????private?val?TAG?=?"GestureDemoActivity"

????????override?fun?onShowPress(e:?MotionEvent?)?{
????????????Log.d(TAG,?"onShowPress:?e?is?$e")
????????}

????????override?fun?onSingleTapUp(e:?MotionEvent?):?Boolean?{
????????????Log.d(TAG,?"onSingleTapUp:?e?is?$e")
????????????return?false
????????}

????????override?fun?onDown(event:?MotionEvent):?Boolean?{
????????????Log.d(TAG,?"onDown:?$event")
????????????return?true
????????}

????????override?fun?onFling(
????????????????event1:?MotionEvent,
????????????????event2:?MotionEvent,
????????????????velocityX:?Float,
????????????????velocityY:?Float
????????)
:?Boolean?{
????????????Log.d(TAG,?"onFling:?$event1?$event2")
????????????return?false
????????}

????????override?fun?onScroll(e1:?MotionEvent?,?e2:?MotionEvent?,?distanceX:?Float,?distanceY:?Float):?Boolean?{
????????????Log.d(TAG,?"onScroll:?distanceX?is?$distanceX,distanceY?is?$distanceY?")
????????????return?false
????????}

????????override?fun?onLongPress(e:?MotionEvent?)?{
????????????Log.d(TAG,?"onLongPress:?e?is?$e")
????????}
????}

第二步:設(shè)置雙擊監(jiān)聽(tīng)

//?設(shè)置雙擊監(jiān)聽(tīng)
mDetector.setOnDoubleTapListener(object?:?GestureDetector.OnDoubleTapListener?{
????????????override?fun?onDoubleTap(e:?MotionEvent?):?Boolean?{
????????????????Log.d(TAG,?"onDoubleTap:?e?is?e")
????????????????return?false
????????????}

????????????override?fun?onDoubleTapEvent(e:?MotionEvent?):?Boolean?{
????????????????Log.d(TAG,?"onDoubleTapEvent:?e?is?e")
????????????????return?false
????????????}

????????????override?fun?onSingleTapConfirmed(e:?MotionEvent?):?Boolean?{
????????????????Log.d(TAG,?"onSingleTapConfirmed:?e?is?e")
????????????????return?false
????????????}

????????})

最后,重寫 Activity 或者 View 的 onTouchEvent ,將事件交給 mDetector 處理。

通常會(huì)有兩種寫法,第一種是如果手勢(shì)處理器處理了,直接返回 true,進(jìn)行消費(fèi)。否則,進(jìn)行默認(rèn)處理

override?fun?onTouchEvent(event:?MotionEvent):?Boolean?{
????????return?if?(mDetector.onTouchEvent(event))?{
????????????true
????????}?else?{
????????????super.onTouchEvent(event)
????????}
????}

第二種寫法是直接在 onTouchEvent 方法中,直接調(diào)用?mDetector.onTouchEvent(event)?方法

override?fun?onTouchEvent(event:?MotionEvent):?Boolean?{
????????mDetector.onTouchEvent(event)
????????return?super.onTouchEvent(event)
????}

第二種寫法,一般不會(huì)影響當(dāng)前 View 或者 Activity 事件的傳遞,在開(kāi)發(fā)當(dāng)中,有時(shí)候?yàn)榱藴p少一些觸摸事件的沖突,經(jīng)常這樣寫。

ScaleGestureDetector 這里暫時(shí)不展開(kāi)描述了了,寫著寫著,發(fā)現(xiàn)好多呀,一個(gè)周末就這樣過(guò)去,賊快,覺(jué)得對(duì)你有幫助的,請(qǐng)來(lái)個(gè)三連,點(diǎn)贊,收藏,轉(zhuǎn)發(fā)??。

小結(jié)

這篇文章,其實(shí)不難。主要是將 View 的事件分發(fā)機(jī)制,滑動(dòng)沖突,以及開(kāi)發(fā)當(dāng)中經(jīng)常用到的一些知識(shí)點(diǎn),總結(jié)一下。



Android 圖解 View 事件分發(fā)機(jī)制,看這一篇就夠了的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
驻马店市| 阿克| 山阳县| 南和县| 浮山县| 蒲江县| 濮阳县| 连南| 凭祥市| 潞城市| 民县| 玛多县| 青川县| 怀柔区| 东海县| 溧阳市| 行唐县| 科技| 万载县| 紫阳县| 鹤山市| 阿拉善盟| 苍南县| 嵊泗县| 乌拉特后旗| 尼勒克县| 襄城县| 淮安市| 舞阳县| 金湖县| 晋江市| 神池县| 洞口县| 民丰县| 澄迈县| 阜城县| 桐乡市| 大同市| 石河子市| 金堂县| 甘泉县|