多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國(guó)內(nèi)最全I(xiàn)T社區(qū)平臺(tái) 聯(lián)系我們 | 收藏本站
阿里云優(yōu)惠2
您當(dāng)前位置:首頁(yè) > php開(kāi)源 > 綜合技術(shù) > 徹底理解View事件體系!

徹底理解View事件體系!

來(lái)源:程序員人生   發(fā)布時(shí)間:2016-07-06 13:43:05 閱讀次數(shù):2803次

我的簡(jiǎn)書(shū)同步發(fā)布:完全理解View事件體系!

轉(zhuǎn)載請(qǐng)注明出處:【huachao1001的專欄:http://blog.csdn.net/huachao1001】

View的事件體系整體上理解還是比較簡(jiǎn)單的,但是卻有很多細(xì)節(jié)。這些細(xì)節(jié)很容易忘記,本文的目標(biāo)是理解性的記憶,爭(zhēng)取做到看完不忘。最近在溫習(xí),希望本文能對(duì)你也有所幫助。如果你已對(duì)View事件體系有1定的了解,那末查漏補(bǔ)缺,看看你是否是已掌握了以下內(nèi)容呢?

1 View事件相干基礎(chǔ)

在正式接觸View事件體系之前,先看看相干基礎(chǔ)部份。

1.1 View的坐標(biāo)及寬高

在Android系統(tǒng)中,1個(gè)子View在ViewGroup中顯示的區(qū)域由top、right、bottom、left4個(gè)屬性肯定。它們分別肯定4條邊,以下圖所示:

子View所在區(qū)域

這4個(gè)參數(shù)我們可以通過(guò)以下方法得到:

//假定v是個(gè)View實(shí)例 //View v=···; int top = v.getTop(); int right = v.getRight(); int bottom = v.getBottom(); int left = v.getLeft();

拿到這4個(gè)參數(shù)后,我們也能夠計(jì)算出寬高:

int width = right-left; int height = bottom-top;

我們知道,在Android3.0(api 11)之前,是不能用屬性動(dòng)畫(huà)的,只能用補(bǔ)間動(dòng)畫(huà),而補(bǔ)間動(dòng)畫(huà)所做的動(dòng)畫(huà)效果只是將View的顯示轉(zhuǎn)為圖片,然后再針對(duì)這個(gè)圖片做透明度、平移、旋轉(zhuǎn)、縮放等效果。這帶來(lái)的問(wèn)題是,View所在的區(qū)域并沒(méi)有產(chǎn)生變化,變化的只是個(gè)“幻影”而已。也就是說(shuō),在Android 3.0之前,要想將View區(qū)域產(chǎn)生變化,就得改變topleftrightbottom。如果我們想讓View的動(dòng)畫(huà)是實(shí)際的位置產(chǎn)生變化,并且要兼容3.0之前的軟件,該怎樣辦呢?為了解決這個(gè)問(wèn)題,從3.0開(kāi)始,加了幾個(gè)新的參數(shù):xytranslationXtranslationY

x = left + translationX; y = top + translationY;

這樣,如果我們想要移動(dòng)View,只需改變translationXtranslationY就能夠了,top和left不會(huì)產(chǎn)生變化。也能夠使用屬性動(dòng)畫(huà)去改變translationXtranslationY

1.2 手勢(shì)辨認(rèn)

(1)VelocityTracker 速度追蹤

我們知道,很多ViewGroup中,假定手指滑動(dòng)的距離相同,但是滑動(dòng)速度不同,那末滑動(dòng)速度越快,ViewGroup中內(nèi)容轉(zhuǎn)動(dòng)的距離越遠(yuǎn)。那末如何辨認(rèn)用戶滑動(dòng)的速度呢?固然了,你可以在onTouchEvent中不斷的監(jiān)聽(tīng)計(jì)算。但是那樣的代碼太臃腫了,而且容易算錯(cuò)。好在Android系統(tǒng)內(nèi)置了速度追蹤類VelocityTracker。有了它,媽媽不再用擔(dān)心如何計(jì)算速度追蹤。先看看怎樣用:

//event1般是通過(guò)onTouchEvent函數(shù)傳遞的MotionEvent對(duì)象 VelocityTracker vt=VelocityTracker.obtain(); vt.addMovement(event);

