View的事件體系

View基礎(chǔ)知識

?

什么是View

Android中的控件主要分為容器控件和普通控件罢绽,它們都繼承View父類,容器控件中可以容納多個控件(容器控件與普通控件)和屎。這種關(guān)系最終形成View樹的結(jié)構(gòu)


View的位置參數(shù)

View的位置主要由4個頂點來決定乔询,分別是:top楞陷,left送巡,right摹菠,bottom。其中top是控件左端橫坐標骗爆,right是控件右端橫坐標次氨,top是控件頂部縱坐標,bottom是控件底部縱坐標摘投≈蠊眩可以通過getter方法獲得

那么,控件的寬高就為

Width = right –?left犀呼;Height = bottom –?top

從Android3.0開始幸撕,View增加了額外的參數(shù):x,y外臂,translationX和tranlationY坐儿,這四個參數(shù)都是相對于父容器的坐標;x宋光,y表示View的左上角坐標貌矿,translationX與translationY是表示View相對于父容器的偏移量(屬性動畫平移);兩者的換算關(guān)系:

X?= left + translationX罪佳;Y = top + translationY逛漫;

注意:在View平移過程中,top和left是原始左上角的位置信息赘艳,其值并不會改變尽楔,改變的是x,y第练,translationX和tranlationY

?

MotionEvent和TouchSlop

在手機觸摸屏幕后產(chǎn)生的一系列事件,典型的事件類型:

ACTION_DOWN, ACTION_MOVE, ACTION_UP

例如:點擊事件玛荞,?DOWN-> UP

滑動事件:DOWN->MOVE->UP

通過MotionEvent對象提供的方法我們可以獲取到事件發(fā)生的坐標位置:getX/getY和getRowX/getRowY娇掏;前者獲取的坐標是相對于View自身,后者坐標的是相對于手機屏幕左上角勋眯;


TouchSlop是系統(tǒng)所能識別出的被認為是滑動的最小距離婴梧,如果用戶在屏幕滑動的兩點之間距離小于這個常量,那么系統(tǒng)就認為是在進行滑動操作客蹋。這是一個常量塞蹭,與設(shè)備有關(guān),不同設(shè)備上可能不同

ViewConfiguration.get(this).getScaledTouchSlop()

意義:在處理滑動時可以把該值作為臨界值

VelocityTracker讶坯、GestureDetector和Scroller

VelocityTracker用于追蹤手指在滑動過程中的速度(包括水平與垂直)

//?獲取對象

VelocityTracker tracker = VelocityTrakcher.obtain();

//?添加事件

tracker.addMovement(event);

//計算速度

tracker.computeCurrentVelocity(times);

int xVelocity = (int) tracker.getXVelocity();

int yVelocity = (int) tracker.getYVelocity();

//?不用時重置并回收

tracker.clear();

tracker.recycle();

GestureDetector作用主要是進行手勢的設(shè)定與檢測番电,可輔助檢測用戶的單擊,滑動,雙擊等行為

Scroller主要用于實現(xiàn)View的彈性滑動


View的滑動

實現(xiàn)View滑動的三種方式:

1.使用ScrollTo/ScrollBy

ScrollBy是基于當前位置的相對滑動

ScrollTo是基于參數(shù)的絕對滑動

ScrollBy內(nèi)部其實也是調(diào)用的scrollTo方法

scrollTo(mScrollX + x,mScrollY + y);

mScrollX表示View左邊緣到View內(nèi)容左邊緣的距離

mScrollY表示View上邊緣到View內(nèi)容上邊緣的距離

注意:兩方法改變的是View內(nèi)容位置無法改變View在布局中的位置

(也就是說無法scroll到其他控件的區(qū)域中)

SrcollTo/By是瞬間完成漱办,可以配合Scroller或Handler實現(xiàn)彈性滑動

利用Handler即每次延時發(fā)送一個消息調(diào)用SrcollTo/By方法

利用Scroller實現(xiàn)彈性滑動原理:

