本系列文章均为A2BGeek原创,转载务必在明显处注明:
转载自A2BGeek的【Android每周专题】系列,原文链接:
http://blog.csdn.net/benbmw2008/article/details/11367631
这篇专题来研究一下Android的触摸屏手势Gesture,Android的手势有两种,一种是View和Activity的(基于触摸屏事件,所以Activity也能添加手势),一种是手写输入法或者一些手机浏览器的快捷手势那样的带笔迹的手势识别。这一点从API文档中就能体现出来:分别有android.view.GestureDetector和android.gesture.Gesture。
我们先来介绍View和Activity的手势,再介绍输入法手势识别。
View和Activity的手势
这一部分想必大家都已经非常熟悉了,现在很多APP都会加入手势来提高交互体验,其中在某个界面向右Fling关闭该界面这一操作貌似成了APP的标配了。废话不多说了,直接进入正题。
为View和Activity加入手势操作的步骤如下:
1、为View或者Activity实现OnGestureListener接口。
2、覆写View或者Activity的OnTouchEvent方法,这里要返回GestureDetector.onTouchEvent()。
3、覆写你需要的手势的回调方法。
这里解释一下各种回调方法的含义:
按下(onDown): 刚刚手指接触到触摸屏的那一刹那,就是触的那一下。
抛掷(onFling): 手指在触摸屏上迅速移动,并松开的动作。
长按(onLongPress): 手指按在持续一段时间,并且没有松开。
滚动(onScroll): 手指在触摸屏上滑动。
按住(onShowPress): 手指按在触摸屏上,它的时间范围在按下起效,在长按之前。
抬起(onSingleTapUp):手指离开触摸屏的那一刹那。
除了这些定义之外,鄙人也总结了一点算是经验的经验吧,在这里和大家分享一下。
任何手势动作都会先执行一次按下(onDown)动作。
长按(onLongPress)动作前一定会执行一次按住(onShowPress)动作。
按住(onShowPress)动作和按下(onDown)动作之后都会执行一次抬起(onSingleTapUp)动作。
长按(onLongPress)、滚动(onScroll)和抛掷(onFling)动作之后都不会执行抬起(onSingleTapUp)动作。
抛掷(onFling): 手指在触摸屏上迅速移动,并松开的动作。
长按(onLongPress): 手指按在持续一段时间,并且没有松开。
滚动(onScroll): 手指在触摸屏上滑动。
按住(onShowPress): 手指按在触摸屏上,它的时间范围在按下起效,在长按之前。
抬起(onSingleTapUp):手指离开触摸屏的那一刹那。
除了这些定义之外,鄙人也总结了一点算是经验的经验吧,在这里和大家分享一下。
任何手势动作都会先执行一次按下(onDown)动作。
长按(onLongPress)动作前一定会执行一次按住(onShowPress)动作。
按住(onShowPress)动作和按下(onDown)动作之后都会执行一次抬起(onSingleTapUp)动作。
长按(onLongPress)、滚动(onScroll)和抛掷(onFling)动作之后都不会执行抬起(onSingleTapUp)动作。
我这里贴上一段代码,大家可以看一下实现的步骤,实验代码和触摸屏事件专题的代码差不多,我就不重复上传了。
package com.example.gesturedemo; import android.content.Context; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.GestureDetector.OnGestureListener; import android.view.MotionEvent; import android.widget.TextView; public class MyTextView extends TextView implements OnGestureListener { private GestureDetector mGestureDetector; public MyTextView(Context context) { super(context); // TODO Auto-generated constructor stub mGestureDetector = new GestureDetector(context, this); } public MyTextView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub mGestureDetector = new GestureDetector(context, this); } public MyTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub mGestureDetector = new GestureDetector(context, this); } @Override public boolean dispatchTouchEvent(MotionEvent event) { // TODO Auto-generated method stub DebugTool.log("MyTextView--->dispatchTouchEvent"); return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub DebugTool.log("MyTextView--->onTouchEvent"); // int action = event.getAction(); // switch (action) { // case MotionEvent.ACTION_DOWN: // DebugTool.log("MyTextView--->onTouchEvent--->DOWN"); // break; // case MotionEvent.ACTION_MOVE: // DebugTool.log("MyTextView--->onTouchEvent--->MOVE"); // break; // case MotionEvent.ACTION_UP: // DebugTool.log("MyTextView--->onTouchEvent--->UP"); // break; // } return mGestureDetector.onTouchEvent(event); } @Override public boolean onDown(MotionEvent e) { // TODO Auto-generated method stub return false; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // TODO Auto-generated method stub DebugTool.log("MyTextView--->onFling"); return false; } @Override public void onLongPress(MotionEvent e) { // TODO Auto-generated method stub } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { // 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; } }
在OnGestureListener的带返回值的几个回调方法默认返回false的情况下,你会发现Fling手势是识别不到的,这是什么原因呢?这需要用上一专题的知识来解释 http://blog.csdn.net/benbmw2008/article/details/11143893 。 大家应该能看出来手势是基于触摸屏事件传递的,对照着上一篇的“默认事件流向”图,读者可以想象其实就是在“MyTextView onTouchEvent”和“MyRelativeLayout onTouchEvent”之间加一个"MyTextView's GestureDetector onTouchEvent",而"MyTextView's GestureDetector onTouchEvent"返回false,事件还是会继续传递给“MyRelativeLayout onTouchEvent”。 解决的办法是什么呢?自然是把OnGestureListener的带返回值的几个回调方法返回true,这样触摸屏事件就被“GestureDetector onTouchEvent”所消费,看一下日志会更加清楚:
09-08 10:36:10.714: V/gesturedemo(13130): MainActivity--->dispatchTouchEvent
09-08 10:36:10.718: V/gesturedemo(13130): MyRelativeLayout--->dispatchTouchEvent
09-08 10:36:10.722: V/gesturedemo(13130): MyRelativeLayout--->onInterceptTouchEvent
09-08 10:36:10.722: V/gesturedemo(13130): MyTextView--->dispatchTouchEvent
09-08 10:36:10.722: V/gesturedemo(13130): MyTextView--->onTouchEvent
09-08 10:36:10.824: V/gesturedemo(13130): MainActivity--->dispatchTouchEvent
09-08 10:36:10.828: V/gesturedemo(13130): MyRelativeLayout--->dispatchTouchEvent
09-08 10:36:10.832: V/gesturedemo(13130): MyRelativeLayout--->onInterceptTouchEvent
09-08 10:36:10.835: V/gesturedemo(13130): MyTextView--->dispatchTouchEvent
09-08 10:36:10.835: V/gesturedemo(13130): MyTextView--->onTouchEvent
09-08 10:36:10.863: V/gesturedemo(13130): MainActivity--->dispatchTouchEvent
09-08 10:36:10.878: V/gesturedemo(13130): MyRelativeLayout--->dispatchTouchEvent
09-08 10:36:10.886: V/gesturedemo(13130): MyRelativeLayout--->onInterceptTouchEvent
09-08 10:36:10.886: V/gesturedemo(13130): MyTextView--->dispatchTouchEvent
09-08 10:36:10.886: V/gesturedemo(13130): MyTextView--->onTouchEvent
09-08 10:36:10.898: V/gesturedemo(13130): MainActivity--->dispatchTouchEvent
09-08 10:36:10.898: V/gesturedemo(13130): MyRelativeLayout--->dispatchTouchEvent
09-08 10:36:10.898: V/gesturedemo(13130): MyRelativeLayout--->onInterceptTouchEvent
09-08 10:36:10.898: V/gesturedemo(13130): MyTextView--->dispatchTouchEvent
09-08 10:36:10.898: V/gesturedemo(13130): MyTextView--->onTouchEvent
09-08 10:36:10.917: V/gesturedemo(13130): MainActivity--->dispatchTouchEvent
09-08 10:36:10.917: V/gesturedemo(13130): MyRelativeLayout--->dispatchTouchEvent
09-08 10:36:10.917: V/gesturedemo(13130): MyRelativeLayout--->onInterceptTouchEvent
09-08 10:36:10.917: V/gesturedemo(13130): MyTextView--->dispatchTouchEvent
09-08 10:36:10.917: V/gesturedemo(13130): MyTextView--->onTouchEvent
09-08 10:36:10.917: V/gesturedemo(13130): MyTextView--->onFling
09-08 10:36:10.718: V/gesturedemo(13130): MyRelativeLayout--->dispatchTouchEvent
09-08 10:36:10.722: V/gesturedemo(13130): MyRelativeLayout--->onInterceptTouchEvent
09-08 10:36:10.722: V/gesturedemo(13130): MyTextView--->dispatchTouchEvent
09-08 10:36:10.722: V/gesturedemo(13130): MyTextView--->onTouchEvent
09-08 10:36:10.824: V/gesturedemo(13130): MainActivity--->dispatchTouchEvent
09-08 10:36:10.828: V/gesturedemo(13130): MyRelativeLayout--->dispatchTouchEvent
09-08 10:36:10.832: V/gesturedemo(13130): MyRelativeLayout--->onInterceptTouchEvent
09-08 10:36:10.835: V/gesturedemo(13130): MyTextView--->dispatchTouchEvent
09-08 10:36:10.835: V/gesturedemo(13130): MyTextView--->onTouchEvent
09-08 10:36:10.863: V/gesturedemo(13130): MainActivity--->dispatchTouchEvent
09-08 10:36:10.878: V/gesturedemo(13130): MyRelativeLayout--->dispatchTouchEvent
09-08 10:36:10.886: V/gesturedemo(13130): MyRelativeLayout--->onInterceptTouchEvent
09-08 10:36:10.886: V/gesturedemo(13130): MyTextView--->dispatchTouchEvent
09-08 10:36:10.886: V/gesturedemo(13130): MyTextView--->onTouchEvent
09-08 10:36:10.898: V/gesturedemo(13130): MainActivity--->dispatchTouchEvent
09-08 10:36:10.898: V/gesturedemo(13130): MyRelativeLayout--->dispatchTouchEvent
09-08 10:36:10.898: V/gesturedemo(13130): MyRelativeLayout--->onInterceptTouchEvent
09-08 10:36:10.898: V/gesturedemo(13130): MyTextView--->dispatchTouchEvent
09-08 10:36:10.898: V/gesturedemo(13130): MyTextView--->onTouchEvent
09-08 10:36:10.917: V/gesturedemo(13130): MainActivity--->dispatchTouchEvent
09-08 10:36:10.917: V/gesturedemo(13130): MyRelativeLayout--->dispatchTouchEvent
09-08 10:36:10.917: V/gesturedemo(13130): MyRelativeLayout--->onInterceptTouchEvent
09-08 10:36:10.917: V/gesturedemo(13130): MyTextView--->dispatchTouchEvent
09-08 10:36:10.917: V/gesturedemo(13130): MyTextView--->onTouchEvent
09-08 10:36:10.917: V/gesturedemo(13130): MyTextView--->onFling
Activity的手势实现和View的是一样的,这里就不重复说了,只不过你不用再修改手势回调方法的返回值了。
下面我又突然想到了一个问题,就是手势区域重复了怎么办?举个例子,有一个只包含一个View的Activity,需要实现在View区域向右Fling关闭Activity,向下Fling做别的一些操作,
1、如果逻辑都实现在View上是非常简单的,在onFling中直接判断就可以了。
2、但是有的情况只允许向右Fling的判断实现在Activity中,向下Fling的判断实现在View中(或者相反,总之是分开判断),这个时候又该怎么办?
对于第二种情况,答案还是很有意思的,读者如果感兴趣的话可以发表评论,有营养的评论超过5条后我会将答案的代码下载地址放在文章的最后。
输入法手势识别
在手写输入中,会为每一个字符定义一个特征码,这些特征码都保存在相应的文件中(可能有一个或多个这样的文件),当用户绘制一个描述字符的图形时,系统会为所绘制的图形提取特征码,然后会在保存特征码文件中查找相对应的特征码,如果找到,就会将对应的字符返回。其中,这些文件被称为手势文件。
我们来做一个简单的手势识别APP。
一、首先需要建立手势文件,这里需要借助SDK自带的Sample,大家在Eclipse中File--->New--->Project--->Android--->Android Sample Project,选择GestureBuilder,运行之,建立几个自己的手势,然后把手势文件拿出来即可(每建立一个手势会有一个Toast弹出,告诉你文件的保存路径)。
其实可以想像gestures手势文件中就是一个一个的键值对。
二、把gestures文件放入工程的res/raw目录下,有关raw和assets的区别,请移步
http://blog.csdn.net/benbmw2008/article/details/8754373
。
布局文件如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/tv1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:text="@string/hello_world" android:textSize="24sp" /> <android.gesture.GestureOverlayView android:id="@+id/gestures" android:layout_width="200dip" android:layout_height="200dip" android:layout_centerInParent="true" android:background="#33B5E5" > </android.gesture.GestureOverlayView> </RelativeLayout>
代码如下:
package com.a2bgeek.gesturedemo2; import java.util.ArrayList; import android.os.Bundle; import android.app.Activity; import android.gesture.Gesture; import android.gesture.GestureLibraries; import android.gesture.GestureLibrary; import android.gesture.GestureOverlayView; import android.gesture.GestureOverlayView.OnGesturePerformedListener; import android.gesture.Prediction; import android.view.Menu; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity { private TextView mTextView; private GestureOverlayView mGestureOverlayView; private GestureLibrary mGestureLibrary; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); } private void initViews() { mTextView = (TextView) findViewById(R.id.tv1); mGestureLibrary = GestureLibraries.fromRawResource( getApplicationContext(), R.raw.gestures); if (mGestureLibrary.load()) { mGestureOverlayView = (GestureOverlayView) findViewById(R.id.gestures); mGestureOverlayView .addOnGesturePerformedListener(new MyGesturePerformListener()); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } private class MyGesturePerformListener implements OnGesturePerformedListener { @Override public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) { // TODO Auto-generated method stub ArrayList<Prediction> list = mGestureLibrary.recognize(gesture); if (list.size() > 0) { //list是mGestureLibrary中可能与gesture匹配的手势集,匹配的程度会有一个score评分。 StringBuilder sb = new StringBuilder(); for (Prediction prediction : list) { sb.append(prediction.name); sb.append(":"); sb.append(prediction.score); sb.append("\n"); } mTextView.setText(sb.toString()); } else { Toast.makeText(getApplicationContext(), "没有匹配", Toast.LENGTH_SHORT).show(); } } } }
好了,今天的内容就到这里了,第一周更新了两篇专题,是个良好的开始,嗯嗯,加油。