編輯:關於android開發
第三章 View的事件體系
3.1 View基礎知識
3.1.1 什麼是view
View 是Android中所有控件的基類,是一種界面層的控件的一種抽象,它代表了一個控件。
3.1.2 View的位置參數
View的位置主要由它的四個頂點來決定,分別對應於View的四個屬性:top,left,right,bottom;需要注意的是這些坐標都是相對於View的父容器來說的;(在Android中X軸和Y軸的正方向分別為右和下)。
3.0開始新增的屬性
x- view左上角橫坐標;
y- view左上角縱坐標;
translationX- view左上角相對於父容器的水平偏移量;
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)只需要修改裡面的條件即可。
Android系統之路(初識MTK) ------ System-Bluetooth name/WiFi AP name/sleep add never/Notifica
Android-ViewPager的使用 Android-ViewPager的使用 ViewPager是安卓App很常用的工具類,通常是用來設置界面導航,比如微信,QQ
安卓UI適配限定符 引言 對於程序在不同尺寸的Android機器上運行,對UI的適用性造成了額外的開銷,不過限定符的出現,很方便的解決了這個問題。通過創建
Android如何自學----轉自lavor從segmentfault,如何自學Android 1. Java知識儲備 本知識點不做重點講解: 對於有基礎的