當我們構(gòu)造一個Scroller對象并調(diào)用startScroll方法時这刷,其實Scroller內(nèi)部什么事也沒做(只是保存了傳遞的參數(shù))

那么View是如何實現(xiàn)滑動呢

調(diào)用startScroll后接著調(diào)用invalidate方法,該方法會導(dǎo)致View重繪娩井,在重繪時會調(diào)用computeScroll方法暇屋,該方法由我們覆寫調(diào)用computeScrollOffset。computeScrollOffset方法會根據(jù)插值器類型以及時間的流逝計算當前的scrollX與scrollY洞辣,接著我們就可以通過Scroller對象getter方法獲取這兩個值并調(diào)用scrollTo方法咐刨;調(diào)用scrollTo方法后再次調(diào)用invalidate方法觸發(fā)下一次滑動直至滑動結(jié)束

computeScrollOffset的返回值是boolean值,true表示滑動未結(jié)束

總結(jié):Scroller本身不能實現(xiàn)View的滑動扬霜,需要配合computeScroll方法才能完成彈性滑動效果定鸟,通過不斷的讓View重繪,而每次重繪距離起始時間會有一個時間間隔畜挥,通過這個時間間隔獲得View當前需要滑動的位置仔粥,然后通過scrollTo進行滑動


2.使用動畫

動畫可以分為:View動畫(幀動畫,補間動畫)與屬性動畫

兩種動畫的區(qū)別本質(zhì)在于:后者能夠改變控件的位置

原因分析:之前有提到translationX/Y蟹但,這兩個屬性是在android3.0新增的躯泰,而屬性動畫只支持android3.0+,低版本需要使用nineoldandroids庫進行兼容华糖。translationX/Y是表示控件相對于父容器的偏移位置(類似margin缸兔,兩者相互獨立)。屬性動畫即通過這兩個值來改變控件位置的陶衅,也就是說設(shè)置translationX/Y是能夠改變控件位置的脯宿,但是不會改變控件LayoutParams中的margin屬性,改變的是控件本身android:translationX/Y屬性兼搏。

margin屬性會改變控件的頂點坐標(lrtb)卵慰,而translationX/Y屬性是不會改變LayoutParams的,改變的是x與y佛呻。magin屬性是屬于父容器的屬性裳朋,而translationX/Y是屬于控件本身的(理解)。

那么對于屬性動畫的復(fù)位吓著,我們可以直接用view.?setTranslationX(0);

可以通過以下幾種方式獲取控件位置:

view.getLocationInWindow(pos);//控件在其父窗口中的坐標位置

view.getLocationOnScreen(pos);//控件在其整個屏幕上的坐標位置

????view.getLocalVisibleRect();

view.getGlobalVisibleRect();

注:通過View動畫?+ updateLayoutParams的方式也可實現(xiàn)改變位置

Android3.0以下通過兼容庫實現(xiàn)的屬性動畫本質(zhì)還是View動畫


3.改變布局參數(shù):適合于有交互的View

layoutParams=(RelativeLayout.LayoutParams) v.getLayoutParams();


View的事件分發(fā)機制

三個核心方法:

dispatchTouchEvent(MotionEvent event)

onInterceptTouchEvent(MotionEvent event)

onTouchEvent(MotionEvent event)

三者關(guān)系(偽代碼):

public void diapatchTouchEvent(Motion event){

boolean?consume = false;

if(onInterceptTouchEvent(event)){

consume = true;

}else{

if(haveChild)

consume = child. dispatchTouchEvent(event);

}

return consume;

}


事件的傳遞:事件的捕獲過程與事件的冒泡過程

捕獲(傳遞)過程:Activity -> Window -> View(過程中被攔截將終止)

冒泡(處理)過程:View -> Activity(過程中被消費將終止)

