您好,登錄后才能下訂單哦!
這篇“Android View的事件體系實例分析”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Android View的事件體系實例分析”文章吧。
View是Android中所有控件的基類,不管是Button、ListView還是RelativeLayout,它們的基類都是View。View是一種界面層的控件的一種抽象,代表了一個控件。
而什么是ViewGroup,從字面上看,ViewGroup應該指的是一個控件組,即ViewGroup中可以包含許多控件。而ViewGroup繼承自View,所以View本身就可以是單個控件也可以由多個控件組成的一組控件。這樣就構成了View樹。
View的位置由它的四個頂點確定,top(左上角縱坐標)、left(左上角橫坐標)、bottom(右下角縱坐標)、right(右下角橫坐標),這幾個參數都是相對父級容器而言的。
在Android中,X軸和Y軸的正方向分別為向右和向下。
根據四個頂點及AndroidView的坐標系,我們可以很容易得到View的寬高和坐標的關系:
width=right-left
height=bottom-top
那么如何得到這四個頂點呢?
left=getLeft();
right=getRight();
top=getTop();
bottom=getBottom();
從Android3.0開始,View增加了x,y,translationX和translationY。其中x和y是view左上角的坐標(相對坐標系),而translationX和translationY是View左上方相對父容器的偏移量。
x=left+translationX
y=top+translationY
需要注意的是View在平移過程中,top和left表示的是原始左上角的位置信息,其值不會改變,此時改變的是x、y、translationX和translationY
在手指接觸屏幕后所產生的一系列事件中,典型的事件有:
ACTION_DOWN——手指剛接觸屏幕
ACTION_MOVE——手指在屏幕上移動
ACTION_DOWN——手指從屏幕上松開
一般我們可以將一次手指接觸屏幕的行為分為兩種情況:
點擊屏幕后松開,事件序列為DOWN->UP
點擊屏幕滑動一段時間后松開,事件序列為DOWN->MOVE->…->MOVE->UP
TouchSlop即系統能識別滑動的最小距離,這是一個與設備有關的系統常量。不難得知其意思,當手指在屏幕上滑動小于這個距離時,系統不認為你在進行滑動操作。
通過ViewConfiguration.get(getContext()).getScaledTouchSlop()方法來獲取這個系統常量。
速度追蹤,用于追蹤手指在滑動過程中的速度,包括水平和豎直方向上的速度。
具體用法:
在View的onTouchEvent方法中追蹤當前單擊事件的速度。
VelocityTracker velocityTracker =VelocityTracker.obtain(); velocityTracker.addMovement(event); //接著當我們我們想知道當前的滑動速度時 //獲取速度前先計算速度, 參數 時間間隔 單位ms velocityTracker.computeCurrentVelocity(1000); //獲取速度 int xVelocity = (int)velocityTracker.getXVelocity(); int yVelocity=(int)velocityTracker.getYVelocity();
需要注意的是,這邊的計算得到的速度與時間間隔有關,其計算公式如下:
速度=(終點位置-起點位置)/時間間隔
計算速度時得到是就是一定時間間隔內手指在水平或豎直方向上滑動的像素數,如
100像素/1000ms,這里的速度值即為100。
當然在不需要使用它時,需要調用clear方法來重置并回收內存。
velocityTracker.clear();
velocityTracker.recycle();
4.GestureDetector
手勢檢測,用于輔助檢測用戶的單擊、滑動、長按、雙擊等行為。如果只是監聽滑動相關的建議在onToucheEvent實現,如果需要監聽雙擊,使用GestureDetector。
彈性滑動對象,用來實現View的彈性滑動,View的scrollTo/scrollBy是瞬間完成的,使用Scroller配合View的computeScroll方法配合使用達到彈性滑動的效果
其典型代碼是通用的.
/** * 平滑滾動 * @param dx 橫向位移 * @param dy */ private void smoothScrollBy(int dx, int dy) { //水平滑動 mScroller.startScroll(getScrollX(),0,dx,0,500); invalidate(); } @Override public void computeScroll() { if(mScroller.computeScrollOffset()){ scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); postInvalidate(); } }
實現View滑動的幾種方式:
View的滑動方式 | 特點 | 適用場景 |
使用ScrollTo/ScrollBy | 只能改變View的內容,不能改變View本身的位置 | 適合對view內容的滑動 |
通過動畫實現View的平移效果 | 只對影像進行操作,不能改變View的位置參數 | 適用于沒有交互的View和實現復雜的動畫效果。 |
使用屬性動畫實現View的平移效果 | 改變View的位置參數,可響應觸摸等事件 | 適用于有交互的View,適配到Android3.0 |
改變View的LayoutParams,使得View重新布局實現滑動 | 改變View的位置參數,可響應觸摸等事件 | 適用于有交互的View,使用稍復雜 |
前面提到了彈性滑動對象Scroll,其實實現彈性滑動的方法不止這一種,它們的共同思想就是將一次大的滑動分成若干次小的滑動并要求在一定時間內完成。實現彈性滑動的具體實現方式有:
通過Scroll實現
通過動畫
使用延時策略
使用Scroll實現彈性滑動需要配合View的computeScroll方法實現,簡單來講就是實現多次重繪,每一次重繪有一定的時間間隔,通過這個時間間隔Scroller可以得到View的當前滑動位置,然后通過ScrollTo方法實現滑動。
具體實現方法是在自己實現的平滑滑動方法中調用invalidate方法,它會導致View重繪,又因為在View的draw方法中又會去調用computeScroll方法,而在computeScroll方法中,我們實現了scrollTo方法來實現滑動,接著調用postInvalidate來進行第二次重繪,此時又會調用View中的draw方法,,繼而調用computeScroll方法,如此反復,直到整個滑動過程完成。
動畫本身就是一種漸漸地過程,可以很好地實現彈性滑動。
使用延時策略完成滑動,核心思想就是通過發送一系列的延時消息從而達到一種漸進的效果。具體的實現可以采用Handler或View的postDelayed方法,也可以采用sleep休眠。對于postDelayed方法們可以通過它來延時發送一個消息,然后在消息中進行View的滾動。如果接連不斷發的發送這種消息,則可以達到彈性滑動對象。
而對于sleep方法,通過在while循環中不斷滑動View和sleep即可實現。
分發對象:MotionEvent,所謂的事件分發其實就是對MotionEvent事件的分發過程,即需要將這個事件傳遞到一個具體的View上進行處理。而完成這一過程需要三個重要方法來共同完成:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。
public boolean dispatchTouchEvent(MotionEvent ev)
用來進行事件的分發,返回結果受當前View的onTouchEvent和下級View的dispatchTouchEvent方法的影響,表示是否消耗當前事件。
public boolean onInterceptTouchEvent(MotionEvent ev)
在dispatchTouchEvent方法內部調用,用于判斷是否攔截某個時間,如果當前View攔截了某個事件,那么在同一個事件序列當中,此方法不會被再次調用。
public boolean onTouchEvent(MotionEvent event)
在dispatchTouchEvent方法中調用,用于處理點擊事件,返回結果表示是否消耗事件,如果不消耗,則在同一個事件序列中,當前View無法再次收到事件。
建立在上述方法的基礎上,我們簡單分析一下事件分發的過程。
由上述流程圖不難發現,在MotionEvent被一個View所攔截時,其內部的事件分發的過程中,onTouchListener的優先級高于onTouchEvent,而常用的onClickListener的優先級是最低的。即在onTouch->onTouchEvent->onClick。
幾個重要的結論:
在整個View樹中的事件分發中,如果一個View一旦開始處理事件,但它不消耗ACTION_DOWN事件(onTOuchEvent返回false),那么同一個事件序列中的其他事件也不會交給它來處理,而是將事件重新交給它的父元素進行處理,即父元素的onTouchEvent會被調用。
而如果一個View消耗了ACTION_DOWN,但沒有消耗事件序列中的其他事件,那么這個點擊事件會消失,并且此時父元素的onTouchEvent也不會被調用,當前View可以持續受到后續的事件,最終這些消失的點擊事件會傳遞給Activity處理。
ViewGroup默認不攔截任何事件
View沒有onInterceptTouchEvent方法,一旦有點擊事件傳遞給它,就好調用它的onTouchEvent方法。
View的onTouchEvent默認都是會消耗事件的,除非它是不可點擊的(clickable和longClickable為false)
事件傳遞過程是由外向內的,即事件總是傳給父元素,然后再由父元素分發給子View,通過requestDisallowInterceptTouchEvent方法可以在子元素中干預父元素中的分發過程(除ACTION_DOWN)
1.外部滑動和內部滑動方向不一致
2.外部滑動方向和內部滑動方向一致
3.上面兩張情況的嵌套
對于第一種情況,一個很好的例子就是ViewPager和Fragment嵌套使用組成的頁面滑動效果,而在Fragment內部又會嵌套一個ListView。大家都知道ViewPager的滑動方向是水平的,而ListView的滑動方向是豎直的,這種情形和第一種情況是相符的。當然ViewPager在內部處理了這種滑動沖突,因此采用ViewPager不用考慮這個問題。而如果我們采用的是ScrollView,則必須手動處理這種滑動沖突了。
對于第二種情況,即內外兩層都是需要上下滑動或者左右滑動的。可以舉一個常見的例子,即ViewPager和NavigationDrawer。這兩者都是水平方向的滑動。當然在實際使用中,會發現并沒有滑動沖突,還是上一個原因,ViewPager內部處理了這種滑動沖突。
第三種情況即前面兩個例子的融合。
如何解決滑動沖突,這就需要用到前面講到的事件分發機制了,其核心思想就是根據實際事件的特點(down的位置,水平滑動距離,豎直滑動距離等)來判斷由哪個View來攔截事件。對于第一種情況可以簡單地判斷是水平滑動還是豎直滑動來判斷由哪個View來攔截事件。(可以根據水平和豎直方向上的距離差或速度差來進行判斷),而對于第二種情況,可根據down的位置來加以區分。
外部攔截法 —— 即點擊事件先經過父容器的攔截處理,如果父容器需要此事件就攔截,不需要就不攔截,需要重寫父容器的onInterceptTouchEvent方法;在onInterceptTouchEvent方法中,首先ACTION_DOWN這個事件,父容器必須返回false,即不攔截ACTION_DOWN事件,因為一旦父容器攔截了ACTION_DOWN,那么后續的ACTION_MOVE/ACTION_UP都會直接交給父容器處理;其次是ACTION_MOVE,根據需求來決定是否要攔截;最后ACTION_UP事件,這里必須要返回false,在這里沒有多大意義。
內部攔截法 —— 所有事件都傳遞給子元素,如果子元素需要就消耗掉,不需要就交給父元素處理,需要子元素配合requestDisallowInterceptTouchEvent方法才能正常工作;父元素需要默認攔截除ACTION_DOWN以外的事件,這樣子元素調用parent.requestDisallowInterceptTouchEvent(false)方法時,父元素才能繼續攔截需要的事件。(ACTION_DOWN事件不受requestDisallowInterceptTouchEvent方法影響,所以一旦父元素攔截ACTION_DOWN事件,那么所有元素都無法傳遞到子元素去)
兩種攔截方法的范式(偽代碼形式):
外部攔截法:只需要重寫父容器的onInterceptTouchEvent方法
private int mLastXIntercepet=0; private int mLastYIntercepet=0; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean intercepted =false; int x=(int) ev.getX(); int y=(int) ev.getY(); switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: intercepted=false; break; case MotionEvent.ACTION_MOVE: if(父容器需要當前點擊事件){ intercepted=true; }else{ intercepted=false; } break; case MotionEvent.ACTION_UP: intercepted=false; break; default: break; } mLastXIntercepet=x; mLastYIntercepet=y; return intercepted; }
內部攔截法:需要重寫子元素的dispatchTouchEvent方法和父容器的onInterceptTouchEvent方法。
子元素的dispatchTouchEvent方法
//分別記錄上次滑動的坐標 private int mLastX=0; private int mLastY=0; @Override public boolean dispatchTouchEvent(MotionEvent ev) { int x= (int) ev.getX(); int y= (int) ev.getY(); switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: int deltaX=x-mLastX; int deltaY=y-mLastY; if(父容器需要此類點擊事件){ getParent().requestDisallowInterceptTouchEvent(false); } break; case MotionEvent.ACTION_UP: break; default: break; } mLastX=x; mLastY=y; return super.dispatchTouchEvent(ev); }
父容器的onInterceptTouchEvent:
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { int x= (int) ev.getX(); int y= (int) ev.getY(); int action=ev.getAction(); if(action==MotionEvent.ACTION_DOWN) return false; else return true; }
以上就是關于“Android View的事件體系實例分析”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。