在使用自定義視圖的時候,對觸屏事件的處理是必不可少的,有能力的可以自己寫代碼處理,這樣更加的靈活。如果不想這么麻煩,Android提供了一個手勢監聽類GestureDetector,可以供我們使用。GestureDetector使用很方便,提供了單擊,雙擊,長按等操作的處理,但是一般的定義界面都比較復雜,還用很多需要注意的地方,在這兒總結一下GestureDetector的使用。
首先新建一個空白的工程,主界面的layout中只需要添加一個按鈕就行
1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:paddingBottom="@dimen/activity_vertical_margin" 6 android:paddingLeft="@dimen/activity_horizontal_margin" 7 android:paddingRight="@dimen/activity_horizontal_margin" 8 android:paddingTop="@dimen/activity_vertical_margin" 9 tools:context=".MainActivity" > 10 11 <Button 12 android:id="@+id/btn_textgesture" 13 android:layout_width="fill_parent" 14 android:layout_height="fill_parent" 15 android:text="@string/app_name" /> 16 17 </RelativeLayout>
由於要測試的觸屏事件,所有這個按鈕比較大,主界面為如下效果:
首先介紹一下觸屏事件處理的基本思路。觸屏一般有三個基本事件,down按下,move移動,up離開,通過對這三個基本事件的監聽,判斷用戶執行了何種操作。一個標准的觸屏操作一般都是一系列基本事件的組合,在Android的框架中,通過onTouch()函數可以獲取基本的觸屏事件,而像onClick這樣的函數,已經是一系列基本事件的組合。
比如,發生了Down事件,在up事件之前沒有發生move事件,或者move的范圍很小,並且down事件和up事件的間隔很短,這就是一個click或者singelTap事件,
對比實體鍵盤按鍵的事件,實體鍵盤是在down事件發生后進行操作,而觸屏事件一般是up事件發生后進行操作。
下面是activity的代碼
1 package com.example.testgesture; 2 3 import android.app.Activity; 4 import android.os.Bundle; 5 import android.util.Log; 6 import android.view.GestureDetector; 7 import android.view.GestureDetector.SimpleOnGestureListener; 8 import android.view.MotionEvent; 9 import android.view.View; 10 import android.view.View.OnTouchListener; 11 import android.widget.Button; 12 13 public class MainActivity extends Activity { 14 15 private Button mButton; 16 private GestureDetector mGestureDetector; 17 18 @Override 19 protected void onCreate(Bundle savedInstanceState) { 20 super.onCreate(savedInstanceState); 21 setContentView(R.layout.activity_main); 22 23 mGestureDetector = new GestureDetector(this, new MyOnGestureListener()); 24 25 mButton = (Button) findViewById(R.id.btn_textgesture); 26 mButton.setOnTouchListener(new OnTouchListener() { 27 28 @Override 29 public boolean onTouch(View v, MotionEvent event) { 30 Log.i(getClass().getName(), "onTouch-----" + getActionName(event.getAction())); 31 mGestureDetector.onTouchEvent(event); 32 // 一定要返回true,不然獲取不到完整的事件 33 return true; 34 } 35 }); 36 } 37 38 private String getActionName(int action) { 39 String name = ""; 40 switch (action) { 41 case MotionEvent.ACTION_DOWN: { 42 name = "ACTION_DOWN"; 43 break; 44 } 45 case MotionEvent.ACTION_MOVE: { 46 name = "ACTION_MOVE"; 47 break; 48 } 49 case MotionEvent.ACTION_UP: { 50 name = "ACTION_UP"; 51 break; 52 } 53 default: 54 break; 55 } 56 return name; 57 } 58 59 class MyOnGestureListener extends SimpleOnGestureListener { 60 @Override 61 public boolean onSingleTapUp(MotionEvent e) { 62 Log.i(getClass().getName(), "onSingleTapUp-----" + getActionName(e.getAction())); 63 return false; 64 } 65 66 @Override 67 public void onLongPress(MotionEvent e) { 68 Log.i(getClass().getName(), "onLongPress-----" + getActionName(e.getAction())); 69 } 70 71 @Override 72 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 73 Log.i(getClass().getName(), 74 "onScroll-----" + getActionName(e2.getAction()) + ",(" + e1.getX() + "," + e1.getY() + ") ,(" 75 + e2.getX() + "," + e2.getY() + ")"); 76 return false; 77 } 78 79 @Override 80 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 81 Log.i(getClass().getName(), 82 "onFling-----" + getActionName(e2.getAction()) + ",(" + e1.getX() + "," + e1.getY() + ") ,(" 83 + e2.getX() + "," + e2.getY() + ")"); 84 return false; 85 } 86 87 @Override 88 public void onShowPress(MotionEvent e) { 89 Log.i(getClass().getName(), "onShowPress-----" + getActionName(e.getAction())); 90 } 91 92 @Override 93 public boolean onDown(MotionEvent e) { 94 Log.i(getClass().getName(), "onDown-----" + getActionName(e.getAction())); 95 return false; 96 } 97 98 @Override 99 public boolean onDoubleTap(MotionEvent e) { 100 Log.i(getClass().getName(), "onDoubleTap-----" + getActionName(e.getAction())); 101 return false; 102 } 103 104 @Override 105 public boolean onDoubleTapEvent(MotionEvent e) { 106 Log.i(getClass().getName(), "onDoubleTapEvent-----" + getActionName(e.getAction())); 107 return false; 108 } 109 110 @Override 111 public boolean onSingleTapConfirmed(MotionEvent e) { 112 Log.i(getClass().getName(), "onSingleTapConfirmed-----" + getActionName(e.getAction())); 113 return false; 114 } 115 } 116 }
首先是聲明一個GestureDetector,然后重寫Button的onTouch函數,將觸屏事件交給GestureDetector處理。
首先做一個對按鈕做一個單擊
onSingleTapUp被調用,說明發生了單擊事件,onSingleTapConfirmed被調用,說明確認發生了一個單擊事件,不是雙擊的事件。需要注意的是onSingleTapUp已經是一click事件,onSingleTapUp觸發的時候是ACTION_UP事件。onSingleTapConfirmed是在用戶手指離開屏幕后觸發,所有up並不是所有觸屏事件的結束。
做一個雙擊的操作
首先發生了一個onSingleTapUp,說明完成了一次單擊事件,然后發生了onDoubleTap,至此,一次雙擊事件已經完成。我們可以看到,onDoubleTap發生的時候是ACTION_DOWN事件,也就是說雙擊事件是第二次按下屏幕的時候觸發,而不是第二次離開屏幕的時候觸發,在onDoubleTap發生之后,就可以在onDoubleTapEvent中監聽到雙擊事件發生后從按下到彈起的所有觸屏事件。onDoubleTap發生后不會觸發onSingleTapUp和onSingleTapConfirmed。
做一個長按的操作
onLongPress實在ACTION_DOWN時發生,onLongPress發生后在up之前不會用其他事件觸發,可以在onShowPress處理狀態的改變,比如按鈕的按下狀態。
做一個滑動操作
onScroll事件是拖動,onFling是拋。結合log來了解一下。首先是ACTION_DOWN,之后多次ACTION_MOVE,移動超過一定距離,觸發了onScroll,如果onScroll被觸發,在up之前是不會有長按,單擊,雙擊等事件的。看一下onScroll的參數
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
e1為第一次按下的事件,和onDown事件里面的一樣,e2為當前的事件,distanceX為本次onScroll移動的X軸距離,distanceY為移動的Y軸距離,移動的距離是相對於上一次onScroll事件的移動距離,而不是當前點和按下點的距離。
這次滑動最后觸發了onFling事件,但是onFling事件的觸發不是一定的,onFling是在ACTION_UP觸發,平時列表在離開屏幕是繼續滾動,就是通過這種方式觸發。
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
onFling的前兩個參數和onScroll相同,e2為用戶拖動完離開屏幕時的點。veloctiyX,velocitY為離開屏幕時的初始速度,以這兩個速度為初始速度做勻減速運動,就是現在拖動列表和拖動圖片的各種緩存滾動的效果。
函數的返回值
除了onLongPress,這些函數都是有返回值的,
1 mButton.setOnTouchListener(new OnTouchListener() { 2 3 @Override 4 public boolean onTouch(View v, MotionEvent event) { 5 Log.i(getClass().getName(), "onTouch-----" + getActionName(event.getAction())); 6 mGestureDetector.onTouchEvent(event); 7 // 一定要返回true,不然獲取不到完整的事件 8 return true; 9 } 10 });
這些返回值會通過 mGestureDetector.onTouchEvent(event); 傳遞給onTouch。
最后總結一下
GestureDetector結合SimpleOnGestureListener可以很方便的獲取到單擊,雙擊,長按等事件,但是對這些事件的處理不是簡單的在對應的函數里做一些操作就可以的,復雜的自定義視圖還是要在onTouch里面進行判斷個個控件的焦點狀態,而且GestureDetector也不是萬能的,你如果要處理長按之后的移動,就要費一番功夫了,以為GestureDetector在長按發生后是不會在有onScroll的,你只能通過onTouch里面的ACTION_MOVE處理。