這一期主要集中學習了android的事件處理相關內容。主要參考內容為:
Android 事件處理
Android核心技術與執行個體詳解—事件處理
android的視窗機制分析------事件處理 (該文屬於android源碼分析層級,比較難,沒完全看懂)
Android開發指南-使用者介面-事件處理
目的:通過全面的分析Android的滑鼠和鍵盤事件。瞭解Android中如何接收和處理鍵盤和滑鼠事件,以及如何用代碼來產生事件。
主要學習內容:
1). 接收並處理滑鼠事件:按下、彈起、移動、雙擊、長按、滑動、滾動
2). 接收並處理按鍵事件:按下、彈起
3). 類比滑鼠/按鍵事件
1. Android事件
現代的使用者介面,都是以事件來驅動的來實現人機交換的,而Android上的一套UI控制項,無非就是派發滑鼠和鍵盤事件,然後每個控制項收到相應的事件之後,做相應的處理。如Button控制項,就只需要處理Down、move、up這幾個事件,Down的時候重繪控制項,move的時候一般也需要重繪控制項,當up的時候,重繪控制項,然後產生onClick事件。在Android中通過實現OnClickListener介面的onClick方法來實現對Button控制項的處理。
對於觸控螢幕事件(滑鼠事件)有按下有:按下、彈起、移動、雙擊、長按、滑動、滾動。按下、彈起、移動(down、move、up)是簡單的觸控螢幕事件,而雙擊、長按、滑動、滾動需要根據運動的軌跡來做識別的。在Android中有專門的類去識別,android.view.GestureDetector。
對於按鍵(keyevent),無非就是按下、彈起、長按等。
2. Android事件處理
Android手機的座標系是以左上定點為原點座標(0,0), 向右為X抽正方形,向下為Y抽正方向。
android平台的事件處理機制有兩種:一種是基於回調的,一種基於事件監聽的。當然還有更複雜的手勢判別。
3. 事件接聽程式Event Listeners
事件接聽程式是視圖View類的介面,包含一個單獨的回調方法。這些方法將在視圖中註冊的接聽程式被使用者介面操作觸發時由Android架構調用。下面這些回調方法被包含在事件接聽程式介面中:
onClick()
包含於View.OnClickListener。當使用者觸摸這個item(在觸摸模式下),或者通過瀏覽鍵或跟蹤球聚焦在這個item上,然後按下“確認”鍵或者按下跟蹤球時被調用。
onLongClick()
包含於View.OnLongClickListener。當使用者觸摸並控制住這個item(在觸摸模式下),或者通過瀏覽鍵或跟蹤球聚焦在這個item上,然後保持按下“確認”鍵或者按下跟蹤球(一秒鐘)時被調用。
onFocusChange()
包含於View.OnFocusChangeListener。當使用者使用瀏覽鍵或跟蹤球瀏覽進入或離開這個item時被調用。
onKey()
包含於View.OnKeyListener。當使用者聚焦在這個item上並按下或釋放裝置上的一個按鍵時被調用。
onTouch()
包含於View.OnTouchListener。當使用者執行的動作被當做一個觸摸事件時被調用,包括按下,釋放,或者螢幕上任何的移動手勢(在這個item的邊界內)。
onCreateContextMenu()
包含於View.OnCreateContextMenuListener。當正在建立一個操作功能表的時候被調用(作為持續的“長點擊”動作的結果)。參閱建立菜單Creating Menus章節以擷取更多資訊。
這些方法是它們相應介面的唯一“住戶”。要定義這些方法並處理你的事件,在你的活動中實現這個嵌套介面或定義它為一個匿名類。然後,傳遞你的實現的一個執行個體給各自的View.set...Listener() 方法。(比如,調用setOnClickListener()並傳遞給它你的OnClickListener實現。)
注意上面提到的onClick()回調沒有傳回值,但是一些其它事件接聽程式必須返回一個布爾值。原因和事件相關。對於其中一些,原因如下:
1) onLongClick() – 返回一個布爾值來指示你是否已經消費了這個事件而不應該再進一步處理它。也就是說,返回true 表示你已經處理了這個事件而且到此為止;返回false 表示你還沒有處理它和/或這個事件應該繼續交給其他on-click接聽程式。
2) onKey() –返回一個布爾值來指示你是否已經消費了這個事件而不應該再進一步處理它。也就是說,返回true 表示你已經處理了這個事件而且到此為止;返回false 表示你還沒有處理它和/或這個事件應該繼續交給其他on-key接聽程式。
3)onTouch() - 返回一個布爾值來指示你的接聽程式是否已經消費了這個事件。重要的是這個事件可以有多個彼此跟隨的動作。因此,如果當接收到向下動作事件時你返回false,那表明你還沒有消費這個事件而且對後續動作也不感興趣。那麼,你將不會被該事件中的其他動作調用,比如手勢或最後出現向上動作事件。
記住按鍵事件總是遞交給當前焦點所在的視圖。它們從視圖層次的頂層開始被分發,然後依次向下,直到到達恰當的目標。如果你的視圖(或者一個子視圖)當前擁有焦點,那麼你可以看到事件經由dispatchKeyEvent()方法分發。除了從你的視圖截獲按鍵事件,還有一個可選方案,你還可以在你的活動中使用onKeyDown() and onKeyUp()來接收所有的事件。
注意: Android 將首先呼叫事件處理器,其次是類定義中合適的預設處理器。這樣,從這些事情接聽程式中返回true 將停止事件向其它事件接聽程式傳播並且也會阻塞視圖中的缺事件處理器的回呼函數。因此當你返回true時確認你希望終止這個事件。
對於這些事件的具體例子,詳見: Android核心技術與執行個體詳解—事件處理
4. 事件處理器Event Handlers
如果你從視圖建立一個自訂群組件,那麼你將能夠定義一些回調方法被用作預設的事件處理器。在建立自訂群組件Building Custom Components的文檔中,你將學習到一些用作事件處理的通用回呼函數,包括:
·onKeyDown(int, KeyEvent) - 當一個新的按鍵事件發生時被調用。
·onKeyUp(int, KeyEvent) - 當一個向上鍵事件發生時被調用。
·onTrackballEvent(MotionEvent) - 當一個跟蹤球運動事件發生時被調用。
·onTouchEvent(MotionEvent) - 當一個觸控螢幕移動事件發生時調用。
·onFocusChanged(boolean, int, Rect) - 當視圖獲得或者丟失焦點時被調用。
你應該知道還有一些其它方法,並不屬於視圖類的一部分,但可以直接影響你處理事件的方式。所以,當在一個布局裡管理更複雜的事件時,考慮一下這些方法:
·Activity.dispatchTouchEvent(MotionEvent) - 這允許你的活動可以在分發給視窗之前捕獲所有的觸摸事件。
·ViewGroup.onInterceptTouchEvent(MotionEvent) - 這允許一個視圖組ViewGroup 在分發給子視圖時觀察這些事件。
·ViewParent.requestDisallowInterceptTouchEvent(boolean) - 在一個父視圖之上調用這個方法來表示它不應該通過onInterceptTouchEvent(MotionEvent)來捕獲觸摸事件。
對於這些事件的具體例子,詳見: Android核心技術與執行個體詳解—事件處理
5. 手勢識別
很多時候,一個好的使用者介面能夠吸引使用者的眼球。現在我們經常看到一些好的介面都帶有滑動、滾動等效果。但是觸控螢幕是不可能產生滾動、滑動的訊息的,需要根據其運動的軌跡用演算法去判斷實現。在Android系統中,android.view.GestureDetector來實現手勢的識別,我們只需要實現其GestureDetector.OnGestureListener介面來偵聽GestureDetector識別後的事件。我們需要在onTouchEvent,GestureDetector的onTouchEvent方法是進行軌跡識別。
import android.view.GestureDetector; import android.view.GestureDetector.OnGestureListener; public class TestEvent extends Activity { /** Called when the activity is first created. */ TextView m_eventType; int oldevent = -1; private GestureDetector gestureDetector= new GestureDetector(new OnGestureListener() { // 滑鼠按下的時候,會產生onDown。由一個ACTION_DOWN產生。 public boolean onDown(MotionEvent event) { DisplayEventType("mouse down" + " " + event.getX() + "," + event.getY()); return false; } // 使用者按下觸控螢幕、快速移動後鬆開,這個時候,你的手指運動是有加速度的。 // 由1個MotionEvent ACTION_DOWN, // 多個ACTION_MOVE, 1個ACTION_UP觸發 // e1:第1個ACTION_DOWN MotionEvent // e2:最後一個ACTION_MOVE MotionEvent // velocityX:X軸上的移動速度,像素/秒 // velocityY:Y軸上的移動速度,像素/秒 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { DisplayEventType("onFling"); return false; } // 使用者長按觸控螢幕,由多個MotionEvent ACTION_DOWN觸發 public void onLongPress(MotionEvent event) { DisplayEventType("on long pressed"); } // 滾動事件,當在觸控螢幕上迅速的移動,會產生onScroll。由ACTION_MOVE產生 // e1:第1個ACTION_DOWN MotionEvent // e2:最後一個ACTION_MOVE MotionEvent // distanceX:距離上次產生onScroll事件後,X抽移動的距離 // distanceY:距離上次產生onScroll事件後,Y抽移動的距離 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { DisplayEventType("onScroll" + " " + distanceX + "," + distanceY); return false; } //點擊了觸控螢幕,但是沒有移動和彈起的動作。onShowPress和onDown的區別在於 //onDown是,一旦觸控螢幕按下,就馬上產生onDown事件,但是onShowPress是onDown事件產生後, //一段時間內,如果沒有移動滑鼠和彈起事件,就認為是onShowPress事件。 public void onShowPress(MotionEvent event) { DisplayEventType("pressed"); } // 輕擊觸控螢幕後,彈起。如果這個過程中產生了onLongPress、onScroll和onFling事件,就不會 // 產生onSingleTapUp事件。 public boolean onSingleTapUp(MotionEvent event) { DisplayEventType("Tap up"); return false; } }); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); m_eventType = (TextView)this.findViewById(R.id.eventtype); } @Override public boolean onTouchEvent(MotionEvent event) { if(gestureDetector.onTouchEvent(event)) return true; else return false; } }
6、類比滑鼠/按鍵事件
Instrumentation發送鍵盤滑鼠事件:Instrumentation提供了豐富的以send開頭的函數介面來實現類比鍵盤滑鼠,如下所述:
sendCharacterSync(int keyCode) //用於發送指定KeyCode的按鍵
sendKeyDownUpSync(int key) //用於發送指定KeyCode的按鍵
sendPointerSync(MotionEvent event) //用於類比Touch
sendStringSync(String text) //用於發送字串
Instrumentation inst=new Instrumentation();
inst.sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(),SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 10, 10, 0));
inst.sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(),SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 10, 10, 0));