VelocityTracker.obtain();這句可以看出,這里是使用了享元模式,對(duì)享元模式不太熟習(xí)的童鞋請(qǐng)參考我的另外一篇文章《從Android代碼中來(lái)記憶23種設(shè)計(jì)模式》 。那末如何獲得當(dāng)前的移動(dòng)速度呢?

vt.computeCurrentVelocity(1000); int xv=(int) vt.getXVelocity(); int yv=(int) vt.getYVelocity();

在調(diào)用獲得x和y方向的速度之前,先要調(diào)用computeCurrentVelocity函數(shù),用于設(shè)定計(jì)算速度的時(shí)間間隔。很明顯,速度的計(jì)算為(終端位置-起始位置)/間隔時(shí)間。

既然是享元模式,那肯定是需要回收的啦~我們看看如何回收VelocityTracker對(duì)象:

vt.clear(); vt.recycle();

(2)GestureDetector手勢(shì)檢測(cè)

一樣,我們有時(shí)還需要檢測(cè)用戶的:?jiǎn)螕簟⒒瑒?dòng)、長(zhǎng)按、雙擊等動(dòng)作。懶得自己去計(jì)算時(shí)間來(lái)辨認(rèn),直接用系統(tǒng)的GestureDector來(lái)監(jiān)聽(tīng)這些事件,GestureDector的使用也非常簡(jiǎn)單:

GestureDetector.OnGestureListener listener=new GestureDetector.OnGestureListener() { @Override public boolean onDown(MotionEvent e) { //手指出品按下的瞬間 return false; } @Override public void onShowPress(MotionEvent e) { //手指觸摸屏幕,并且還沒(méi)有松開(kāi)或拖動(dòng)。與onDown的區(qū)分是,onShowPress強(qiáng)調(diào)沒(méi)用松開(kāi)和沒(méi)有拖動(dòng) } @Override public boolean onSingleTapUp(MotionEvent e) { //手指離開(kāi)屏幕(單擊) return false; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { //手指按下并拖動(dòng),當(dāng)前正在拖動(dòng) return false; } @Override public void onLongPress(MotionEvent e) { //手指長(zhǎng)按事件 } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { //手指快速滑動(dòng) return false; } }; GestureDetector mGestureDetector = new GestureDetector(this,listener); //避免長(zhǎng)按后沒(méi)法拖動(dòng)的問(wèn)題 mGestureDetector.setIsLongpressEnabled(false);

既然要讓GestureDetector來(lái)辨認(rèn)各種動(dòng)作事件,那末就得讓GestureDetector來(lái)接收事件管理,即在onTouchEvent里面只寫入以下代碼:

return mGestureDetector.onTouchEvent(event);

我們看到,OnGestureListener 監(jiān)聽(tīng)器包括了各種事件的監(jiān)聽(tīng)。除OnGestureListener之外,還有OnDoubleTapListener它主要是處理雙擊相干的事件,可以通過(guò)setOnDoubleTapListener將該監(jiān)聽(tīng)器設(shè)置到GestureDetector中。

2 View事件分發(fā)機(jī)制

2.1 3個(gè)重要函數(shù)

前面做了基礎(chǔ)熱身以后,我們現(xiàn)在開(kāi)始學(xué)習(xí)View的事件分發(fā)機(jī)制。View的事件分發(fā)主要是由3個(gè)函數(shù)決定:dispatchTouchEventonInterceptTouchEventonTouchEvent。1個(gè)觸摸事件,如果事件坐標(biāo)處于ViewGroup所“管轄范圍”,首先調(diào)用的是該ViewGroupdispatchTouchEvent函數(shù),dispatchTouchEvent函數(shù)內(nèi)部調(diào)用onInterceptTouchEvent函數(shù),用于判斷是不是攔截該事件,如果攔截,則調(diào)用ViewGrouponTouchEvent。否則調(diào)用子ViewdispatchTouchEvent函數(shù),可以參考以下圖:

事件分發(fā)過(guò)程

注意,上述圖中,只是描寫事件從ViewGroup往下傳遞進(jìn)程,沒(méi)有斟酌子ViewonTouchEvent的返回值,即沒(méi)有斟酌事件從子View往上回傳的進(jìn)程。后面再介紹事件回傳的進(jìn)程。ViewGroup是不是攔截事件,是通過(guò)onTnterceptTouchEvent返回值來(lái)肯定,當(dāng)返回true時(shí),表示攔截該事件,那末該系列事件全部傳遞給ViewGrouponTouchEvent,如果返回false,則表示不攔截該系列事件,該系列事件全部交給子View來(lái)處理。為何我們說(shuō)是“該系列事件”,而不是說(shuō)“該事件”呢?注意,View的事件體系中,從down->move->……->move->up。這1個(gè)進(jìn)程為同1個(gè)事件系列,當(dāng)不攔截該系列事件是,該系列事件的所有的事件都不會(huì)攔截。

2.2 事件來(lái)源

我們知道,我們直接通過(guò)onTouchEvent里面的形參就能夠拿到事件對(duì)象,可是事件對(duì)象時(shí)從哪里產(chǎn)生的?又是經(jīng)歷過(guò)哪些曲折的道路才到達(dá)目的地的?

首先,Activity拿到事件對(duì)象,Activity把事件對(duì)象傳遞給PhoneWindowPhoneWindow再傳遞給DecorViewDecorView通過(guò)遍歷再傳遞到我們的ViewGroup。那末Activity又是從哪里得到事件對(duì)象的呢?這里面就觸及的比較底層了,感興趣的童鞋參考任玉剛的《 Android中MotionEvent的來(lái)源和ViewRootImpl 》這篇文章。

2.3 從onTouch、onClick、onTouchEvent優(yōu)先級(jí)開(kāi)始

當(dāng)1個(gè)View處理觸摸事件時(shí),如果同時(shí)設(shè)置了OnTouchListener(內(nèi)含onTouch抽象方法)、OnClickListener(內(nèi)含onClick抽象方法).那末到底哪一個(gè)函數(shù)先履行?我們做1個(gè)實(shí)驗(yàn),自定義1個(gè)View,重寫onTouchEvent:

@Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: { Log.d("--> down ", "onTouchEvent"); break; } case MotionEvent.ACTION_MOVE: { Log.d("--> move ", "onTouchEvent"); break; } case MotionEvent.ACTION_UP: { Log.d("--> up ", "onTouchEvent"); break; } } return true; }

