全面掌握Android中的手势Gesture
首先关于手势我们常用的应该有几种呢?
向上滑、向下滑,向左滑,向后滑,向左上滑,向左下滑,向右上滑,向右下滑,单击,双击,长按,双击,等自定义手势。
我们知道View类有个View.OnTouchListener内部接口,通过重写他的onTouch(View v, MotionEvent event)方法,我们可以处理一些touch事件,但是这个方法太过简单,如果需要处理一些复杂的手势,用这个接口就会很麻烦(因为我们要自己根据用户触摸的轨迹去判断是什么手势)。
Androidsdk给我们提供了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除了提供了手势检测之外,还允许应用程序吧用户手势添加到指定文件中,以备以后使用-------如果程序需要,当用户下次再次画出该手势时,系统将可识别该手势。
android使用GestureLibrary来代表手势库,并提供了GestureLibrary工具类
下面是4个静态方法,
static GestureLibary from File(String path): 从path代表的文件中加载手势库。
static GestureLibrary fromFile(File path):从path代表的文件中加载手势库。
static GestureLibrary fromPrivateFile(Context context,int resourceId);
从resourceId所代表的资源中加载手势库
一旦在程序中或得了 GestureLibary对象之后,该对象提供了如下方法来添加手势,识别手势
void addGesture(String entryName,Gesture gesture): 添加一个名为entryName的手势
Set<String>getGestureEntries():获取该手势库中的所有手势的名称
ArrayList<Gesture>getGestures(String entryName):获取entryName名称对应的全部手势
void removeEntry(String entryName):删除手势库中entryName对应的手势。
void removeEntry(String engtryName):删除手势库中entryName对应的手势。
void removeGesture(String entryName,Gesture gesture):删除手势库总entryName、gesture对应的手势
boolean save() :当向手势中添加手势或从中删除手势后调用该方法保存手势库。
GestureOverlayView,该组件就像一个“绘图组件”,只是用户在组件上绘制的不是图形,而是手势,为GestureOverlayView,提供了 OnGestureListenter、 OnGesturePerformedListener、 OnGesturingdListener三个监听器接口,这些是分别响应手势的开始、结束、完成、取消等事件。其中 OnGesturePerformedListener较为常用
下面看一个例子
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();
}
});
}
}
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: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>