一、GestureDetector手勢檢測
概述:
當用戶觸摸屏幕的時候,會產生許多手勢,如 down、up 、scroll 、fling等。
GestureDetector(手勢檢測)類,通過這個類可以識別很多手勢。在識別出於勢之後,具體的事務處理則交 由程序員自己來實現。
GestureDetector.OnGestureListener接口:
1.基本講解
如果我們寫一個類並繼承自OnGestureListener,則會提示有幾個必須重寫的函數。
private class gesturelistener implements GestureDetector.OnGestureListener {
@Override
public boolean onDown(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}
@Override
public void onShowPress(MotionEvent e) {
// TODO Auto-generated method stub
}
@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
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
// TODO Auto-generated method stub
return false;
}
}
這裏重寫 了 6 個函數,這些函數在什麼情況下才會被觸發呢?
• onDown(MotionEvent e):用戶按下屏幕就會觸發該函數。
• onShowPress(MotionEvent e):如果按下的時間超過瞬間,而且在按下的時候沒有鬆開或者是拖動的,該函數就會被觸發。
• onLongPress(MotionEvent e):長接觸摸屏,超過一定時長,就會觸發這個函數。
觸發順序:onDown→onShowPress→onLongPress
• onSingleTapUp(MotionEvent e): 從名字中也可 以看出,一次單獨的輕擊擡起操作,也就是輕擊一下屏幕,立刻擡起來,纔會觸發這個函數。當然,如果除down以外還有其他操作,就不再算是單獨操作了,也就不會觸發這個函數了。
單擊一下非常快的(不滑動)Touchup,觸發順序:onDown→onSingleTapUp→onSingleTapConfirmed
單擊一下稍微慢一點的(不滑動) Touchup,觸發順序 :onDown→onShowPress→onSingleTapUp→onSingleTapConfirmed
• onFling(MotionEvent el, MotionEvent e2, float velocityX, float velocityY):滑屏,用戶按下觸摸屏、快速移動後鬆開,由一個MotionEvent ACTION_DOWN、多個ACTION_MOVE、一個ACTION_UP觸發。
• onScroll(MotionEvent el, MotionEvent e2,float distanceX, float distanceY):在屏幕上拖動事件。無論是用手拖動View,還是以拋的動作滾動,都會多次觸發這個函數,在ACTION_MOVE動作發生時就會觸發該函數。
滑屏,即手指觸動屏幕後,稍微滑動後立即鬆開,觸發順序:onDown→onScroll→onScroll→onScroll→...→onFling
拖動,觸發順序:onDown→onScroll→onScroll→onFling
可見,無論是滑屏還是拖動,影響的只是中間onScroll被觸發的數量而己,最終都會觸發onFling事件。
2.示例
要使用 GestureDetector,有四步要走 。
(1) 創建 OnGestureListener()監聽函數。
GestureDetector.OnGestureListener listener = new GestureDetector.OnGestureListener() {
};
或構造類:
private class gestureListener implements GestureDetector.OnGestureListener {
}
(2) 創建GestureDetector實例mGestureDetector。
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) 綁定控件。
TextView tv = (TextView) findViewByid(R.id.tv);
tv.setOnTouchListener(this);
示例:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.gesturedetectorinterface.MainActivity">
<TextView
android:id="@+id/tv"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layoutmargin="50dip"
android:background="#ff00ff"
android:text="@string/hello_world"/>
</RelativeLayout>
public class MainActivity extends Activity implements View.OnTouchListener {
private GestureDetector mGestureDetector;
@Override
protected void onCreate(Bundle savedinstanceState) {
super.onCreate(savedinstanceState);
setContentView(R.layout.activity_main);
TextView tv = (TextView) findViewById(R.id.tv);
tv.setOnTouchListener(this);
tv.setFocusable(true);
tv.setClickable(true);
tv.setLongClickable(true);
mGestureDetector = new GestureDetector(new MyGestureListener());
}
@Override
public boolean onTouch(View v, MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
private class MyGestureListener implements GestureDetector.OnGestureListener {
@Override
public boolean onDown(MotionEvent e) {
Log.d("TAG", "onDown");
return false;
}
@Override
public void onShowPress(MotionEvent e) {
Log.d("TAG", "onShowPress");
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
Log.d("TAG", "onSingleTapUp");
return true;
}
@Override
public boolean onScroll(MotionEvent el, MotionEvent e2, float distanceX, float distanceY) {
Log.d("TAG2", "onScroll:" + (e2.getX() - el.getX()) + "" + distanceX);
return true;
}
@Override
public void onLongPress(MotionEvent e) {
Log.d("TAG", "onLongPress");
}
@Override
public boolean onFling(MotionEvent el, MotionEvent e2, float velocityX, float velocityY) {
Log.d("TAG", "onFling");
return true;
}
}
}
GestureDetector.OnDoubleTapListener接口:
1.構建
方法一:新建一個類,同時派生OnGestureListener和OnDoubleTapListener
private class gestureListener implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {
}
方法二:使用GestureDetector.setOnDoubleTapListener()函數設置雙擊監聽
// 構建 GestureDetector 實例
mGestureDetector = new GestureDetector(new MyGestureListener());
private class MyGestureListener implements GestureDetector.OnGestureListener {
}
// 設置雙擊監聽
mGestureDetector.setOnDoubleTapListener(new MyDoubleTapListener());
private class MyDoubleTapListener implements GestureDetector.OnDoubleTapListener {
}
可以看到,無論是在方法一還是在方法二中,都需要派生自GestureDetector.OnGestureListener。
2.函數講解
先來看一下OnDoubleTapListener接口必須重寫的三個函數。
private class doubleTapListener implements GestureDetector.OnDoubleTapListener {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}
}
• onSingleTapConfirmed(MotionEvente):單擊事件,用來判定該次單擊是SingleTap,而不是DoubleTap。如果連續單擊兩次,就是DoubleTap手勢;如果只單擊一次,系統等待一段時間後沒有收到第二次單擊,則判定該次單擊爲SingleTap,而不是DoubleTap,然後觸發SingleTapConfinned事件。觸發順序:onDown→onSingleTapUp→onSingleTapConfinned。OnGestureListener有這樣一個函數onSingleTapUp(),它和onSingleTapConfinned()函數容易混淆。二者的區別是:對於onSingleTapUp()函數來說,只要手擡起就會被觸發;而對於onSingleTapConfinned0函數來說,如果雙擊,則該函數就不會被觸發。
• onDoubleTap(MotionEvente):雙擊事件。
• onDoubleTapEvent(MotionEvente):雙擊間隔中發生的動作。指在觸發onDoubleTap以後,在雙擊之間發生的其他動作,包含down、up和move事件。
GestureDetector.SimpleOnGestureListener類:
SimpleOnGestureListener類與OnG巳stureListener和OnDoubleTapListener接口的不同之處在於:
(1)這是一個類,在它的基礎上新建類,要用巳xtends派生,而不能用implements繼承。
(2)OnGestureListener和OnDoubleTapListen巳r接口裏的函數都是被強制重寫的,即使用不到也要重寫出來一個空函數:而在SimpleOnGestureListener類的實例或派生類中不必如此,可以根據情況,用到哪個函數就重寫哪個函數,因爲SimpleOnGestureListener類本身已經實現了這兩個接口中的所有函數,只是裏面全是空的而已。
SimpleOnGestureListener類內部的函數與OnGestureListener和OnDoubleTapListener接口中的函數是完全相同的。唯一不同的就是 SimpleOnGestureListener 類內部的函數不必被強制全部重寫,用到哪個函數就重寫哪個函 數;而OnGestureListener和OnDoubleTapListener是接口,它們內部的函數是必須被重寫的。
onFling()函數的應用——識別是向左滑還是向右滑
boolean onFling(MotionEvent el, MotionEvent e2, float velocityX, float velocityY)
• el:第一個ACTION_DOWN MotionEvent
• e2:最後一個ACTION_MOVE MotionEvent
• velocityX:X軸上的移動速度,單位爲像素/秒
• velocityY:Y軸上的移動速度,單位爲像素/秒
實現的功能:當用戶向左滑動距離超過100像素,且滑動速度超過100像素/秒時,即判斷爲向左滑動:向右同理。核心代碼是在onFling()函數中判斷當前的滑動方向及滑動速度是不是達到指定值。代碼如下:
private class simpleGestureListener extends GestureDetector.SimpleOnGestureListener {
/* ******OnGestureListener的函數****** */
final int FLING_MIN_DISTANCE = 100, FLING_MIN_VELOCITY = 200;
// 觸發條件:
// X軸的座標位移大於FLING_MIN_DISTANCE,且移動速度大於FLING_MIN_VELOCITY像素/秒
public boolean onFling(MotionEvent el, MotionEvent e2, float velocityX, float velocityY) {
if (el.getX() - e2.getX() > FLING_MIN_DISTANCE && Math.abs(velocityX) > FLING_MIN_VELOCITY) {// 向左滑
Log.d("TAG", "Fling left");
} else if (e2.getX() - el.getX () > FLING_MIN_DISTANCE && Math.abs(velocityX) > FLING_MIN_VELOCITY) {// 向右滑
Log.i("TAG", "Fling right" );
}
return true;
}
}
二、Window與WindowManager
Window表示窗口,在某些特殊的時候,比如需要在桌面或者鎖屏上顯示一些類似懸浮窗的效果,就需要用到Window。Android中所有的視圖都是通過Window來呈現的,不管是Activity、Dialog還是Toast,它們的視圖實際上都是附加在Window上的。而WindowManager則提供了對這些Window的統一管理功能。
Window與WindowManager的聯繫:
使用WindowManager來添加一個Window:
WindowManager manager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(width, height, type, flags, format);
manager.addView(btn, layoutParams);
◆ flags:
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
表示此Window不需要獲取焦點,不接收各種輸入事件,此標記會同時啓用FLAG_NOT_TOUCH_MODAL,
最終事件會直接傳遞給下層具有焦點的Window。
public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;
自己Window區域內的事件自己處理;自己Window區域外的事件傳遞給底層Window處理。
一般這個選項會默認開啓,否則其他Window無法接收事件。
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
可以讓此Window顯示在鎖屏上。
◆ type:
type參數也是int類型的,表示Window的類型。
Window有三種類型:應用Window、子Window、系統Window。
應用Window:對應着一個Activity。
子Window:不能獨立存在,它需要附屬在特定的父Window中,比如Dialog就是一個子Window。
系統Window:需要聲明權限才能創建,比如Toast和系統狀態欄都是系統Window。
Window是分層的,層級大的Window會覆蓋在層級小的Window上面。
● 應用Window層級範圍:1~99
● 子Window層級範圍:1000~1999
● 系統Window層級範圍:2000~2999
type參數就對應以上這些數字。如果想讓Window置於頂層,則採用較大的層級即可。
如果是系統類型的Window,則需要在AndroidManifest.xml中配置如下權限聲明:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
WindowManager提供的功能很簡單,常用的只有三個方法,即添加View、更新View和刪除View。這三個方法定義在ViewManager中,而 WindowManager繼承自ViewManager。
public interface WindowManager extends ViewManager {
}
public interface ViewManager {
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
WindowManager操作Window的過程更像在操作Window中的View。
示例:騰訊手機管家懸浮窗的小火箭效果
<?xml version ="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match parent">
<Button
android:id="@+id/add_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="add view"/>
<Button
android:id="@+id/rmv_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="remove view"/>
</LinearLayout>
public class MainActivity extends Activity implements View.OnTouchListener, View.OnClickListener {
private Button mCreateWndBtn, mRmvWndBtn;
private ImageView mImageView;
private WindowManager.LayoutParams mLayoutParams;
private WindowManager mWindowManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Intent myIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivityForResult(myIntent, 100);
} else {
initView();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 100) {
initView();
}
}
private void initView() {
mCreateWndBtn = (Button) findViewById(R.id.add_btn);
mRmvWndBtn = (Button) findViewById(R.id.rmv_btn);
mCreateWndBtn.setOnClickListener(this);
mRmvWndBtn.setOnClickListener(this);
mWindowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE: {
mLayoutParams.x = rawX;
mLayoutParams.y = rawY;
mWindowManager.updateViewLayout(mImageView, mLayoutParams);
break;
}
}
return false;// return false:會執行onClick方法
}
@Override
protected void onDestroy() {
try {
mWindowManager.removeView(mImageView);
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
super.onDestroy();
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.add_btn) {
mImageView = new ImageView(this);
mImageView.setBackgroundResource(R.mipmap.ic_launcher);
mLayoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, 2099,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
,
PixelFormat.TRANSPARENT);
mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
mLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
mLayoutParams.x = 0;
mLayoutParams.y = 300;
mImageView.setOnTouchListener(this);
mWindowManager.addView(mImageView, mLayoutParams);
} else if (v.getId() == R.id.rmv_btn) {
mWindowManager.removeViewImmediate(mImageView);
}
}
}
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
需要注意的是,在 SDK API>=23(Android6.0)時,不僅需要在AndroidManifest.xml中添加權限申請,也需要在代碼中動態申請。