并在MainActivity設(shè)置OnTouchListenerOnClickListener

myView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { Log.d("--> down", "onTouch"); break; } case MotionEvent.ACTION_MOVE: { Log.d("--> move", "onTouch"); break; } case MotionEvent.ACTION_UP: { Log.d("--> up", "onTouch"); break; } } return false; } }); myView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.d("-->", "onClick"); } });

點(diǎn)擊后,打印的日志信息以下:

06-27 00:36:56.756 2407-2407/? D/--> down: onTouch 06-27 00:36:56.756 2407-2407/? D/--> down: onTouchEvent 06-27 00:36:56.848 2407-2407/? D/--> up: onTouch 06-27 00:36:56.849 2407-2407/? D/--> up: onTouchEvent

注意到,首先履行的是onTouch然后再履行onTouchEvent,因而可知,onTouchonTouchEvent優(yōu)先級(jí)高。代碼中,onTouch返回的是false,表示不消耗事件,因此,觸摸事件能順利的從onTouch傳遞到onTouchEvent,現(xiàn)在我們把onTouch返回值改成true,表示消耗觸摸事件,看看會(huì)打印甚么日志:

06-27 00:42:09.783 2499-2499/? D/--> down: onTouch 06-27 00:42:09.863 2499-2499/? D/--> up: onTouch

正如我們所料想的那樣,并沒(méi)有履行onTouchEvent。我們看到,onClick并沒(méi)有履行。這是為何呢?仔細(xì)看看onTouchEvent的返回值,我們看到,onTouchEvent返回的是true,表示消耗觸摸事件,而此時(shí)onClick就沒(méi)履行了。是否是可以料想:onTouchEvent優(yōu)先級(jí)比onClick高。我們把onTouchEvent返回值改成false,看看日志信息(確保onTouch返回值也是false,否則onTouchEvent連觸摸事件都拿不到,更別談是不是消耗觸摸事件的問(wèn)題了):

06-27 00:48:22.214 2947-2947/? D/--> down: onTouch 06-27 00:48:22.214 2947-2947/? D/--> down: onTouchEvent

