開發同學都知道自定義view的時候可以重寫onTouch()方法,進而擴展按下、移動、鬆開這三個函數,這也是常用的形式。但是這個方法太過簡單,如果需要處理一些複雜的手勢,用這個接口就會很麻煩。Android其實有一個手勢庫——GestureDetector,已經爲我們封裝了一些常用的手勢方法,接下來就總結一下GestureDetector的使用和總結。
GestureDetector使用:
1.實現方式
GestureDetector的實現方式根據類別分有兩種:
- 實現兩個接口的相關方法:OnGestureListener,OnDoubleTapListener
- 繼承一個內部類進而重寫其相關方法:SimpleOnGestureListener
其中OnGestureListener,OnDoubleTapListener,可視情況遞增實現,即必須實現OnGestureListener接口,OnDoubleTapListener可根據情況追加。而SimpleOnGestureListener其實是OnGestureListener,OnDoubleTapListener這兩個接口中所有函數的集成,它包含了這兩個接口裏所有必須要實現的函數。
2.創建GestureDetector實例
根據第一步的實現方式,創建GestureDetector實例,構造函數有下面三個,根據需要選擇:
GestureDetector gestureDetector=new GestureDetector(GestureDetector.OnGestureListener listener);
GestureDetector gestureDetector=new GestureDetector(Context context,GestureDetector.OnGestureListener listener);
GestureDetector gestureDetector=new GestureDetector(Context context,GestureDetector.SimpleOnGestureListener listener);
3.onTouch(View v, MotionEvent event)中攔截
public boolean onTouch(View v, MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
4.控件綁定
假如我們在某個View上進行手勢操作,那麼需要綁定當前View的OnTouchListener監聽。
TextView tv = (TextView)findViewById(R.id.tv);
tv.setOnTouchListener(this);
GestureDetector.OnGestureListener
基本方法
private class gesturelistener implements GestureDetector.OnGestureListener{
public boolean onDown(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}
public void onShowPress(MotionEvent e) {
// TODO Auto-generated method stub
}
public boolean onSingleTapUp(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
// TODO Auto-generated method stub
return false;
}
public void onLongPress(MotionEvent e) {
// TODO Auto-generated method stub
}
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
// TODO Auto-generated method stub
return false;
}
}
這裏總共重寫了六個方法:
1.OnDown(MotionEvent e):
用戶按下屏幕就會觸發;
2.onShowPress(MotionEvent e):
如果是按下的時間超過瞬間,而且在按下的時候沒有鬆開或者是拖動的,那麼onShowPress就會執行。
3.onLongPress(MotionEvent e):
長按觸摸屏,超過一定時長,就會觸發這個事件。觸發順序:onDown->onShowPress->onLongPress
4.onSingleTapUp(MotionEvent e):
從名子也可以看出,一次單獨的輕擊擡起操作,也就是輕擊一下屏幕,立刻擡起來,纔會有這個觸發,當然,如果除了Down以外還有其它操作,那就不再算是Single操作了,所以也就不會觸發這個事件。觸發順序:
點擊一下非常快的(不滑動)Touchup:onDown->onSingleTapUp->onSingleTapConfirmed
點擊一下稍微慢點的(不滑動)Touchup:onDown->onShowPress->onSingleTapUp->onSingleTapConfirmed
5.onFling(MotionEvent e1, MotionEvent e2, float velocityX,float velocityY):
滑屏,用戶按下觸摸屏、快速移動後鬆開,由1個MotionEvent ACTION_DOWN, 多個ACTION_MOVE, 1個ACTION_UP觸發。參數解釋:
- e1:第1個ACTION_DOWN MotionEvent
- e2:最後一個ACTION_MOVE MotionEvent
- velocityX:X軸上的移動速度,像素/秒
- velocityY:Y軸上的移動速度,像素/秒
6.onScroll(MotionEvent e1, MotionEvent e2,float distanceX, float distanceY):
在屏幕上拖動事件。無論是用手拖動view,或者是以拋的動作滾動,都會多次觸發,這個方法在ACTION_MOVE動作發生時就會觸發。
滑屏,手指觸動屏幕後,稍微滑動後立即鬆開:onDown-----》onScroll----》onScroll----》onScroll----》………----->onFling
拖動:onDown------》onScroll----》onScroll------》onFiling
可見,無論是滑屏,還是拖動,影響的只是中間OnScroll觸發的數量多少而已,最終都會觸發onFling事件!
例子
結合我們開篇所講的GestureDetector使用步驟,下邊演示一個例子:
public class MainActivity extends Activity implements OnTouchListener{
private GestureDetector mGestureDetector;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mGestureDetector = new GestureDetector(new gestureListener()); //使用派生自OnGestureListener
TextView tv = (TextView)findViewById(R.id.tv);
tv.setOnTouchListener(this);
tv.setFocusable(true);
tv.setClickable(true);
tv.setLongClickable(true);
}
/*
* 在onTouch()方法中,我們調用GestureDetector的onTouchEvent()方法,將捕捉到的MotionEvent交給GestureDetector
* 來分析是否有合適的callback函數來處理用戶的手勢
*/
public boolean onTouch(View v, MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
private class gestureListener implements GestureDetector.OnGestureListener{
// 用戶輕觸觸摸屏,由1個MotionEvent ACTION_DOWN觸發
public boolean onDown(MotionEvent e) {
Log.i("MyGesture", "onDown");
Toast.makeText(MainActivity.this, "onDown", Toast.LENGTH_SHORT).show();
return false;
}
/*
* 用戶輕觸觸摸屏,尚未鬆開或拖動,由一個1個MotionEvent ACTION_DOWN觸發
* 注意和onDown()的區別,強調的是沒有鬆開或者拖動的狀態
*
* 而onDown也是由一個MotionEventACTION_DOWN觸發的,但是他沒有任何限制,
* 也就是說當用戶點擊的時候,首先MotionEventACTION_DOWN,onDown就會執行,
* 如果在按下的瞬間沒有鬆開或者是拖動的時候onShowPress就會執行,如果是按下的時間超過瞬間
* (這塊我也不太清楚瞬間的時間差是多少,一般情況下都會執行onShowPress),拖動了,就不執行onShowPress。
*/
public void onShowPress(MotionEvent e) {
Log.i("MyGesture", "onShowPress");
Toast.makeText(MainActivity.this, "onShowPress", Toast.LENGTH_SHORT).show();
}
// 用戶(輕觸觸摸屏後)鬆開,由一個1個MotionEvent ACTION_UP觸發
///輕擊一下屏幕,立刻擡起來,纔會有這個觸發
//從名子也可以看出,一次單獨的輕擊擡起操作,當然,如果除了Down以外還有其它操作,那就不再算是Single操作了,所以這個事件 就不再響應
public boolean onSingleTapUp(MotionEvent e) {
Log.i("MyGesture", "onSingleTapUp");
Toast.makeText(MainActivity.this, "onSingleTapUp", Toast.LENGTH_SHORT).show();
return true;
}
// 用戶按下觸摸屏,並拖動,由1個MotionEvent ACTION_DOWN, 多個ACTION_MOVE觸發
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
Log.i("MyGesture22", "onScroll:"+(e2.getX()-e1.getX()) +" "+distanceX);
Toast.makeText(MainActivity.this, "onScroll", Toast.LENGTH_LONG).show();
return true;
}
// 用戶長按觸摸屏,由多個MotionEvent ACTION_DOWN觸發
public void onLongPress(MotionEvent e) {
Log.i("MyGesture", "onLongPress");
Toast.makeText(MainActivity.this, "onLongPress", Toast.LENGTH_LONG).show();
}
// 用戶按下觸摸屏、快速移動後鬆開,由1個MotionEvent ACTION_DOWN, 多個ACTION_MOVE, 1個ACTION_UP觸發
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
Log.i("MyGesture", "onFling");
Toast.makeText(MainActivity.this, "onFling", Toast.LENGTH_LONG).show();
return true;
}
};
}
GestureDetector.OnDoubleTapListener
OnDoubleTapListener接口主要用於實現雙擊手勢攔截。
基本方法
private class doubleTapListener implements GestureDetector.OnDoubleTapListener{
public boolean onSingleTapConfirmed(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}
public boolean onDoubleTap(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}
public boolean onDoubleTapEvent(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}
}
這裏總共重寫了三個方法:
1.onSingleTapConfirmed(MotionEvent e):
單擊事件。用來判定該次點擊是SingleTap而不是DoubleTap,如果連續點擊兩次就是DoubleTap手勢,如果只點擊一次,系統等待一段時間後沒有收到第二次點擊則判定該次點擊爲SingleTap而不是DoubleTap,然後觸發SingleTapConfirmed事件。
觸發順序是:OnDown->OnsingleTapUp->OnsingleTapConfirmed
關於onSingleTapConfirmed和onSingleTapUp的一點區別:
OnGestureListener有這樣的一個方法onSingleTapUp,和onSingleTapConfirmed容易混淆。二者的區別是:onSingleTapUp,只要手擡起就會執行,而對於onSingleTapConfirmed來說,如果雙擊的話,則onSingleTapConfirmed不會執行。
2.onDoubleTap(MotionEvent e):
雙擊事件
3.onDoubleTapEvent(MotionEvent e):
雙擊間隔中發生的動作。指觸發onDoubleTap以後,在雙擊之間發生的其它動作,包含down、up和move事件;
兩點總結:
- 在第二下點擊時,先觸發OnDoubleTap,然後再觸發OnDown(第二次點擊)
- 其次在觸發OnDoubleTap以後,就開始觸發onDoubleTapEvent了。
使用形式
開篇我們也提到要想使用OnDoubleTapListener必須要使用OnGestureListener,因爲創建GestureDetector實例的三個構造方法的入參根本沒有OnDoubleTapListener的形式。以下有兩種方式設置雙擊監聽:
1.新建一個類同時派生自OnGestureListener和OnDoubleTapListener
private class gestureListener implements GestureDetector.OnGestureListener,GestureDetector.OnDoubleTapListener{
}
2.使用GestureDetector::setOnDoubleTapListener()函數設置監聽
//構建GestureDetector實例
mGestureDetector = new GestureDetector(new gestureListener()); //使用派生自OnGestureListener
private class gestureListener implements GestureDetector.OnGestureListener{
}
//設置雙擊監聽器
mGestureDetector.setOnDoubleTapListener(new doubleTapListener());
private class doubleTapListener implements GestureDetector.OnDoubleTapListener{
}
可以看到,在構造函數中,除了後面要講的SimpleOnGestureListener 以外的其它兩個構造函數都必須是OnGestureListener的實例。所以要想使用OnDoubleTapListener的幾個函數,就必須先實現OnGestureListener。
GestureDetector.SimpleOnGestureListener
它與前兩個不同的是,這個是一個類,我們可以採用繼承的形式,然後重寫合適的方法,更加靈活。這個也是我們平時在使用GestureDetector最常用的一種實現形式。另外開篇也講到SimpleOnGestureListener是OnGestureListener和OnDoubleTapListener所有方法的集合。
例子
下面利用SimpleOnGestureListener類來重新實現上面的幾個效果:
public class MainActivity extends Activity implements OnTouchListener {
private GestureDetector mGestureDetector;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mGestureDetector = new GestureDetector(new simpleGestureListener());
TextView tv = (TextView)findViewById(R.id.tv);
tv.setOnTouchListener(this);
tv.setFocusable(true);
tv.setClickable(true);
tv.setLongClickable(true);
}
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
return mGestureDetector.onTouchEvent(event);
}
private class simpleGestureListener extends
GestureDetector.SimpleOnGestureListener {
/*****OnGestureListener的函數*****/
public boolean onDown(MotionEvent e) {
Log.i("MyGesture", "onDown");
Toast.makeText(MainActivity.this, "onDown", Toast.LENGTH_SHORT)
.show();
return false;
}
public void onShowPress(MotionEvent e) {
Log.i("MyGesture", "onShowPress");
Toast.makeText(MainActivity.this, "onShowPress", Toast.LENGTH_SHORT)
.show();
}
public boolean onSingleTapUp(MotionEvent e) {
Log.i("MyGesture", "onSingleTapUp");
Toast.makeText(MainActivity.this, "onSingleTapUp",
Toast.LENGTH_SHORT).show();
return true;
}
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
Log.i("MyGesture", "onScroll:" + (e2.getX() - e1.getX()) + " "
+ distanceX);
Toast.makeText(MainActivity.this, "onScroll", Toast.LENGTH_LONG)
.show();
return true;
}
public void onLongPress(MotionEvent e) {
Log.i("MyGesture", "onLongPress");
Toast.makeText(MainActivity.this, "onLongPress", Toast.LENGTH_LONG)
.show();
}
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
Log.i("MyGesture", "onFling");
Toast.makeText(MainActivity.this, "onFling", Toast.LENGTH_LONG)
.show();
return true;
}
/*****OnDoubleTapListener的函數*****/
public boolean onSingleTapConfirmed(MotionEvent e) {
Log.i("MyGesture", "onSingleTapConfirmed");
Toast.makeText(MainActivity.this, "onSingleTapConfirmed",
Toast.LENGTH_LONG).show();
return true;
}
public boolean onDoubleTap(MotionEvent e) {
Log.i("MyGesture", "onDoubleTap");
Toast.makeText(MainActivity.this, "onDoubleTap", Toast.LENGTH_LONG)
.show();
return true;
}
public boolean onDoubleTapEvent(MotionEvent e) {
Log.i("MyGesture", "onDoubleTapEvent");
Toast.makeText(MainActivity.this, "onDoubleTapEvent",
Toast.LENGTH_LONG).show();
return true;
}
}
}
OnFling應用
OnFling是用戶執行拋操作之後的回調,MOVE事件之後手鬆開(UP事件)那一瞬間的x或者y方向速度,如果達到一定數值(源碼默認是每秒50px),就是拋操作(也就是快速滑動的時候鬆手會有這個回調,因此基本上有onFling必然有onScroll)。
下邊就演示一個小例子,利用OnFling函數來識別當前用戶是在向左滑還是向右滑,功能原理:
當用戶向左滑動距離超過100px,且滑動速度超過100 px/s時,即判斷爲向左滑動;向右同理。
代碼如下:
public class MainActivity extends Activity implements OnTouchListener {
private GestureDetector mGestureDetector;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mGestureDetector = new GestureDetector(new simpleGestureListener());
TextView tv = (TextView)findViewById(R.id.tv);
tv.setOnTouchListener(this);
tv.setFocusable(true);
tv.setClickable(true);
tv.setLongClickable(true);
}
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
return mGestureDetector.onTouchEvent(event);
}
private class simpleGestureListener extends
GestureDetector.SimpleOnGestureListener {
/*****OnGestureListener的函數*****/
final int FLING_MIN_DISTANCE = 100, FLING_MIN_VELOCITY = 200;
// 觸發條件 :
// X軸的座標位移大於FLING_MIN_DISTANCE,且移動速度大於FLING_MIN_VELOCITY個像素/秒
// 參數解釋:
// e1:第1個ACTION_DOWN MotionEvent
// e2:最後一個ACTION_MOVE MotionEvent
// velocityX:X軸上的移動速度,像素/秒
// velocityY:Y軸上的移動速度,像素/秒
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
if (e1.getX() - e2.getX() > FLING_MIN_DISTANCE
&& Math.abs(velocityX) > FLING_MIN_VELOCITY) {
// Fling left
Log.i("MyGesture", "Fling left");
Toast.makeText(MainActivity.this, "Fling Left", Toast.LENGTH_SHORT).show();
} else if (e2.getX() - e1.getX() > FLING_MIN_DISTANCE
&& Math.abs(velocityX) > FLING_MIN_VELOCITY) {
// Fling right
Log.i("MyGesture", "Fling right");
Toast.makeText(MainActivity.this, "Fling Right", Toast.LENGTH_SHORT).show();
}
return true;
}
}
}
手勢相關事件的觸發時機
下邊是大神給出的一個手勢相關事件的觸發時機總結說明,再此記錄一下有待以後驗證:
從上面的分析可以看出,雖然GestureDetector能識別很多手勢,但是也是不能滿足所有的需求的,如滑動和長按之後鬆開沒有回調(這個可以重寫onTouch()捕捉UP事件實現)、多點觸控縮放手勢的實現(這個可以用ScaleGestureDetector)等。
參考
- https://blog.csdn.net/harvic880925/article/details/39520901
- https://blog.csdn.net/totond/article/details/77881180#commentBox