之前做的App是完全沒有任何手勢支持的,對於現在的程序來說,如果沒有一些手勢的支持,感覺實在是有點落後了,支持手勢的App才叫cool。於是在這次重新搭建ifood for android框架的同時下決心讓自己的App完全支持手勢。下面就來看下自己實現的一個全局滑動切換窗口的例子。
在android系統中,手勢的識別是通過 GestureDetector.OnGestureListener接口來實現的。如果要自定義手勢需要重寫這個接口裏的一些方法,廢話不多說,下面上代碼:
自定義的一個GestureLisntener:
MyGestureListener.java
public class MyGestureListener implements OnGestureListener {
static final String TAG = "MyGestureListener";
private static final int SWIPE_MAX_OFF_PATH = 100;
private static final int SWIPE_MIN_DISTANCE = 100;
private static final int SWIPE_THRESHOLD_VELOCITY = 100;
public Context context;
public MyGestureListener(Context context) {
this.context = context;
}
@Override
public boolean onDown(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}
@Override
public void onShowPress(MotionEvent e) {
// TODO Auto-generated method stub
Log.e(TAG, "onShowPress");
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
// TODO Auto-generated method stub
return false;
}
@Override
public void onLongPress(MotionEvent e) {
// TODO Auto-generated method stub
Log.e(TAG, "onLongPress");
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
return false;
if ((e1.getX() - e2.getX()) > SWIPE_MIN_DISTANCE
&& Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
Log.e(TAG, "onFling left");
} else if ((e2.getX() - e1.getX()) > SWIPE_MIN_DISTANCE
&& Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
Log.e(TAG, "onFling right");
((Activity) context).finish();
}
return true;
}
}
在這個類中的onFling()方法中從左向右滑動時實現了界面的切換,如果有更復雜的手勢支持,同樣可以在這個基類中進行添加。
接下來新建一個GestureActivity實現Gesture滑動切換界面,讓支持手勢的Activity繼承它,這樣就繼承了它的手勢支持功能,提高代碼複用。
GestureActivity.java
public class GestureActivity extends ActivityBase {
MyGestureListener listener = new MyGestureListener(this);
protected GestureDetector gestureDetector = new GestureDetector(listener);
public boolean onTouchEvent(MotionEvent event) {
if (gestureDetector.onTouchEvent(event))
return true;
else
return false;
}
}
這樣就實現了一個簡單的滑動切換頁面的框架,如果想支持更多的手勢,只需要重寫MyGestureListener的方法就可以了。
不過不要高興的太早,在一般的Activity手勢支持是正常的,可是碰到一些包含ScrollView或者ListView的Activity時,手勢就不相應了。原因是因爲這些滑動的組件本身就已經具有了手勢的支持,這樣就會產生了衝突,導致自定義的手勢沒有被識別到。google了很久,似乎也沒個具體的方法,後來看到說用dispatchTouchEvent(MotionEvent ev) 的方法,果然可以。於是在GestureActivity裏就多了這樣一個方法:
public boolean dispatchTouchEvent(MotionEvent ev) {
gestureDetector.onTouchEvent(ev);
return super.dispatchTouchEvent(ev);
}
此時再試一下,果然所有Activity都實現了自定義的手勢事件。但是爲什麼加上這個方法就可以了呢,必須要搞明白。
android中的事件類型分爲按鍵事件和屏幕觸摸事件,Touch事件是屏幕觸摸事件的基礎事件,有必要對它進行深入的瞭解。
一個最簡單的屏幕觸摸動作觸發了一系列Touch事件:ACTION_DOWN->ACTION_MOVE->ACTION_MOVE->ACTION_MOVE…->ACTION_MOVE->ACTION_UP
當屏幕中包含一個ViewGroup,而這個ViewGroup又包含一個子view,這個時候android系統如何處理Touch事件呢?到底是ViewGroup來處理Touch事件,還是子view來處理Touch事件呢?答案是:不一定。
android系統中的每個View的子類都具有下面三個和TouchEvent處理密切相關的方法:
1.public boolean dispatchTouchEvent(MotionEvent ev) 這個方法用來分發TouchEvent
2.public boolean onInterceptTouchEvent(MotionEvent ev) 這個方法用來攔截TouchEvent
3.public boolean onTouchEvent(MotionEvent ev) 這個方法用來處理TouchEvent
當TouchEvent發生時,首先Activity將TouchEvent傳遞給最頂層的View, TouchEvent最先到達最頂層 view 的 dispatchTouchEvent ,然後由 dispatchTouchEvent 方法進行分發,如果dispatchTouchEvent返回true ,則交給這個view的onTouchEvent處理,如果dispatchTouchEvent返回 false ,則交給這個 view 的 interceptTouchEvent 方法來決定是否要攔截這個事件,如果 interceptTouchEvent 返回 true ,也就是攔截掉了,則交給它的 onTouchEvent 來處理,如果 interceptTouchEvent 返回 false ,那麼就傳遞給子 view ,由子 view 的 dispatchTouchEvent 再來開始這個事件的分發。如果事件傳遞到某一層的子 view 的 onTouchEvent 上了,這個方法返回了 false ,那麼這個事件會從這個 view 往上傳遞,都是 onTouchEvent 來接收。而如果傳遞到最上面的 onTouchEvent 也返回 false 的話,這個事件就會“消失”,而且接收不到下一次事件。
看到這終於清楚了上面的疑問,dispatchTouchEvent()方法直接將觸摸事件交給了gestureDetector的觸摸事件,這樣就解決了衝突問題。