甚么?!!!,為何還是沒(méi)有履行onClick?仔細(xì)視察會(huì)發(fā)現(xiàn)連up事件也沒(méi)了~。為何up事件沒(méi)有了呢?主要是,onTouchEvent返回false,表示對(duì)此系列的事件不處理(不消耗),那末該系列事件又會(huì)返回到ViewGrouponTouchEvent。后續(xù)的moveup事件也不會(huì)再交給子ViewonTouchEvent了。這個(gè)進(jìn)程我們暫時(shí)先放1放,回到我們前面所說(shuō)的,為何onClick不履行?注意!甚么是點(diǎn)擊?其實(shí),點(diǎn)擊包括downup,因此我們需要判斷downup是不是都是在當(dāng)前View區(qū)域內(nèi),我們固然就沒(méi)辦法只根據(jù)1個(gè)事件來(lái)判斷是不是需要履行onClick。因此,onTouchEvent的返回值不能用于決定是不是把事件傳遞給onClick。如果想把事件傳遞到onClick函數(shù),我們需要在onTouchEvent里做判斷,并顯式調(diào)用OnClickListener實(shí)例對(duì)象的onClick。固然了,你可以不用自己寫,直接在你的onTouchEvent中的最后1句改成:

return super.onTouchEvent(event);

View在onTouchEvent函數(shù)中,根據(jù)觸摸事件判斷,顯式的調(diào)用了OnClickListener實(shí)例對(duì)象的onClick。調(diào)用進(jìn)程封裝到performClick函數(shù)中,看看performClick源碼:

public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result; }

因此可以得出結(jié)論,履行的順序是:onTouch->onTouchEvent->onClick。當(dāng)onTouch返回false時(shí),onTouchEvent才會(huì)履行,當(dāng)onTouchEvent顯式調(diào)用onClick時(shí),onClick才會(huì)履行。

2.4 事件的回傳進(jìn)程

我們知道,在ViewGroup中,事件是dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent。由onInterceptTouchEvent決定是不是將事件傳遞給子View。如果傳遞給子View,但是子View其實(shí)不想處理這個(gè)系列的事件(子View的onTouchEvent返回false),該怎樣處理這個(gè)系列事件呢?難道就拋棄這個(gè)系列的觸摸事件不管了嗎?固然不是!我們先看1段測(cè)試代碼:

自定義的ViewGroup,重新以下函數(shù):

@Override public boolean dispatchTouchEvent(MotionEvent ev) { print(ev, "ViewGroup dispatchTouchEvent"); return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { print(ev, "ViewGroup onInterceptTouchEvent"); //不攔截,將事件往子View傳遞 return false; } @Override public boolean onTouchEvent(MotionEvent event) { print(event, "ViewGroup onTouchEvent"); return true; }

為了減少重復(fù)代碼,我們定義了print函數(shù):

private void print(MotionEvent event, String msg) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: { Log.d("--> down ", msg); break; } case MotionEvent.ACTION_MOVE: { Log.d("--> move ", msg); break; } case MotionEvent.ACTION_UP: { Log.d("--> up ", msg); break; } } }

自定義View,重寫以下函數(shù):

