- Android開發藝術探索
- 任玉剛
- 2890字
- 2019-01-10 17:37:39
3.1 View基礎知識
本節主要介紹View的一些基礎知識,從而為更好地介紹后續的內容做鋪墊。主要介紹的內容有:View的位置參數、MotionEvent和TouchSlop對象、VelocityTracker、GestureDetector和Scroller對象,通過對這些基礎知識的介紹,可以方便讀者理解更復雜的內容。類似的基礎概念還有不少,但是本節所介紹的都是一些比較常用的,其他不常用的基礎概念讀者可以自行了解。
3.1.1 什么是View
在介紹View的基礎知識之前,我們首先要知道到底什么是View。View是Android中所有控件的基類,不管是簡單的Button和TextView還是復雜的RelativeLayout和ListView,它們的共同基類都是View。所以說,View是一種界面層的控件的一種抽象,它代表了一個控件。除了View,還有ViewGroup,從名字來看,它可以被翻譯為控件組,言外之意是ViewGroup內部包含了許多個控件,即一組View。在Android的設計中,ViewGroup也繼承了View,這就意味著View本身就可以是單個控件也可以是由多個控件組成的一組控件,通過這種關系就形成了View樹的結構,這和Web前端中的DOM樹的概念是相似的。根據這個概念,我們知道,Button顯然是個View,而LinearLayout不但是一個View而且還是一個ViewGroup,而ViewGroup內部是可以有子View的,這個子View同樣還可以是ViewGroup,依此類推。
明白View的這種層級關系有助于理解View的工作機制。如圖3-1所示,可以看到自定義的TestButton是一個View,它繼承了TextView,而TextView則直接繼承了View,因此不管怎么說,TestButton都是一個View,同理我們也可以構造出一個繼承自ViewGroup的控件。

圖3-1 TestButton的層次結構
3.1.2 View的位置參數
View的位置主要由它的四個頂點來決定,分別對應于View的四個屬性:top、left、right、bottom,其中top是左上角縱坐標,left是左上角橫坐標,right是右下角橫坐標,bottom是右下角縱坐標。需要注意的是,這些坐標都是相對于View的父容器來說的,因此它是一種相對坐標,View的坐標和父容器的關系如圖3-2所示。在Android中,x軸和y軸的正方向分別為右和下,這點不難理解,不僅僅是Android,大部分顯示系統都是按照這個標準來定義坐標系的。

