全面掌握Android中的手勢Gesture
首先關於手勢我們常用的應該有幾種呢?
向上滑、向下滑,向左滑,向後滑,向左上滑,向左下滑,向右上滑,向右下滑,單擊,雙擊,長按,雙擊,等自定義手勢。
我們知道View類有個View.OnTouchListener內部接口,通過重寫他的onTouch(View v, MotionEvent event)方法,我們可以處理一些touch事件,但是這個方法太過簡單,如果需要處理一些複雜的手勢,用這個接口就會很麻煩(因爲我們要自己根據用戶觸摸的軌跡去判斷是什麼手勢)。
Android sdk給我們提供了GestureDetector(Gesture:手勢Detector:識別)類,通過這個類我們可以識別很多的手勢,主要是通過他的onTouchEvent(event)方法完成了不同手勢的識別。雖然他能識別手勢,但是不同的手勢要怎麼處理,應該是提供給程序員實現的。
GestureDetector這個類對外提供了兩個接口:OnGestureListener,OnDoubleTapListener,還有一個內部類SimpleOnGestureListener。
GestureDetector.OnDoubleTapListener接口:用來通知DoubleTap事件,類似於鼠標的雙擊事件。
一、手勢交互的基本原理.
1. 在接觸屏幕瞬間,觸發一個MotionEvent事件。
2. 該事件被OnTouchListener監聽,在其onTouch()方法裏獲得該MotionEvent對象。
3. 通過GestureDetector(手勢識別器)轉發MotionEvent對象至OnGestureListener。
4. OnGestureListener獲得該對象,聽根據該對象封裝的的信息,做出合適的反饋。
二、手勢識別的核心對象 MotionEvent、GestureDetector 和 OnGestureListener。
①MotionEvent: 這個類用於封裝手勢、觸摸筆、軌跡球等等的動作事件。其內部封裝了兩個重要的屬性X和Y,這兩個屬性分別用於記錄橫軸和縱軸的座標。
②GestureDetector: 識別各種手勢。手勢識別器
③OnGestureListener: 這是一個手勢交互的監聽接口,其中提供了多個抽象方法,並根據GestureDetector的手勢識別結果調用相對應的方法。
package org.crazyit.io;
import android.app.Activity;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.widget.Toast;
/**
*
* @author qiyue
*
*/
public class GestureTest extends Activity
{
// 定義手勢檢測器實例
GestureDetector detector;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//創建手勢檢測器
detector = new GestureDetector(this,new myGestureListener());
}
class myGestureListener implements OnGestureListener{
@Override
public boolean onDown(MotionEvent e) {
Toast.makeText(GestureTest.this,"onDown", Toast.LENGTH_LONG).show();
return false;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
Toast.makeText(GestureTest.this,"onDown", Toast.LENGTH_LONG).show();
return false;
}
@Override
public void onLongPress(MotionEvent e) {
Toast.makeText(GestureTest.this,"onDown", Toast.LENGTH_LONG).show();
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
Toast.makeText(GestureTest.this,"onDown", Toast.LENGTH_LONG).show();
return false;
}
@Override
public void onShowPress(MotionEvent e) {
Toast.makeText(GestureTest.this,"onDown", Toast.LENGTH_LONG).show();
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
Toast.makeText(GestureTest.this,"onDown", Toast.LENGTH_LONG).show();
return false;
}
}
//將該Activity上的觸碰事件交給GestureDetector處理
@Override
public boolean onTouchEvent(MotionEvent me)
{
return detector.onTouchEvent(me);
}
}
-
按下(onDown): 剛剛手指接觸到觸摸屏的那一剎那,就是觸的那一下。
-
拋擲(onFling): 手指在觸摸屏上迅速移動,並鬆開的動作。
-
長按(onLongPress): 手指按在持續一段時間,並且沒有鬆開。
-
滾動(onScroll): 手指在觸摸屏上滑動。
-
按住(onShowPress): 手指按在觸摸屏上,它的時間範圍在按下起效,在長按之前。
-
擡起(onSingleTapUp):手指離開觸摸屏的那一剎那。
經驗總結:
-
任何手勢動作都會先執行一次按下(onDown)動作。
-
長按(onLongPress)動作前一定會執行一次按住(onShowPress)動作。
-
按住(onShowPress)動作和按下(onDown)動作之後都會執行一次擡起(onSingleTapUp)動作。
-
長按(onLongPress)、滾動(onScroll)和拋擲(onFling)動作之後都不會執行擡起(onSingleTapUp)動作。
最後,雙擊和三擊的識別過程,在第一次點擊down時,給Handler發送一個演示300ms的消息,如果300ms裏,發生了第二次單擊的down事件,那麼,就認爲是雙擊事件了,並移除之前發送的延時消息。如果300ms後仍沒有第二次的down消息,那麼久判定爲SingleTapConfirmed事件,三擊和此類似多了一次發送消息的過程
例子1:
實現圖片的翻頁效果
import org.sim.io.R;
import android.app.Activity;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.ViewFlipper;
public class GestureFlip extends Activity
implements OnGestureListener
{
// ViewFlipper實例
ViewFlipper flipper;
// 定義手勢檢測器實例
GestureDetector detector;
// 定義一個動畫數組,用於爲ViewFlipper指定切換動畫效果
Animation[] animations = new Animation[4];
// 定義手勢動作兩點之間的最小距離
final int FLIP_DISTANCE = 50;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 創建手勢檢測器
detector = new GestureDetector(this, this);
// 獲得ViewFlipper實例
flipper = (ViewFlipper) this.findViewById(R.id.flipper);
// 爲ViewFlipper添加5個ImageView組件
flipper.addView(addImageView(R.drawable.java));
flipper.addView(addImageView(R.drawable.ee));
flipper.addView(addImageView(R.drawable.ajax));
flipper.addView(addImageView(R.drawable.xml));
flipper.addView(addImageView(R.drawable.classic));
// 初始化Animation數組
animations[0] = AnimationUtils.loadAnimation(
this, R.anim.left_in);
animations[1] = AnimationUtils.loadAnimation(
this, R.anim.left_out);
animations[2] = AnimationUtils.loadAnimation(
this, R.anim.right_in);
animations[3] = AnimationUtils.loadAnimation(
this, R.anim.right_out);
}
// 定義添加ImageView的工具方法
private View addImageView(int resId)
{
ImageView imageView = new ImageView(this);
imageView.setImageResource(resId);
imageView.setScaleType(ImageView.ScaleType.CENTER);
return imageView;
}
@Override
public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY)
{
// 如果第一個觸點事件的X座標大於第二個觸點事件的X座標超過FLIP_DISTANCE
// 也就是手勢從右向左滑。
if (event1.getX() - event2.getX() > FLIP_DISTANCE)
{
// 爲flipper設置切換的的動畫效果
flipper.setInAnimation(animations[0]);
flipper.setOutAnimation(animations[1]);
flipper.showPrevious();
return true;
}
// 如果第二個觸點事件的X座標大於第一個觸點事件的X座標超過FLIP_DISTANCE
// 也就是手勢從右向左滑。
else if (event2.getX() - event1.getX() > FLIP_DISTANCE)
{
// 爲flipper設置切換的的動畫效果
flipper.setInAnimation(animations[2]);
flipper.setOutAnimation(animations[3]);
flipper.showNext();
return true;
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
// 將該Activity上的觸碰事件交給GestureDetector處理
return detector.onTouchEvent(event);
}
@Override
public boolean onDown(MotionEvent arg0)
{
return false;
}
@Override
public void onLongPress(MotionEvent event)
{
}
@Override
public boolean onScroll(MotionEvent event1
, MotionEvent event2, float arg2, float arg3)
{
return false;
}
@Override
public void onShowPress(MotionEvent event)
{
}
@Override
public boolean onSingleTapUp(MotionEvent event)
{
return false;
}
}
<!-- 定義ViewFlipper組件 -->
<ViewFlipper android:id="@+id/flipper"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</LinearLayout>
四、添加手勢android除了提供了手勢檢測之外,還允許應用程序吧用戶手勢添加到指定文件中,以備以後使用-------如果程序需要,當用戶下次再次畫出該手勢時,系統將可識別該手勢。
package org.sim.io;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.gesture.Gesture;
import android.gesture.GestureLibraries;
import android.gesture.GestureLibrary;
import android.gesture.GestureOverlayView;
import android.gesture.GestureOverlayView.OnGesturePerformedListener;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
/**
*
* @author qiyue
*
*/
public class AddGesture extends Activity
{
EditText editText;
GestureOverlayView gestureView;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 獲取文本編輯框
editText = (EditText) findViewById(R.id.gesture_name);
// 獲取手勢編輯視圖
gestureView = (GestureOverlayView)
findViewById(R.id.gesture);
// 設置手勢的繪製顏色
gestureView.setGestureColor(Color.RED);
// 設置手勢的繪製寬度
gestureView.setGestureStrokeWidth(4);
// 爲gesture的手勢完成事件綁定事件監聽器
gestureView.addOnGesturePerformedListener(
new OnGesturePerformedListener()
{
@Override
public void onGesturePerformed(GestureOverlayView overlay,
final Gesture gesture)
{
// 加載save.xml界面佈局代表的視圖
View saveDialog = getLayoutInflater().inflate(
R.layout.save, null);
// 獲取saveDialog裏的show組件
ImageView imageView = (ImageView) saveDialog
.findViewById(R.id.show);
// 獲取saveDialog裏的gesture_name組件
final EditText gestureName = (EditText) saveDialog
.findViewById(R.id.gesture_name);
// 根據Gesture包含的手勢創建一個位圖
Bitmap bitmap = gesture.toBitmap(128,
128, 10, 0xffff0000);
imageView.setImageBitmap(bitmap);
// 使用對話框顯示saveDialog組件
new AlertDialog.Builder(AddGesture.this)
.setView(saveDialog)
.setPositiveButton("保存", new OnClickListener()
{
@Override
public void onClick(DialogInterface dialog,
int which)
{
// 獲取指定文件對應的手勢庫
GestureLibrary gestureLib = GestureLibraries
.fromFile("/mnt/sdcard/mygestures");
// 添加手勢
gestureLib.addGesture(gestureName.getText()
.toString(), gesture);
// 保存手勢庫
gestureLib.save();
}
}).setNegativeButton("取消", null).show();
}
});
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center_horizontal"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="請在下面屏幕上繪製手勢"/>
<!-- 使用手勢繪製組件 -->
<android.gesture.GestureOverlayView
android:id="@+id/gesture"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gestureStrokeType="multiple" />
</LinearLayout>
save.xml<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="8dip"
android:text="@string/gesture_name"
/>
<!-- 定義一個文本框來讓用戶輸入手勢名 -->
<EditText
android:id="@+id/gesture_name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
<!-- 定義一個圖片框來顯示手勢 -->
<ImageView
android:id="@+id/show"
android:layout_width="128dp"
android:layout_height="128dp"
android:layout_marginTop="10dp" />
</LinearLayout>
識別的代碼
package org.crazyit.io;
import java.util.ArrayList;
import android.app.Activity;
import android.app.AlertDialog;
import android.gesture.Gesture;
import android.gesture.GestureLibraries;
import android.gesture.GestureLibrary;
import android.gesture.GestureOverlayView;
import android.gesture.Prediction;
import android.gesture.GestureOverlayView.OnGesturePerformedListener;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.Toast;
/**
*
* @author qiyue
*
*/
public class RecogniseGesture extends Activity
{
// 定義手勢編輯組件
GestureOverlayView gestureView;
// 記錄手機上已有的手勢庫
GestureLibrary gestureLibrary;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 讀取上一個程序所創建的手勢庫
gestureLibrary = GestureLibraries
.fromFile("/mnt/sdcard/mygestures");
if (gestureLibrary.load())
{
Toast.makeText(RecogniseGesture.this, "手勢文件裝載成功!",
Toast.LENGTH_LONG).show();
}
else
{
Toast.makeText(RecogniseGesture.this, "手勢文件裝載失敗!",
Toast.LENGTH_LONG).show();
}
// 獲取手勢編輯組件
gestureView = (GestureOverlayView) findViewById(R.id.gesture);
// 爲手勢編輯組件綁定事件監聽器
gestureView.addOnGesturePerformedListener(
new OnGesturePerformedListener()
{
@Override
public void onGesturePerformed(GestureOverlayView
overlay, Gesture gesture)
{
// 識別用戶剛剛所繪製的手勢
ArrayList<Prediction> predictions = gestureLibrary
.recognize(gesture);
ArrayList<String> result = new ArrayList<String>();
// 遍歷所有找到的Prediction對象
for (Prediction pred : predictions)
{
// 只有相似度大於2.0的手勢纔會被輸出
if (pred.score > 2.0)
{
result.add("與手勢【" + pred.name + "】相似度爲"
+ pred.score);
}
}
if (result.size() > 0)
{
ArrayAdapter<Object> adapter = new
ArrayAdapter<Object>(RecogniseGesture.this,
android.R.layout.simple_dropdown_item_1line
, result.toArray());
// 使用一個帶List的對話框來顯示所有匹配的手勢
new AlertDialog.Builder(RecogniseGesture.this)
.setAdapter(adapter, null)
.setPositiveButton("確定", null).show();
}
else
{
Toast.makeText(RecogniseGesture.this
, "無法找到能匹配的手勢!",
Toast.LENGTH_LONG).show();
}
}
});
}
}
main.xml<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<!-- 使用手勢編輯組件 -->
<android.gesture.GestureOverlayView
android:id="@+id/gesture"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gestureStrokeType="multiple" />
</LinearLayout>
result.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ListView
android:id="@+id/show"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</LinearLayout>