@Override public boolean dispatchTouchEvent(MotionEvent event) { print(event, "childView dispatchTouchEvent"); return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { print(event, "childView onTouchEvent"); //子View不處理該系列事件 return false; }

觸摸子View后,打印以下信息:

06-27 01:25:38.491 3666-3666/? D/--> down: ViewGroup dispatchTouchEvent 06-27 01:25:38.491 3666-3666/? D/--> down: ViewGroup onInterceptTouchEvent 06-27 01:25:38.491 3666-3666/? D/--> down: childView dispatchTouchEvent 06-27 01:25:38.491 3666-3666/? D/--> down: childView onTouchEvent 06-27 01:25:38.491 3666-3666/? D/--> down: ViewGroup onTouchEvent 06-27 01:25:38.589 3666-3666/? D/--> up: ViewGroup dispatchTouchEvent 06-27 01:25:38.589 3666-3666/? D/--> up: ViewGroup onTouchEvent

看到,當(dāng)子ViewonTouchEvent返回的是false,那末該系列的事件會(huì)回到ViewGrouponTouchEvent。注意,down事件先到達(dá)子View的onTouchEvent,如果子View不消耗,則down事件及其后續(xù)的事件會(huì)傳到ViewGrouponTouchEvent。而ViewGrouponTouchEvent也是1樣,如果ViewGroup不處理該系列事件,又會(huì)繼續(xù)回傳到ViewGroup的父View的onTouchEvent。以下圖所示:

事件回傳

我們以上討論的點(diǎn)擊位置都是子View所處的區(qū)域,即以下如所示。

點(diǎn)擊區(qū)域

如果點(diǎn)擊不是子View所處的區(qū)域,事件的傳遞會(huì)是怎樣樣的呢?我們看看日志信息:

06-27 01:48:25.064 3666-3666/? D/--> down: ViewGroup dispatchTouchEvent 06-27 01:48:25.064 3666-3666/? D/--> down: ViewGroup onInterceptTouchEvent 06-27 01:48:25.064 3666-3666/? D/--> down: ViewGroup onTouchEvent 06-27 01:48:25.143 3666-3666/? D/--> move: ViewGroup dispatchTouchEvent 06-27 01:48:25.143 3666-3666/? D/--> move: ViewGroup onTouchEvent 06-27 01:48:25.143 3666-3666/? D/--> up: ViewGroup dispatchTouchEvent 06-27 01:48:25.143 3666-3666/? D/--> up: ViewGroup onTouchEvent

可以看到,子View并沒(méi)有調(diào)用任何函數(shù)。這很容易理解,由于壓根就跟子View沒(méi)有半毛錢關(guān)系,要是點(diǎn)擊任意區(qū)域子View都會(huì)有事件傳遞過(guò)去那才奇怪呢!因此,可以看出,ViewGroup在傳遞觸摸事件時(shí),會(huì)遍歷子View,判斷觸摸點(diǎn)是不是在各個(gè)子View中,如果在,則觸發(fā)調(diào)用相干函數(shù)。如果點(diǎn)擊的位置沒(méi)有子View,那末不管onIntercepTouchEvent返回的是甚么,ViewGroup的onTouchEvent都會(huì)履行!

最后,有幾點(diǎn)必須要知道的:

  • 如果View只消耗down事件,而不消耗其他事件,那末其他事件不會(huì)回傳給ViewGroup,而是默默的消逝掉。我們知道,1旦消耗down時(shí)間,接下來(lái)的該系列所有的事件都會(huì)交給這個(gè)View,因此,如果不處理down之外的事件,這些事件就會(huì)被“拋棄”。
  • 如果ViewGroup決定攔截,那末這個(gè)系列事件都只能由它處理,并且onInterceptTouchEvent不會(huì)再被調(diào)用。
  • 某個(gè)View,在onTouchEvent中,如果針對(duì)最開(kāi)始的down事件都返回false,那末接下來(lái)的事件系列都不會(huì)交給這個(gè)View
  • ViewGroup默許不攔截事件,即onInterceptTouchEvent默許返回false
  • ViewonTouchEvent默許返回true,即消耗事件。
  • View沒(méi)有onInterceptTouchEvent方法。
生活不易,碼農(nóng)辛苦
如果您覺(jué)得本網(wǎng)站對(duì)您的學(xué)習(xí)有所幫助,可以手機(jī)掃描二維碼進(jìn)行捐贈(zèng)
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關(guān)閉
程序員人生
主站蜘蛛池模板: 老司机亚洲精品影院在线 | 欧美成人做性视频在线播放 | 色视频在线播放 | 一区二区视频在线观看 | 毛片网站观看 | 日韩视频一区二区三区 | 国产成人做受免费视频 | 欧美成人全部免费观看1314色 | 欧美日韩视频二区三区 | 欧美三级免费观看 | 另类ts人妖一区二区三区 | www久久久 | 在线亚洲精品视频 | 日本大片免费播放网站 | 男女视频在线观看免费 | 国产精品久久久久久免费播放 | 性xx视频| 日本aaa成人毛片 | 国产尤物精品视频 | 成人亚洲网站 | 色老头福影院韩国激情影院 | 曰韩一级 | 亚洲色图欧美在线 | 青青草原亚洲视频 | 中文字幕欧美日韩久久 | 老司机免费福利视频 | 国产精品成人免费综合 | 99久久精品国产麻豆 | 日韩欧美爱爱 | 国产精品秋霞午夜 | 免费国产阿v视频在线观看 免费国产成高清人在线视频 | 亚洲午夜在线观看 | 最近最新中文字幕在线第一页 | 国产免费全部免费观看 | 亚洲欧美精品久久 | 免费一级毛片在级播放 | tube日本xxxx69 | 国产欧美精品一区二区三区四区 | 欧美福利视频在线 | 午夜网站在线观看 | 欧美精品在线一区 |