圖3-2 View的位置坐標和父容器的關系
根據圖3-2,我們很容易得出View的寬高和坐標的關系:
width = right - left height = bottom - top
那么如何得到View的這四個參數呢?也很簡單,在View的源碼中它們對應于mLeft、mRight、mTop和mBottom這四個成員變量,獲取方式如下所示。
· Left = getLeft();
· Right = getRight();
· Top = getTop;
· Bottom = getBottom()。
從Android3.0開始,View增加了額外的幾個參數:x、y、translationX和translationY,其中x和y是View左上角的坐標,而translationX和translationY是View左上角相對于父容器的偏移量。這幾個參數也是相對于父容器的坐標,并且translationX和translationY的默認值是0,和View的四個基本的位置參數一樣,View也為它們提供了get/set方法,這幾個參數的換算關系如下所示。
x = left + translationX y = top + translationY
需要注意的是,View在平移的過程中,top和left表示的是原始左上角的位置信息,其值并不會發生改變,此時發生改變的是x、y、translationX和translationY這四個參數。
3.1.3 MotionEvent和TouchSlop
1.MotionEvent
在手指接觸屏幕后所產生的一系列事件中,典型的事件類型有如下幾種:
· ACTION_DOWN——手指剛接觸屏幕;
· ACTION_MOVE——手指在屏幕上移動;
· ACTION_UP——手機從屏幕上松開的一瞬間。
正常情況下,一次手指觸摸屏幕的行為會觸發一系列點擊事件,考慮如下幾種情況:
· 點擊屏幕后離開松開,事件序列為DOWN -> UP;
· 點擊屏幕滑動一會再松開,事件序列為DOWN -> MOVE -> … > MOVE -> UP。
上述三種情況是典型的事件序列,同時通過MotionEvent對象我們可以得到點擊事件發生的x和y坐標。為此,系統提供了兩組方法:getX/getY和getRawX/getRawY。它們的區別其實很簡單,getX/getY返回的是相對于當前View左上角的x和y坐標,而getRawX/getRawY返回的是相對于手機屏幕左上角的x和y坐標。
2.TouchSIop
TouchSlop是系統所能識別出的被認為是滑動的最小距離,換句話說,當手指在屏幕上滑動時,如果兩次滑動之間的距離小于這個常量,那么系統就不認為你是在進行滑動操作。原因很簡單:滑動的距離太短,系統不認為它是滑動。這是一個常量,和設備有關,在不同設備上這個值可能是不同的,通過如下方式即可獲取這個常量:ViewConfiguration. get(getContext()).getScaledTouchSlop()。這個常量有什么意義呢?當我們在處理滑動時,可以利用這個常量來做一些過濾,比如當兩次滑動事件的滑動距離小于這個值,我們就可以認為未達到滑動距離的臨界值,因此就可以認為它們不是滑動,這樣做可以有更好的用戶體驗。其實如果細心的話,可以在源碼中找到這個常量的定義,在frameworks/base/core/res/res/values/config.xml文件中,如下所示。這個“config_viewConfigurationTouchSlop”對應的就是這個常量的定義。
<! -- Base "touch slop" value used by ViewConfiguration as a movement threshold where scrolling should begin. --> <dimen name="config_viewConfigurationTouchSlop">8dp</dimen>
3.1.4 VelocityTracker、GestureDetector和Scroller
1.VeIocityTracker
速度追蹤,用于追蹤手指在滑動過程中的速度,包括水平和豎直方向的速度。它的使用過程很簡單,首先,在View的onTouchEvent方法中追蹤當前單擊事件的速度:
VelocityTracker velocityTracker = VelocityTracker.obtain(); velocityTracker.addMovement(event);
接著,當我們先知道當前的滑動速度時,這個時候可以采用如下方式來獲得當前的速度:
velocityTracker.computeCurrentVelocity(1000); int xVelocity = (int) velocityTracker.getXVelocity(); int yVelocity = (int) velocityTracker.getYVelocity();
在這一步中有兩點需要注意,第一點,獲取速度之前必須先計算速度,即getXVelocity和getYVelocity這兩個方法的前面必須要調用computeCurrentVelocity方法;第二點,這里的速度是指一段時間內手指所滑過的像素數,比如將時間間隔設為1000ms時,在1s內,手指在水平方向從左向右滑過100像素,那么水平速度就是100。注意速度可以為負數,當手指從右往左滑動時,水平方向速度即為負值,這個需要理解一下。速度的計算可以用如下公式來表示:
速度 =(終點位置 - 起點位置)/ 時間段
根據上面的公式再加上Android系統的坐標系,可以知道,手指逆著坐標系的正方向滑動,所產生的速度就為負值。另外,computeCurrentVelocity這個方法的參數表示的是一個時間單元或者說時間間隔,它的單位是毫秒(ms),計算速度時得到的速度就是在這個時間間隔內手指在水平或豎直方向上所滑動的像素數。針對上面的例子,如果我們通過velocityTracker.computeCurrentVelocity(100)來獲取速度,那么得到的速度就是手指在100ms內所滑過的像素數,因此水平速度就成了10像素/每100ms(這里假設滑動過程是勻速的),即水平速度為10,這點需要好好理解一下。
最后,當不需要使用它的時候,需要調用clear方法來重置并回收內存:
velocityTracker.clear(); velocityTracker.recycle();
上面就是如何使用VelocityTracker對象的全過程,看起來并不復雜。
2.GestureDetector
手勢檢測,用于輔助檢測用戶的單擊、滑動、長按、雙擊等行為。要使用GestureDetector也不復雜,參考如下過程。
首先,需要創建一個GestureDetector對象并實現OnGestureListener接口,根據需要我們還可以實現OnDoubleTapListener從而能夠監聽雙擊行為:
GestureDetector mGestureDetector = new GestureDetector(this); //解決長按屏幕后無法拖動的現象 mGestureDetector.setIsLongpressEnabled(false);
接著,接管目標View的onTouchEvent方法,在待監聽View的onTouchEvent方法中添加如下實現:
boolean consume = mGestureDetector.onTouchEvent(event); return consume;
做完了上面兩步,我們就可以有選擇地實現OnGestureListener和OnDoubleTapListener中的方法了,這兩個接口中的方法介紹如表3-1所示。
表3-1 OnGestureListener和OnDoubleTapListener中的方法介紹

表3-1里面的方法很多,但是并不是所有的方法都會被時常用到,在日常開發中,比較常用的有:onSingleTapUp(單擊)、 onFling(快速滑動)、onScroll(拖動)、onLongPress(長按)和onDoubleTap(雙擊)。另外這里要說明的是,實際開發中,可以不使用GestureDetector,完全可以自己在View的onTouchEvent方法中實現所需的監聽,這個就看個人的喜好了。這里有一個建議供讀者參考:如果只是監聽滑動相關的,建議自己在onTouchEvent中實現,如果要監聽雙擊這種行為的話,那么就使用GestureDetector。
3.ScroIIer
彈性滑動對象,用于實現View的彈性滑動。我們知道,當使用View的scrollTo/scrollBy方法來進行滑動時,其過程是瞬間完成的,這個沒有過渡效果的滑動用戶體驗不好。這個時候就可以使用Scroller來實現有過渡效果的滑動,其過程不是瞬間完成的,而是在一定的時間間隔內完成的。Scroller本身無法讓View彈性滑動,它需要和View的computeScroll方法配合使用才能共同完成這個功能。那么如何使用Scroller呢?它的典型代碼是固定的,如下所示。至于它為什么能實現彈性滑動,這個在3.2節中會進行詳細介紹。
Scroller scroller = new Scroller(mContext); // 緩慢滾動到指定位置 private void smoothScrollTo(int destX, int destY) { int scrollX = getScrollX(); int delta = destX - scrollX; // 1000ms內滑向destX,效果就是慢慢滑動 mScroller.startScroll(scrollX, 0, delta, 0, 1000); invalidate(); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } }