Android開發藝術探索知識回顧——第3章 View的事件體系

 第3章 View的事件體系

本章將介紹 Android 中十分重要的一個概念:View,雖然說View不屬於四大組件,但是它的作用堪比四大組件,甚至比 ReceiverProvider 的重要性都要大。在 Android 開發中,Activity 承擔這可視化的功能,同時 Android 系統提供了很多基礎控件,常見的有Button, TextView, CheckBox等。

很多時候僅僅使用系統提供的控件是不能滿足需求的,因此我們就需要能夠根據需求進行新控件的定義,而控件的自定義就需要對 Android 的 View 體系有深入的理解,只有這樣才能寫出完美的自定義控件

同時 Android 手機屬於移動設備,移動設備的一個特點就是用戶可以直接通過屏幕來進行一系列操作,一個典型的場景就是屏幕的滑動,用戶可以通過滑動來切換到不同的界面。很多情況下我們的應用都需要支持滑動操作,當處於不同層級的 View 都可以響應用戶的滑動操作時,就會帶來一個問題,那就是滑動衝突。

如何解決滑動衝突呢?這對於初學者來說的確是個頭疼的問題,其實解決滑動衝突本不難,它需要讀者對 View 的事件分發機制有一定的瞭解,在這個基礎上,我們就可以利於這個特性從而得出滑動衝突的解決方法。上述這些內容就是本章所要介紹的內容,同時,View 的內部工作原理和自定義 View 相關的知識會在第4章進行介紹。

 

3.1 View基礎知識

本節主要介紹 View 的一些基礎知識,從而爲更好地介紹後續的內容做鋪墊。主要介紹的內容有:View 的位置參數、MotionEvent TouchSlop 對象、VelocityTrackerGestureDetector 和 Scroller 對象,通過對這些基礎知識的介紹,可以方便讀者理解更復雜的內容。類似的基礎概念還有不少,但是本節所介紹的都是一些比較常用的,其他不常用的基礎概念讀者可以自行了解。

3.1.1 什麼是View

在介紹 View 的基礎知識之前,我們首先要知道到底什麼是 View。View 是 Android 中所有控件的基類,不管是簡單的 Button和 TextView還是複雜的 RelativeLayout 和 ListView,它們的共同基類都是 View。所以說,View 是一種界面層的控件的一種抽象,它代表了一 個控件。

除了 View,還有ViewGroup,從名字來看,它可以被翻譯爲控件組,言外之意是 ViewGroup 內部包含了許多個控件,即一組 ViewAndroid 的設計中,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.2 View的位置參數

View 的位置主要由它的四個頂點來決定,分別對應於 View 的四個屬性:topleftright、bottom,其中 top 是左上角縱座標,left是左上角橫座標,right是右上角橫座標,bottom是 右下角縱座標。

需要注意的是,這些座標都是相對於 View 的父容器來說的,因此它是一種相對座標,View 的座標和父容器的關係如圖3-2所示。在Android中,x軸和y軸的正方向分別爲右和下,這點不難理解,不僅僅是Android,大部分顯示系統都是按照這個標準來定義座標系的。

 

根據圖3-2,我們很容易得出View的寬高和座標的關係:

width = right - left 
height =bottom - top

那麼如何得到 View 的這四個參數呢?也很簡單,在 View 的源碼中它們對應於mLeftmRightmTopmBottom 這四個成員變量,獲取方式如下所示。

• Left = getLeft()

• Right = getRight()

• Top = getTop()

• Bottom = getBottom();

Android3.0 開始,View 增加了額外的幾個參數:xy、translationX translationY,其中 x 和 y 是 View 左上角的座標,而translationX 和 translationY 是 View 左上角相對於父容器的偏移量。這幾個參數也是相對於父容器的座標,並且 translationX translationY 的默認值是 0,和 View 的四個基本的位置參數一樣,View也爲它們提供了 get/set 方法,這幾個參數的換算關係如下所示。

x = left + translationX
y = top + translationY

需要注意的是:View在平移的過程中,top 和 left 表示的是原始左上角的位置信息,其值並不會發生改變,此時發生改變的是xytranslationX 和 translationY 這四個參數。

 

3.1.3 MotionEvent TouchSlop

1、MotionEvent

在手指接觸屏幕後所產生的一系列事件中,典型的事件類型有如下幾種:

  • ACTION_DOWN——手指剛接觸屏幕;
  • ACTION_MOVE——手指在屏幕上移動;
  • ACTION_UP——手機從屏幕上鬆開的一瞬間。

正常情況下,一次手指觸摸屏幕的行爲會觸發一系列點擊事件,考慮如下幾種情況:

  • 點擊屏幕後離開鬆開,事件序列爲DOWN -> UP
  • 點擊屏幕滑動一會再鬆開,事件序列爲DOWN -> MOVE -> ...> MOVE -> UP

上述三種情況是典型的事件序列,同時通過 MotionEvent 對象我們可以得到點擊事件發生的 和 座標。爲此,系統提供了兩組方法:getX/getY 和 getRawX/getRawY它們的區別其實很簡單,getX/getY 返回的是相對於當前 View 左上角的 和 座標,而 getRawX/getRawY 返回的是相對於手機屏幕左上角的 和 座標。

2、TouchSlop

TouchSlop 是系統所能識別出的被認爲是滑動的最小距離,換句話說,當手指在屏幕上滑動時,如果兩次滑動之間的距離小於這個常量,那麼系統就不認爲你是在進行滑動操作。 原因很簡單:滑動的距離太短,系統不認爲它是滑動。這是一個常量,和設備有關,在不同設備上這個值可能是不同的,通過如下方式即可獲取這個常量:Viewconfiguration.get(getContext()).getScaledTouchSlop();

這個常量有什麼意義呢?當我們在處理滑動時,可以利用這個常量來做一些過濾,比如當兩次滑動事件的滑動距離小於這個值,我們就可以認爲未達到滑動距離的臨界值,因此就可以認爲它們不是滑動,這樣做可以有更好的用戶體驗。

其實如果細心的話,可以在源碼中找到這個常量的定義,在 frameworks/base/core/ res/res/values/config.xm文件中,如下所示。這個"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、VelocityTracker

速度追蹤,用於追蹤手指在滑動過程中的速度,包括水平和豎直方向的速度。它的使用過程很簡單,首先,在 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時,在 1內,手指在水平方向從左向右滑過100像素,那麼水平速度就是100。注意速度可以爲負數,當手指從右往左滑動時,水平方向速度即爲負值,這個需要理解一下。速度的計算可以用如下公式來表示:

速度 = (終點位置-起點位置) / 時間段

根據上面的公式再加上 Android 系統的座標系,可以知道,手指逆着座標系的正方向滑動,所產生的速度就爲負值。另外,computeCurrentVelocity 這個方法的參數表示的是一個時間單元或者說時間間隔,它的單位是毫秒(ms),計算速度時得到的速度就是在這個時間間隔內手指在水平或豎直方向上所滑動的像素數。

針對上面的例子,如果我們通過 velocityTracker.computeCurrentVelocity(100) 來獲取速度,那麼得到的速度就是手指在100ms 內所滑過的像素數,因此水平速度就成了 10像素/每100ms  (這裏假設滑動過程是勻速的),

即水平速度爲10,這點需要好好理解一下。

最後,當不需要使用它的時候,需要調用 clear 方法來重置並回收內存:

velocityTracker.clear ();
velocityTracker.recycle();

上面就是如何使用 VelocityTracker 對象的全過程,看起來並不複雜。

 

2、GestureDetector

手勢檢測,用於輔助檢測用戶的單擊、滑動、長按、雙擊等行爲。要使用 GestureDetector 也不復雜,參考如下過程。

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章