當一個點擊事件產(chǎn)生后鲤嫡,對于一個ViewGroup來說首先會調(diào)用dispatchTouchEvent方法,如果這個ViewGroup的onInteceptTouchEvent方法返回true表示要攔截當前事件绑莺,那么該事件會交給這個ViewGroup處理暖眼,它的onTouchEvent方法會被調(diào)用,如果onInterceptTouchEvent方法返回false表示不攔截事件纺裁,這時事件會繼續(xù)傳遞給子元素诫肠,子元素的dispatchTouchEvent被調(diào)用,如此反復(fù)直至事件最終被處理

當一個View需要處理事件時,并且它設(shè)置了onTouchListener区赵,那么onTouch方法會先被調(diào)用惭缰,如果onTouch方法返回false會繼續(xù)調(diào)用onTouchEvent方法,否則onTouchEvent方法將不會被調(diào)用笼才,也就是說onTouch優(yōu)先級比onTouchEvent高漱受。我們常見的onClick優(yōu)先級是最低的,onClick會在onTouchEvent中處理骡送;

在事件傳遞與處理的過程中昂羡,如果某個View的onTouchEvent方法返回false,那么事件會傳遞給父容器的onTouchEvent摔踱;如果最后所有View的onTouchEvent方法均返回false(所有元素都不處理該事件)虐先,那么該事件最終會傳遞給Activity,即Activity的onTouchEvent方法被調(diào)用(一層一層向上拋的過程派敷,類似冒泡)


關(guān)于事件傳遞機制的一些總結(jié):

1.?同一個事件序列是指從手指觸摸屏幕開始到手指離開屏幕蛹批,即從down開始,中間包含n個move篮愉,最后以up結(jié)束

2.?正常情況下一個事件序列只能夠被一個View攔截且消耗腐芍。因為一旦某個元素攔截了某事件,那么同一個事件序列的后續(xù)事件都會直接交由它處理试躏,因此同一個事件序列的事件不能分別由兩個View處理猪勇。(特殊手段:通過調(diào)用其他View的onTouchEvent方法)

3.?當View決定攔截事件后,那么同一事件序列的后續(xù)事件都會由它處理颠蕴,不會再調(diào)用onInterceptTouchEvent詢問是否攔截

4.當View一旦開始處理事件(即執(zhí)行onTouchEvent方法)泣刹,若它不消耗ACTION_DOWN事件,那么同一事件序列的后續(xù)事件都不會再交由它處理犀被,并且將該DOWN事件重新交由父容器處理

5.如果View不消耗除ACTION_DOWN以外的其他事件椅您,那么這個點擊事件會消失,此時父元素的onTouchEvent方法不會被調(diào)用寡键,并且該View能夠持續(xù)收到后續(xù)事件掀泳,最終消失的點擊事件由Activity處理

6.?ViewGroup默認不攔截任何事件

7.?View沒有onInterceptTouchEvent方法,不具有攔截功能昌腰,一旦事件傳遞給它,它的onTouchEvent方法會被調(diào)用

8.?View的onTouchEvent方法默認返回true(除非它是不可點擊的膀跌,clickable與longClickable均為false)遭商。View的longClickable默認都為false,clickable視情況而定捅伤,如Button的clickable默認為true劫流,而TextView的clickable默認為false

注: setOnClickListener時會自動設(shè)置clickable為true

setOnLongClickListener時會自動設(shè)置longClickable為true

9.?View的enable屬性不影響onTouchEvent方法的默認返回值

10.?OnClick發(fā)生的前提的View可點擊并且收到DOWN與UP事件

11.通過requestDisallowInterceptTouchEvent方法可以在子元素中干預(yù)父容器的事件分發(fā)過程(除ACTION_DOWN事件外)

12.在View消耗ACTION_DOWN事件后,父容器仍然可攔截后續(xù)事件

注意:上述提及的可攔截事件的View均指的是ViewGroup

源碼解析:

Activity#dispatchTouchEvent?事件分發(fā)源頭

Window#?superDispatchTouchEvent事件傳遞給DecorView(抽象)

-?Window可控制頂級View的外觀和行為策略

