第三章 View的事件體系
ide
3.1 View基礎知識
佈局
3.1.1 什麼是viewpost
View 是Android中全部控件的基類,是一種界面層的控件的一種抽象,它表明了一個控件。動畫
3.1.2 View的位置參數spa
View的位置主要由它的四個頂點來決定,分別對應於View的四個屬性:top,left,right,bottom;須要注意的是這些座標都是相對於View的父容器來講的;(在Android中X軸和Y軸的正方向分別爲右和下)。code
3.0開始新增的屬性blog
x- view左上角橫座標;事件
y- view左上角縱座標;ci
translationX- view左上角相對於父容器的水平偏移量;get
translationY- view左上角相對於父容器的垂直偏移量;
View在平移過程當中,top和left表示的是原始左上角的位置信息,其值並不會發生變化,此時發生變化的是x,y,translationX,translationY這四個參數。
3.1.3 MotionEvent和TouchSlop
1 MotionEvent
getX和getY方法返回的是當前View左上角的x和y座標,而getRawC和getRawY方法返回的是相對於手機屏幕左上角的x和y座標。
2 TouchSlop
TouchSlop是系統所能識別出的被認爲是滑動的最小距離,是一個常量,經過以下方式能夠得到這個常量:ViewConfiguration.get(getContext()).getScaledTouchSlop();
3.1.4 VelocityTracker,GestureDetector和Scroller
1 VelocityTracker
VelocityTracker—速度追蹤,追蹤手指在滑動過程當中的速度(水平垂直兩個方向),注意速度能夠爲負值,當手指從右向左滑動時,水平方向速度即爲負值。
2 GestureDetector
GestureDetector—手勢檢測,用於輔助檢測用戶的單擊,滑動,長,雙擊等行爲。
建議:若是隻是監聽滑動相關的,在onTouchEvent中實現,若是要監聽雙擊行爲就用GestureDetector。
3 Scroller
Scroller—彈性滑動,用於實現View的彈性滑動(有過渡效果的滑動),須要配合View的computeScroll方法使用。
3.2 View的滑動
實現View滑動的三種方式:
1 經過View自己的scrollTo/scrollBy方法實現滑動;
2 經過動畫給View施加平移效果實現滑動;
3 經過改變View的LayoutParams使得View從新佈局實現滑動;
3.2.1 使用scrollTo/scrollBy
scrollTo/scrollBy的源碼以下:
public void scrollTo(int x ,int y){ if(mScrollX!=x||mScrollY!=y){ int oldX=mScrollX; int oldY=mScrollY; mScrollX=x; mScrollY=y; invalidateParentCaches(); onScrollChanged(mScrollX,mScrollY,oldX,oldY); if(!awakenScrollBars()){ postInvalidateOnAnimation(); } } } public void scrollBy(int x,int y){ scrollTo(mScrollX+x,mScrollY+y); }
其中mScrollX的值老是等於View左邊緣和View內容左邊緣在水平方向的距離,mScrollY的值老是等於View的上邊緣和View內容上邊緣在垂直方向上的距離;
scrollTo/scrollBy只能改變View內容的位置而不能改變自身在佈局中的位置。
也就是說使用scrollTo/scrollBy來實現View的滑動只能將View的內容進行移動,並不能將View自己進行移動。
3.2.2 使用動畫
使用動畫來移動View 主要是操做View的translationX和translationY屬性。(傳統的View動畫和屬性動畫)
View動畫是對View的影像操做,它並不能真正改變View的位置參數,包括寬/高。
3.2.3 改變佈局參數
主要是經過改變View的LayoutParams來實現;下面的代碼展現如何給一個mButton1從新設置LayoutParams:
MarginLayoutParams params=(MarginLayoutParams)mButton1.getLayoutParams();
params.width+=100; params.leftMargin+=100; mButton1.requestLayout();
//或者mButton1.setLayoutParams(params)
3.2.4各類滑動方式的對比
scrollTo/scrollBy:操做簡單,適合對View內容的滑動;
動畫:操做簡單,主要適用於沒有交互的View和實現複雜的動畫效果;
改變佈局參數:操做稍微複雜,適用於有交互的View。
3.3彈性滑動
3.3.1使用Scroller
注意Scroller產生的滑動也是對View內容的滑動而非View自己位置的改變。
Scroller的典型使用方式以下:
Scroller scroller=new Scroller(context); //緩慢滾動到指定位置 private void smoothScrollTo(int destX,int destY){ int scrollx=getScrollX(); int deltaX=destX-scrollX; //1000ms內滑向destX,效果就是慢慢滑動 scroller.startScroll(scrollX,0,deltaX,0,1000); invalidate(); } @Override public void computeScroll(){ if(scroller.computeScrollOffset()){ scrollTo(scroller.getCurrX(),scroller.getCurrY()); postInvalidate(); } }
Scroller的工做機制:Scroller自己並不能實現View的滑動,它須要配合View的computeScroll方法才能完成彈性滑動的效果,它不斷的讓View重繪,而每一次重繪距滑動起始時間會有一個時間間隔,經過這個時間間隔Scroller就能夠得出View當前的滑動位置,知道了滑動位置就能夠經過scrollTo方法來View的滑動。就這樣,View的每一次重繪都會致使View進行小幅度的滑動,而屢次的小幅度滑動就組成了彈性滑動。
3.4 View的時間分發機制
3.4.1 點擊事件的傳遞規則
點擊事件的分發過程主要由三個方法來完成。
dispatchTouchEvent() :用來進行事件的分發,若是事件可以傳遞給當前View,此方法必定會被調用,返回結果受當前View的onTouchEvent和下級View的dispatchTouchEvent方法影響,表示是否消耗當前事件。
onInterceptTouchEvent() :在dispatchTouchEvent方法內部調用,判斷是否攔截某個事件,若是當前View攔截了某個事件,那麼在同一個事件序列中,此方法不會在被調用,返回結果表示是否攔截當前事件。
onTouchEvent() :在dispatchTouchEvent方法中調用,用來處理點擊事件,返回結果表示是否消耗當前事件,若是不消耗,則在同一個事件序列中,當前View沒法再次接收到其餘事件。
事件序列是指從手指接觸屏幕的那一刻起,到手指離開屏幕的那一刻結束,在這個過程當中所產生的一系列事件,這個事件序列以down事件開始,中間含有數量不固定的move事件,最終以up事件結束。
理解了這三個方法的工做過程也就理解了事件的分發機制。僞代碼表示三個方法的關係以下:
public boolean dispatchTouchEvent(MotionEvent ev){ boolean consume=false; if(onInterceptTouchEvent(ev)){ consume=onTouchEvent(ev); }else{ consume=child.dispatchTouchEvent(ev); } return consume; }
具體傳遞規則:對於一個根ViewGroup來講,點擊事件產生之後,首先會傳遞給它,這時它的dispatchTouchEvent就會被調用,若是這個ViewGroup的onInterceptTouchEvent方法返回true就表示它要攔截當前事件,接着事件就會交給這個ViewGroup處理,即它的onTouchEvent方法就會被調用,若是這個ViewGroup的onInterceptTouchEvent返回false就表示它不攔截當前事件,這時當前事件就會繼續傳遞給它的子元素,接着子元素的dispatchTouchEvent方法就會被調用,如此反覆直到事件被最終處理。
當一個點擊事件產生後,它的傳遞過程遵循以下順序:Activity->Window->View,即事件老是先傳遞給Activity,Activity在傳遞給Window,最後Window在傳遞給頂級View。頂級View接收到事件後,就會按照事件分發機制去分發事件。考慮一種狀況,若是一個View的onTouchEvent返回false,那麼它的父容器的onTouchEvent將會被調用,依此類推。若是所用的元素都不處理這個事件,那麼這個事件將會最終傳遞給Activity處理,即Activity的onTouchEvent方法被調用。
幾個重要結論:
正常狀況下,一個事件序列只能被一個VIew攔截且消耗,由於一旦一個元素攔截了某個事件,那麼同一個事件序列內的全部事件都會直接交給它處理,所以同一個事件序列中的事件不能分別有兩個View同時處理,可是經過特殊手段能夠作到,好比一個View將本該本身處理的事件經過onTouchEvent強行傳遞給其餘View處理。
某個View一旦決定攔截,那麼這一個事件序列都只能由它處理,而且它的onInterceptTouchEvent不會再被調用。
某個View一旦開始處理事件,若是它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那麼同一事件序列中的其餘事件都不會再交給它處理,而且事件將從新交由它的父元素去處理,即父元素的onTouchEvent會被調用。意思就是事件一旦交給一個View處理,那麼它就必須消耗掉,不然同一事件序列中剩下的事件就再也不交給它來處理了。
若是View不消耗出ACTION_DOWN之外的其餘事件,那麼這個點擊事件會消失,最終這些消失的點擊事件會傳遞給Activity處理。
ViewGroup默認不攔截任何事件。
View沒有onInterceptTouchEvent方法,一旦有事件傳遞給它,那麼它的onTouchEvent方法就會被調用。
View的OnTouchEvent方法默認都會消耗事件(返回true)。
View的enable屬性不影響onTouchEvent的默認返回值。
onClick會發生的前提是當前View是可點擊的,而且它收到了down和up的事件。
事件的傳遞過程是由外向內的,即事件老是項傳遞給父元素,而後再由父元素分發給子View,經過requestDisallowInterceptTouchEvent方法能夠在子元素中干預父元素的事件分發過程,可是ACTION_DOWN事件除外。(requestDisallowInterceptTouchEvent方法主要設置父元素中的FLAG_DISALLOW_INTERCEPT標記位,一旦設置後,ViewGroup將沒法攔截除了ACTION_DOWN之外的其餘點擊事件)
3.5 View的滑動衝突
常見的滑動衝突場景
解決滑動衝突的方式:外部攔截法和內部攔截法。
1 外部攔截法
點擊事件都先通過父容器的攔截處理,若是父容器須要此事件就攔截,若是不須要此事件就不攔截。
實現方法主要是重寫父容器的onInterceptTouchEvent方法,代碼以下:
@Override
public boolean onInterceptTouchEvent(MotionEvent event) { boolean intercepted=false; int x=(int)event.getX(); int y=(int)event.getY(); switch (event.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; } mLastXIntercept=x; mLastYIntercept=y; return intercepted; }
2 內部攔截法
父容器不攔截任何事件,所用的事件都傳遞給子元素,若是子元素須要此事件就直接消耗掉,不然就交由父容器進行處理。須要配合requestDisallowInterceptTouchEvent方法才能正常工做,同時重寫子元素的dispatchTouchEvent方法,代碼以下:
子元素:
@Override
public boolean dispatchTouchEvent(MotionEvent event) { int x=(int)event.getX(); int y=(int)event.getY(); switch (event.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(event); }
父元素:
@Override
public boolean onInterceptTouchEvent(MotionEvent event) { int action=event.getAction(); if (action==MotionEvent.ACTION_DOWN){ return false; }else{ return true; } }
以上就是滑動處理滑動衝突的典型代碼,當面對不一樣的滑動策略時(場景1,2,3)只須要修改裏面的條件便可。