View手勢GestureDetector使用

開發同學都知道自定義view的時候可以重寫onTouch()方法,進而擴展按下、移動、鬆開這三個函數,這也是常用的形式。但是這個方法太過簡單,如果需要處理一些複雜的手勢,用這個接口就會很麻煩。Android其實有一個手勢庫——GestureDetector,已經爲我們封裝了一些常用的手勢方法,接下來就總結一下GestureDetector的使用和總結。

GestureDetector使用:

1.實現方式

GestureDetector的實現方式根據類別分有兩種:

  1. 實現兩個接口的相關方法:OnGestureListener,OnDoubleTapListener
  2. 繼承一個內部類進而重寫其相關方法: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事件;

兩點總結:

  1. 在第二下點擊時,先觸發OnDoubleTap,然後再觸發OnDown(第二次點擊)
  2. 其次在觸發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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章