-?Window的唯一實現(xiàn)類是PhoneWindow

PhoneWindow #?superDispatchTouchEvent事件傳遞DecorView(實現(xiàn))

-?DecorView是PhoneWindow的內(nèi)部類,代表頂級View

-?DecorView繼承自FrameLayout祠汇,會調(diào)用其dispatchTouchEvent

ViewGroup#dispatchTouchEvent??ViewGroup的事件分發(fā)

-?ACTION_DOWN事件會導(dǎo)致狀態(tài)重置

-?(actionMasked == MotionEvent.ACTION_DOWN

|| mFirstTouchTarget != null)避免多次執(zhí)行攔截方法

-遍歷子View將事件向下分發(fā)

-?dispatchTransformedTouchEvent調(diào)用子View的事件分發(fā)

若找到接收事件的子View會跳出循環(huán)并賦值mFirstTouchTarget

-如果ViewGroup沒有子元素或者沒有任何子元素處理事件將調(diào)用View的事件分發(fā)super.dispatchTouchEvent(event);


View的滑動沖突

在界面中只要內(nèi)外兩層同時可以滑動仍秤,這個時候就會產(chǎn)生滑動沖突。

主要有三個場景:

場景1:內(nèi)外兩層滑動方向不一致

(如:ViewPager嵌套Fragment可很,F(xiàn)ragment存在ListView诗力,ViewPager?內(nèi)部已進行了滑動沖突的處理)

場景2:內(nèi)外兩層滑動方向一致

(如:ViewPager與SlideMenu同時存在)

場景3:上述兩種場景的嵌套

(如:網(wǎng)易云音樂首頁界面)


滑動沖突的處理規(guī)則

原理:主要利用事件分發(fā)機制

分析:針對場景1情況,我們可以通過某些滑動信息來決定將事件交給誰處理我抠,如:水平滑動距離與垂直滑動距離的比較苇本、水平速度與豎直速度或者通過路徑與水平/垂直方向的角度大小菜拓;針對場景2瓣窄,只能根據(jù)業(yè)務(wù)需求或者自定義控件的效果去決定由誰處理事件;而場景3是場景1與場景2的嵌套纳鼎,我們只需要分別處理好中間層與外層俺夕,以及中間層與內(nèi)層兩個滑動沖突即可。

方式:外部攔截法贱鄙,內(nèi)部攔截法

外部攔截法是指點擊事件先經(jīng)過父容器的攔截處理劝贸,如果父容器需要處理該事件則將其攔截(符合事件分發(fā)機制)

外部攔截法偽代碼如下:

public void boolean onInterceptTouchEvent(MotionEvent event){

? ? boolean intercepted = false;

? ? int x = (int) event.getX();

? ? int y = (int) event.getY();

? ? switch(event.getAction()){

? ? ? ? case MotionEvent.ACTIO_DOWN:

? ? ? ? ? ??intercepted?= false;

? ? ? ? ? ? break;

? ? ? ? case MotionEvent.ACTION_MOVE:

? ? ? ? ? ? if(父容器需要攔截當前事件){

? ? ? ? ? ? ? ??intercepted?= true;

? ? ? ? ? ? }else{

? ? ? ? ? ? ? ??intercepted?= false;

? ? ? ? ? ? }

? ? ? ? ? ? break;

? ? ? ? case MotionEvent.ACTION_UP:

? ? ? ? ? ??intercepted?= false;

? ? ? ? ? ? break;

? ? ? ? default:

? ? ? ? ? ? break;

? ? }

? ? mLastXIntercept = x;

? ? mLastYIntercept = y;

? ? return intercepted;

}

針對不同的滑動沖突,只需修改if條件即可贰逾。在DOWN事件時必須返回false悬荣,因為一旦返回true,后續(xù)事件直接交由父容器處理疙剑,那么事件根本無法傳遞給子元素氯迂;在MOVE事件中進行具體判斷決定是否攔截事件;UP事件中必須返回false言缤,主要2個原因:1.UP事件本身沒有太大意義嚼蚀,其作為事件序列的最后一個事件必定會傳遞給父容器2.若UP事件返回true那么子元素將處理不了click事件

