一些有的沒的
???????? View是Android中所有控件的基類惰爬,同時(shí)也是界面層所有控件的一種抽象循签,代表了一個(gè)控件尤蛮,如Button轧葛,TextView尿扯。
???????? ViewGroup也是繼承view的衷笋,翻譯為控件組泊脐,代表許多View的集合约郁,如LinearLayout供置,ListView。
????????MotionEvent事件為手機(jī)屏幕觸摸事件封裝類的對(duì)象诱鞠,其中封裝了該事件的所有信息始衅,如觸摸位置、類型、時(shí)間等參數(shù)托嚣,其中最重要的參數(shù)有三個(gè):
① ACTION_DOWN:表示所有事件的開始
② ACTION_MOVE:滑動(dòng)事件
③ ACTION_UP:表示事件的結(jié)束
View和ViewGroup的主要事件函數(shù):
① 對(duì)應(yīng)view類控件
????dispatchTouchEvent(MotionEvent ev)??? 分發(fā)TouchEvent
????onTouchEvent(MotionEvent ev)???????? 處理TouchEvent
② 對(duì)應(yīng)viewgroup類控件
????dispatchTouchEvent(MotionEvent ev)??? 分發(fā)TouchEvent
????onTouchEvent(MotionEvent ev)???????? 處理TouchEvent
????onInterceptTouchEvent(MotionEvent en) 攔截TouchEvent
方法一:
public boolean dispatchTouchEvent(MotionEvent ev){
?????????? returnsuper.dispatchTouchEvent(ev);
}
主要負(fù)責(zé)對(duì)onTouchEvent事件進(jìn)行分類大咱,本身不處理onTouchEvent事件,而分類的方式由其返回值決定:
返回true:將事件交給當(dāng)前view本身的onTouchEvent處理注益;
返回false:將事件交給當(dāng)前view本身的onInterceptTouchEvent處理碴巾,再由其決定事件傳遞的方向。
方法二:
???????? publicboolean onInterceptTouchEvent(MotionEvent ev){
???????? return super.onInterceptTouchEvent(ev);
? ? ? ? ?}
主要用于負(fù)責(zé)處理事件并改變事件的傳遞方向丑搔,其傳遞方向也由返回值決定:
返回true:事件會(huì)傳遞給當(dāng)前控件的onTouchEvent()厦瓢,而不再傳遞給子控件提揍,就是所謂的Intercept(截?cái)?,并且一旦截?cái)嘀蟪穑朔椒ň筒粫?huì)再被調(diào)用劳跃。
返回false:事件會(huì)傳遞給子控件的dispatchTouchEvent方法,再由子控件分發(fā)浙垫。
方法三:
???public Boolean onTouchEvent(MotionEvent event){
?????????????????? returnsuper.onTouchEvent(event);
????}
??? 當(dāng)已經(jīng)完整的處理了該事件且不希望其他回調(diào)方法再次處理時(shí)返回true刨仑,否則返回false。
View的事件分發(fā)
???????? 首先給一個(gè)Button注冊(cè)點(diǎn)擊和觸摸事件:
輸出日志:
可以看到事件的傳遞順序是先經(jīng)過onTouch再傳遞到onClick夹姥,如果將onTouch方法的返回值改為true再執(zhí)行:
可以看到onClick方法不再執(zhí)行了杉武!下面從源碼中看原理。
???????? 首先需要知道的是辙售,只要觸摸到了任何一個(gè)控件轻抱,就一定會(huì)調(diào)用該控件的dispatchTouchEvent方法,看一下view中dispatchTouchEvent方法的源碼:
????????第一個(gè)條件mOnTouchListener是在setOnTouchListener方法里賦值的旦部,只要我們注冊(cè)了onTouch事件祈搜,這個(gè)變量就一定被賦值了;第二個(gè)條件判斷當(dāng)前點(diǎn)擊的控件是否是enable的士八,按鈕默認(rèn)都是enable容燕;第三個(gè)條件是去回調(diào)onTouch方法,如果onTouch方法返回true婚度,整個(gè)方法就返回true缰趋,不會(huì)再向下執(zhí)行(這里所說的不會(huì)向下執(zhí)行就是不會(huì)再執(zhí)行onTouchEvent方法,從日志中看出其不會(huì)再執(zhí)行onClick方法陕见,也可以說明秘血,onClick方法的調(diào)用肯定在onTouchEven方法中!)评甜,如果onTouch方法返回false灰粮,整個(gè)方法會(huì)去執(zhí)行onTouchEven(event)方法。
onTouch和onTouchEvent方法的區(qū)別:
???????? 這兩個(gè)方法法都是在dispatchTouchEvent中調(diào)用的忍坷,onTouch優(yōu)先于onTouchEven執(zhí)行粘舟,如果在onTouch方法中通過返回true將事件消費(fèi)掉,onTouchEven將不會(huì)再執(zhí)行佩研。
???????? 從dispatchTouchEvent的執(zhí)行條件可以看到柑肴,onTouch能夠得到執(zhí)行需要兩個(gè)前提條件:mOnTouchListener的值不能為空;當(dāng)前點(diǎn)擊的控件必須是enable的旬薯。
???????? 從onTouchEven的源碼中可以得到的結(jié)論:如果該控件是可以點(diǎn)擊的晰骑,就會(huì)返回true,也即绊序,只要觸摸的控件是可以點(diǎn)擊的硕舆,dispatchTouchEvent方法的返回值就是true秽荞。
Touch事件的層級(jí)傳遞:
???????? 如果給一個(gè)控件注冊(cè)了Touch事件,每次點(diǎn)擊它的時(shí)候就會(huì)觸發(fā)一系列的ACTION_DOWN抚官、ACTION_MOVE迹鹅、ACTION_UP事件纺讲,在執(zhí)行ACTION_DOWN的時(shí)候返回了false,后面一系列的其他action就不會(huì)再得到執(zhí)行了疙筹,也即昧绣,當(dāng)dispatchTouchEvent在進(jìn)行事件分發(fā)的時(shí)候茅糜,只有前一個(gè)action返回了true熟嫩,才會(huì)觸發(fā)后一個(gè)action枢步。
ViewGroup的事件分發(fā)
???????? 首先自定一個(gè)布局MyLayout并繼承LinearLayout,并在布局中定義兩個(gè)button娱挨,并注冊(cè)MyLayout的觸摸事件以及兩個(gè)button的點(diǎn)擊事件余指。
運(yùn)行程序捕犬,并分別點(diǎn)擊button1跷坝,button2和MyLayout的空白區(qū)域后,輸出日志如下:
????????可以發(fā)現(xiàn)碉碉,當(dāng)點(diǎn)擊按鈕的時(shí)候柴钻,MyLayout注冊(cè)的onTouch方法并不會(huì)執(zhí)行,只有點(diǎn)擊空白區(qū)域的時(shí)候才會(huì)執(zhí)行該方法垢粮,可以理解為button的onClick方法把事件先消費(fèi)掉了贴届,因此事件不會(huì)再繼續(xù)傳遞。從源碼中看原理:
???????? MyLayout繼承的LinearLayout屬于viewgroup蜡吧,其中有一個(gè)onInterceptTouchEvent方法毫蚓,其源碼為:
????????在MyLayout中重寫此方法,返回值改為true后昔善,再點(diǎn)擊button1元潘、button2以及MyLayout的空白區(qū),輸出日志如下:
????????無論點(diǎn)擊哪里君仆,都只執(zhí)行MyLayout的onTouch方法翩概,看一下viewgroup的dispatchTouchEvent方法的源碼:
????????代碼13行有一個(gè)條件判斷,如果disallowIntercept和!onInterceptTouchEvent(ev)兩者有一個(gè)為true返咱,就會(huì)進(jìn)入到這個(gè)條件判斷中钥庇,disallowIntercept是指是否禁用掉事件攔截的功能,默認(rèn)為false咖摹;當(dāng)?shù)谝粋€(gè)值為false時(shí)就會(huì)完全依賴第二個(gè)值來決定是否可以進(jìn)入到條件判斷的內(nèi)部评姨,第二個(gè)值就是對(duì)onInterceptTouchEvent方法的返回值取反,默認(rèn)值為false萤晴,取反后為true就進(jìn)入到條件判斷的內(nèi)部了参咙;當(dāng)重寫了onInterceptTouchEvent方法后返回值為true龄广,導(dǎo)致所有按鈕的點(diǎn)擊事件都屏蔽了,也就說明按鈕點(diǎn)擊事件的處理就是在此條件判斷內(nèi)部進(jìn)行的蕴侧。
????????情況①:當(dāng)不更改onInterceptTouchEvent的返回值時(shí)择同,會(huì)進(jìn)入到13行的條件判斷,在條件語句內(nèi)部19行有一個(gè)for循環(huán)净宵,遍歷了當(dāng)前viewgroup下的所有字view敲才,之后在第24行判斷當(dāng)前的view是不是正在點(diǎn)擊的view,如果是的話就進(jìn)入到下一個(gè)條件判斷內(nèi)择葡,在第29行調(diào)用了當(dāng)前view的dispatchTouchEvent方法紧武,下面執(zhí)行的活動(dòng)和view的事件分發(fā)是一致的了。調(diào)用子view的dispatchTouchEvent方法后是有返回值的敏储,當(dāng)一個(gè)控件是可點(diǎn)擊的阻星,那么點(diǎn)擊該控件時(shí),dispatchTouchEvent的返回值必定是true已添,也就會(huì)在31行代碼處給viewgroup的dispatchTouchEvent方法直接返回true妥箕,這樣后面的代碼就不會(huì)執(zhí)行了。
????????做個(gè)測(cè)試更舞,重寫button1的onTouch方法畦幢,點(diǎn)擊button1、button2和MyLayout的空白區(qū)域后缆蝉,調(diào)用過程應(yīng)該為:button1的onTouch方法宇葱,button1的onClick方法,button2的onClick方法刊头,MyLayout的onTouch方法黍瞧,結(jié)果如下:
????????情況②:點(diǎn)擊空白區(qū)域。在第44行代碼原杂,如果target等于null印颤,就會(huì)進(jìn)入到之后的條件判斷內(nèi)部,就會(huì)在第50行調(diào)用super.dispatchTouchEvent方法污尉,也就是會(huì)進(jìn)入View中的dispatchTouchEvent方法膀哲,也就只會(huì)運(yùn)行MyLayout的onTouch方法。(在介紹View的事件分發(fā)時(shí)說過被碗,執(zhí)行View的dispatchTouchEvent方法需要判斷三個(gè)條件:1. mOnTouchListener是否注冊(cè)了點(diǎn)擊事件某宪;2.控件是否可點(diǎn)擊;3.回調(diào)onTouch方法返回值锐朴。如果三個(gè)條件都為true兴喂,則執(zhí)行條件語句返回true,如果其中有一個(gè)不為true,則執(zhí)行onTouchEvent方法)
????????情況③:更改onInterceptTouchEvent方法的返回值為true衣迷,再分別點(diǎn)擊button和MyLayout的空白區(qū)域畏鼓,則不管點(diǎn)擊哪里,都只會(huì)執(zhí)行MyLayout的onTouch方法壶谒,輸出日志為:
????????筆記寫到這其實(shí)應(yīng)該很清楚才對(duì)云矫,可我已經(jīng)成功把自己繞暈了,所以汗菜,我決定不看源碼了让禀,直接寫代碼!
????????自定義了一個(gè)TestButton繼承Button陨界,一個(gè)GroupView繼承LinearLayout巡揍,并重寫他們的dispatchTouchEvent方法、onTouchEvent方法和onInterceptTouchEvent方法菌瘪。
???????? 首先模擬TestButton處理該事件腮敌,也就是令TestButton的onTouchEvent方法的返回值為true,并點(diǎn)擊TestButton:
點(diǎn)擊空白區(qū)域:
????????模擬GroupView處理該事件俏扩,令ViewGroup的onTouchEvent方法的返回值為true糜工,點(diǎn)擊TestButton:
點(diǎn)擊空白區(qū)域:
????????恩。动猬。啤斗。表箭。赁咙。運(yùn)行完之后我發(fā)現(xiàn)繞的圈圈在哪里了,就是免钻,在模擬GroupView處理事件時(shí)彼水,點(diǎn)擊按鈕,為啥還會(huì)執(zhí)行TestButton的onTouchEvent方法极舔?凤覆??(只執(zhí)行到ACTION_DOWN這個(gè)動(dòng)作)
???????? 我懂了2鹞骸6㈣搿!
???????? 剛才模擬GroupView處理事件時(shí)渤刃,是將GroupView的onTouchEvent的返回值改為了true拥峦,并不是將GroupView的onInterceptTouchEvent的返回值改為了true,它還會(huì)向view傳遞卖子,并調(diào)用view的dispatchTouchEvent方法和onTouchEvent方法略号,但執(zhí)行到ACTION_DOWN動(dòng)作后,onTouchEvent方法的返回值為false,就不會(huì)再執(zhí)行下面一系列的action了玄柠。
好啦突梦,繞暈后梳理一哈
Activity對(duì)點(diǎn)擊事件的分發(fā)
???????? 點(diǎn)擊事件發(fā)生的時(shí)候,最先傳遞給了Activity羽利,由Activity的dispatchTouchEvent來進(jìn)行事件的分發(fā)宫患。具體工作由Activity內(nèi)部的Window來完成,Window將事件傳遞給décor view(一般是當(dāng)前界面的底層容器这弧,即setContentView所設(shè)置的View的父容器撮奏,通過Activity.getWindow.getDecorView()可以獲得。)
???????? 看下Activity的dispatchTouchEvent方法:
????????一般事件都是從ACTION_DOWN開始的当宴,所以進(jìn)入if語句后執(zhí)行onUserInteraction()方法:
????????沒錯(cuò)這是一個(gè)空方法畜吊,主要用于屏保。接下來看一下方法superDispatchTouchEvent():
????????再看mDecor.superDispatchTouchEvent()方法:
????????明白啦户矢,就是調(diào)用了ViewGroup的dispatchTouchEvent方法玲献,也就是將事件分發(fā)給了ViewGroup!也就是說梯浪,當(dāng)ViewGroup的dispatchTouchEvent方法返回true后捌年,Activity的dispatchTouchEvent方法也返回true,表示被消費(fèi)掉了(具體被誰消費(fèi)掉的不管挂洛,反正不是Activity)礼预,如果返回false,則執(zhí)行Activity的onTouchEvent方法虏劲,表示事件被Activity消費(fèi)掉了托酸。
ViewGroup對(duì)點(diǎn)擊事件的分發(fā)
???????? ViewGroup的dispatchTouchEvent方法很長(zhǎng),但倔強(qiáng)的我還是把它貼了出啦(哈哈哈柒巫,神經(jīng)怖ぁ!)
????????圖中框出來的黃色框框堡掏,有一個(gè)if條件判斷应结,當(dāng)進(jìn)入條件判斷之后,就會(huì)進(jìn)入一個(gè)紅色框框的for循環(huán)泉唁,對(duì)子view進(jìn)行遍歷鹅龄,判斷是否是當(dāng)前點(diǎn)擊的view,看藍(lán)色框框的if判斷亭畜,似曾相識(shí)有木有扮休!和Activity的dispatchTouchEvent里面的判斷一樣有木有,調(diào)用子view的dispatchTouchEvent方法贱案,也即將點(diǎn)擊事件分發(fā)給子view肛炮。厲害了止吐,下面分析一下ViewGroup甩鍋的兩個(gè)條件(有一個(gè)為true即執(zhí)行):
① disallowIntercept:表示是否禁用事件攔截的功能,默認(rèn)值為false侨糟,可以通過調(diào)用requestDisallowInterceptTouchEvent方法對(duì)這個(gè)值進(jìn)行修改碍扔;
② onInterceptTouchEvent(ev)方法返回值的取反,默認(rèn)為false:
?????????那么問題還有一個(gè)秕重,如果沒有進(jìn)入到這個(gè)條件判斷呢(比如攔截不同,比如沒有遍歷到正確的子View,再進(jìn)一步講溶耘,子View的dispatchTouchEvent方法返回的是false二拐,又會(huì)執(zhí)行什么操作呢,看源碼謝謝5时)
???????? 先看有攔截時(shí)百新,也就是當(dāng)onInterceptTouchEvent方法返回值為true時(shí),黃色框庐扫,return?true饭望,意思是ViewGroup要自己消費(fèi)這個(gè)事件,這里又有一個(gè)坑形庭,請(qǐng)?zhí)海ㄈ绻?dāng)前的ViewGroup是可以點(diǎn)擊的铅辞,那么當(dāng)前ViewGroup的onTouchEvent方法會(huì)返回true,如果不可點(diǎn)擊萨醒,onTouchEvent方法返回false斟珊,當(dāng)返回false時(shí),事件就會(huì)推給上一級(jí)處理富纸,也就是會(huì)交給ViewGroup的上一級(jí)Activity處理)
????????當(dāng)前ViewGroup可點(diǎn)擊輸出日志:
????????當(dāng)前ViewGroup不可點(diǎn)擊輸出日志:
????????區(qū)別在:在執(zhí)行ACTION_DOWN動(dòng)作的時(shí)候囤踩,執(zhí)行完ViewGroup的onTouchEvent方法后又執(zhí)行了Activity的onTouchEvent方法,并且在執(zhí)行ACTION_UP和ACTION_MOVE動(dòng)作時(shí)胜嗓,都是Activity做的處理高职,ViewGroup已不再進(jìn)行處理钩乍。
???????? 再看另一種情況辞州,沒有遍歷到目標(biāo)子view,也就是點(diǎn)擊了空白區(qū)域寥粹。綠色框框?qū)Ξ?dāng)前的點(diǎn)擊進(jìn)行了判斷变过,判斷是不是點(diǎn)擊了空白區(qū)域,如果點(diǎn)擊的區(qū)域是空白區(qū)涝涤,便return super.dispatchTouchEvent()(這里有個(gè)坑媚狰,我很勇敢的跳了進(jìn)去),就問阔拳!ViewGroup的父類是誰崭孤!是View啊!1娉琛遗锣!不是Activity啊`托巍>ァ!這里操作的就是View的dispatchTouchEvent方法啊赋兵,那么就看下一小節(jié)笔咽,View對(duì)點(diǎn)擊事件的分發(fā)。?
View對(duì)點(diǎn)擊事件的分發(fā)
看View的dispatchTouchEvent方法的源碼:
首先三個(gè)判斷:
① mOnTouchListener != null
????????此變量在setOnTouchListener()方法里賦值霹期,也即只要給控件注冊(cè)了Touch事件叶组,此變量一定不為空;
② (mViewFlags&ENABLE_MASK) ==?ENABLE
????????判斷當(dāng)前點(diǎn)擊事件是否可點(diǎn)擊历造,很多View默認(rèn)條件是enable的扶叉,所以默認(rèn)值為true;
③ mOnTouchListener.onTouch(this,event)
????????回調(diào)控件的Touch事件的onTouch方法帕膜,默認(rèn)返回值為false枣氧。
???????? 當(dāng)這三個(gè)條件都為true時(shí),則View的dispatchTouchEvent方法返回值為true垮刹,直接由當(dāng)前view消費(fèi)此事件达吞,若有一個(gè)條件為false,則View的dispatchTouchEvent方法返回值為onTouchEvent()方法荒典。那我們就來看一下onTouchEvent方法吧好吧酪劫。
????????看上面的紅色框框,是對(duì)控件是否可點(diǎn)擊進(jìn)行判斷寺董,如果是可以點(diǎn)擊的覆糟,就一定會(huì)return true,該控件自己處理這個(gè)事件遮咖,就會(huì)調(diào)用上面的黃色框框的方法滩字,performClick:
????????就在這個(gè)函數(shù)里面調(diào)用了onClick有沒有,也就解釋了御吞,當(dāng)ACTION_UP動(dòng)作結(jié)束之后才會(huì)有onClick方法的日志輸出:
????????如果這個(gè)控件是不可點(diǎn)擊的麦箍,就一定會(huì)return?false,則相當(dāng)于view的dispatchTouchEvent方法返回了false陶珠,這里也就對(duì)應(yīng)上面介紹ViewGroup的dispatchTouchEvent方法子view的dispatchTouchEvent方法返回false的情況挟裂,這種情況下從ViewGroup的dispatchTouchEvent方法中可以看出,其走的是target = mMotionTarget揍诽,而此時(shí)mMotionTarget = null诀蓉,也就是target = null栗竖,也就會(huì)調(diào)用:
?????????ViewGroup的super.dispatchTouchEvent方法就是view的dispatchTouchEvent方法,控件不可點(diǎn)擊渠啤,也就直接調(diào)用當(dāng)前viewGroup的onTouchEvent方法了划滋,如果ViewGroup的onTouchEvent也返回false,則會(huì)繼續(xù)調(diào)用Activity的onTouchEvent方法埃篓。
總結(jié)处坪,這個(gè)圖還是蠻好的~~~
???????? 事件由Activity的dispatchTouchEvent開始,將事件傳遞給當(dāng)前的Activity的下一層ViewGroup架专;事件分發(fā)給ViewGroup時(shí)同窘,調(diào)用其dispatchTouchEvent方法接著分發(fā),首先會(huì)調(diào)用ViewGroup的onInterceptTouchEvent方法進(jìn)行攔截部脚,如果返回false(不攔截)想邦,就開始遍歷ViewGroup的子view,將事件分發(fā)給子view委刘,若返回值為true(攔截)丧没,事件由ViewGroup自己處理,將會(huì)調(diào)用ViewGroup自己的onTouchEvent方法锡移,若當(dāng)前ViewGroup是可點(diǎn)擊的呕童,則onTouchEvent返回true,自己消費(fèi)事件淆珊,若當(dāng)前ViewGroup不可點(diǎn)擊夺饲,則onTouchEvent返回false,交由上一級(jí)處理施符;當(dāng)事件分發(fā)到View時(shí)往声,首先還是調(diào)用view的dispatchTouchEvent方法,若返回true戳吝,則當(dāng)前view消耗掉此事件浩销,若返回true,則會(huì)調(diào)用當(dāng)前view的onTouchEvent方法听哭,同樣慢洋,當(dāng)前view是可以點(diǎn)擊的,則返回true欢唾,對(duì)事件進(jìn)行處理且警,返回false,則交由上一級(jí)處理礁遣。