內(nèi)部攔截法是指將父容器是否攔截事件交由子元素決定,這種方式與事件分發(fā)機制不一致管挟,需配合requestDisallowInterceptTouchEvent

內(nèi)部攔截法偽代碼如下:

//?子元素

public boolean dispatchTouchEvent(MotionEvent event){

? ? int x = (int) event.getX();

? ? int y = (int) event.getY();


? ? switch(event.getAction()){

? ? ? ? case MotionEvent.ACTION_DOWN:

? ? ? ? ? ? parent.requestDisallowInterceptTouchEvent(true); // 不允許攔截

? ? ? ? ? ? break;

? ? ? ? case MotionEvent.ACTION_MOVE:

? ? ? ? ? ? int deltaX = x - mLastX;

? ? ? ? ? ? int deltaY = y - mLastY;

? ? ? ? ? ? if(父容器需要攔截事件){

? ? ? ? ? ? ? ? parent.requestDisallowInterceptTouchEvent(false); // 允許攔截

? ? ? ? ? ? }

? ? ? ? ? ? break;

? ? ? ? case MotionEvent.ACTION_UP:

? ? ? ? ? ? break;

? ? ? ? default:

? ? ? ? ? ? break;

? ? }

? ? mLastX = x;

? ? mLastY = y;

}

//?父容器

public boolean onInterceptTouch(MotionEvent event){

? ? int action = event.getAction();

? ? if( action == MotionEvent.ACTION_DOWN ){

? ? ? ? return false;

? ? }else{

? ? ? ? return true; // 默認攔截除ACTION_DOWN以外所有事件

? ? }

}


首先轿曙,父容器默認攔截除DOWN事件以外其他事件。父容器具體是否要攔截事件由子元素決定僻孝,子元素的dispatchTouchEvent方法中导帝,DOWN事件中默認不允許父容器攔截,MOVE事件中根據(jù)具體條件決定父容器是否攔截事件穿铆,UP事件無須關(guān)注

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末您单,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子荞雏,更是在濱河造成了極大的恐慌虐秦,老刑警劉巖平酿,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異悦陋,居然都是意外死亡蜈彼,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門俺驶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來幸逆,“玉大人,你說我怎么就攤上這事痒钝”牛” “怎么了?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵送矩,是天一觀的道長蚕甥。 經(jīng)常有香客問我,道長栋荸,這世上最難降的妖魔是什么菇怀? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮晌块,結(jié)果婚禮上爱沟,老公的妹妹穿的比我還像新娘。我一直安慰自己匆背,他們只是感情好呼伸,可當我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著钝尸,像睡著了一般括享。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上珍促,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天铃辖,我揣著相機與錄音,去河邊找鬼猪叙。 笑死娇斩,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的穴翩。 我是一名探鬼主播犬第,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼芒帕!你這毒婦竟也來了歉嗓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤副签,失蹤者是張志新(化名)和其女友劉穎遥椿,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體淆储,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡冠场,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了本砰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碴裙。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖点额,靈堂內(nèi)的尸體忽然破棺而出舔株,到底是詐尸還是另有隱情,我是刑警寧澤还棱,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布载慈,位于F島的核電站,受9級特大地震影響珍手,放射性物質(zhì)發(fā)生泄漏办铡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一琳要、第九天 我趴在偏房一處隱蔽的房頂上張望寡具。 院中可真熱鬧,春花似錦稚补、人聲如沸童叠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽厦坛。三九已至,卻和暖如春撰豺,著一層夾襖步出監(jiān)牢的瞬間粪般,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工污桦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留亩歹,地道東北人。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓凡橱,卻偏偏與公主長得像小作,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子稼钩,可洞房花燭夜當晚...
    茶點故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容