手势绘制(GestureOverlayView)、手势的形成(Gesture)、手势的保存和加载(GestureLibrary)、手势识别的源码解析

Android手势源码浅析-----手势绘制(GestureOverlayView)

 前言:Android关于手势的操作提供两种形式:一种是针对用户手指在屏幕上划出的动作而进行移动的检测,这些手势的检测通过android提供的监听器来实现;另一种是用户手指在屏幕上滑动而形成一定的不规则的几何图形(即为多个持续触摸事件在屏幕形成特定的形状);本文主要是针对第二种手势的绘制原理进行浅析,我们姑且称它为输入法手势;

    一. 输入法手势

       在Android源码中,谷歌提供了相关的手势库源码,供给开发者丰富多彩的接口调用实现;这些提供相关接口的类所在的源码路径为frameworks/base/core/java/android/gesture;

      如下图gesture文件中的相关类:

手势绘制(GestureOverlayView)、手势的形成(Gesture)、手势的保存和加载(GestureLibrary)、手势识别的源码解析_第1张图片

   绘制手势需要一个视图界面平台,而这个视图界面平台由GestureOverlayView这个类来实现,该类继承FrameLayout容器视图类。所以,当我们在手机屏幕上画手势时,GestureOverlayView主要负责显示和处理手指在屏幕上滑动所形成的手势。

   以下举一个简单的Demo来说明如何通过GestureOverlayView实现在屏幕上绘制手势;

   1). main.xml文件代码如下:

[html]  view plain  copy
  1. xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="fill_parent"  
  4.     android:layout_height="fill_parent"  
  5.     android:orientation="vertical" >  
  6.   
  7.    <android.gesture.GestureOverlayView   
  8.        android:id="@+id/gesture"  
  9.        android:layout_width="fill_parent"  
  10.        android:layout_height="fill_parent"  
  11.        >      
  12.    android.gesture.GestureOverlayView>  
  13.   
  14. LinearLayout>  
      很简单,添加一个android.gesture.GestureOverlayView的布局组件;

   2). 加载布局文件和实现手势绘制的Actitivty代码如下:

[java]  view plain  copy
  1. package com.stevenhu.hu.dgt;  
  2.   
  3. import android.app.Activity;  
  4. import android.gesture.Gesture;  
  5. import android.gesture.GestureOverlayView;  
  6. import android.gesture.GestureOverlayView.OnGesturePerformedListener;  
  7. import android.gesture.GestureOverlayView.OnGesturingListener;  
  8. import android.os.Bundle;  
  9. import android.widget.Toast;  
  10.   
  11. public class DrawGestureTest extends Activity implements OnGesturePerformedListener, OnGesturingListener  
  12. {  
  13.       
  14.     private GestureOverlayView mDrawGestureView;  
  15.     /** Called when the activity is first created. */  
  16.     @Override  
  17.     public void onCreate(Bundle savedInstanceState)  
  18.     {  
  19.         super.onCreate(savedInstanceState);  
  20.         setContentView(R.layout.main);  
  21.           
  22.         mDrawGestureView = (GestureOverlayView)findViewById(R.id.gesture);  
  23.           
  24.         //设置手势可多笔画绘制,默认情况为单笔画绘制  
  25.         mDrawGestureView.setGestureStrokeType(GestureOverlayView.GESTURE_STROKE_TYPE_MULTIPLE);  
  26.         //设置手势的颜色(蓝色)  
  27.         mDrawGestureView.setGestureColor(gestureColor(R.color.gestureColor));  
  28.         //设置还没未能形成手势绘制是的颜色(红色)  
  29.         mDrawGestureView.setUncertainGestureColor(gestureColor(R.color.ungestureColor));  
  30.         //设置手势的粗细  
  31.         mDrawGestureView.setGestureStrokeWidth(4);  
  32.         /*手势绘制完成后淡出屏幕的时间间隔,即绘制完手指离开屏幕后相隔多长时间手势从屏幕上消失; 
  33.          * 可以理解为手势绘制完成手指离开屏幕后到调用onGesturePerformed的时间间隔 
  34.          * 默认值为420毫秒,这里设置为2秒 
  35.          */  
  36.         mDrawGestureView.setFadeOffset(2000);  
  37.           
  38.         //绑定监听器  
  39.         mDrawGestureView.addOnGesturePerformedListener(this);  
  40.         mDrawGestureView.addOnGesturingListener(this);  
  41.     }  
  42.       
  43.     //手势绘制完成时调用  
  44.     @Override  
  45.     public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture)   
  46.     {  
  47.         // TODO Auto-generated method stub  
  48.         showMessage("手势绘制完成");  
  49.     }  
  50.       
  51.     private int gestureColor(int resId)  
  52.     {  
  53.         return getResources().getColor(resId);  
  54.     }  
  55.       
  56.     private void showMessage(String s)  
  57.     {  
  58.         Toast.makeText(this, s, Toast.LENGTH_SHORT).show();  
  59.     }  
  60.   
  61.     //结束正在绘制手势时调用(手势绘制完成时一般是先调用它在调用onGesturePerformed)  
  62.     @Override  
  63.     public void onGesturingEnded(GestureOverlayView overlay)   
  64.     {  
  65.         // TODO Auto-generated method stub  
  66.         showMessage("结束正在绘制手势");  
  67.     }  
  68.   
  69.     //正在绘制手势时调用  
  70.     @Override  
  71.     public void onGesturingStarted(GestureOverlayView overlay)   
  72.     {  
  73.         // TODO Auto-generated method stub  
  74.         showMessage("正在绘制手势");  
  75.     }  
  76.   
  77.     @Override  
  78.     protected void onDestroy()   
  79.     {  
  80.         // TODO Auto-generated method stub  
  81.         super.onDestroy();  
  82.         //移除绑定的监听器  
  83.         mDrawGestureView.removeOnGesturePerformedListener(this);  
  84.         mDrawGestureView.removeOnGesturingListener(this);  
  85.     }  
  86.       
  87. }  
     示例代码下载链接地址:http://download.csdn.net/detail/stevenhu_223/5789777
  通过上面的Demo可知,要想实现绘制和监听操作手势,GestureOverlayView是必不可少的,GestureOverlayView为何方神圣
,它是如何实现手势的绘制和监听操作的,接下来将对它进行浅析。

   二. GestureOverlayView类浅析

      其实手势的绘制原理和前篇<<Android中Path类的lineTo方法和quadTo方法画线的区别>>中绘制轨迹线的原理差不多,只不过在GestureOverlayView中的处理相对比较复杂;

     GestureOverlayView继承FrameLayout,所以它也是ViewGroup类型(继承View),GestureOverlayView重写View的dispatchTouchEvent方法。所以,我们手指在屏幕上触摸滑动时,会调用GestureOverlayView的dispatchTouchEvent方法;代码如下:

[java]  view plain  copy
  1. public class GestureOverlayView extends FrameLayout {  
  2. ...  
  3.  @Override  
  4.     public boolean dispatchTouchEvent(MotionEvent event) {  
  5.         if (isEnabled()) {  
  6.             final boolean cancelDispatch = (mIsGesturing || (mCurrentGesture != null &&  
  7.                     mCurrentGesture.getStrokesCount() > 0 && mPreviousWasGesturing)) &&  
  8.                     mInterceptEvents;  
  9.   
  10.             processEvent(event);  
  11.   
  12.             if (cancelDispatch) {  
  13.                 event.setAction(MotionEvent.ACTION_CANCEL);  
  14.             }  
  15.   
  16.             super.dispatchTouchEvent(event);  
  17.   
  18.             return true;  
  19.         }  
  20.   
  21.         return super.dispatchTouchEvent(event);  
  22.     }  
  23. ...  
  24. }  
   isEnabled()得到当前视图的enable状态,若当前视图的enable状态为true,则继续执行processEvent(event),传入参数为对应的滑动事件。

    ----> 我们接着继续跟踪processEvent方法,代码如下:

[java]  view plain  copy
  1. ...  
  2.  private boolean processEvent(MotionEvent event) {  
  3.         switch (event.getAction()) {  
  4.             case MotionEvent.ACTION_DOWN:  
  5.                 touchDown(event);  
  6.                 invalidate();  
  7.                 return true;  
  8.             case MotionEvent.ACTION_MOVE:  
  9.                 if (mIsListeningForGestures) {  
  10.                     Rect rect = touchMove(event);  
  11.                     if (rect != null) {  
  12.                         invalidate(rect);  
  13.                     }  
  14.                     return true;  
  15.                 }  
  16.                 break;  
  17.             case MotionEvent.ACTION_UP:  
  18.                 if (mIsListeningForGestures) {  
  19.                     touchUp(event, false);  
  20.                     invalidate();  
  21.                     return true;  
  22.                 }  
  23.                 break;  
  24.             case MotionEvent.ACTION_CANCEL:  
  25.                 if (mIsListeningForGestures) {  
  26.                     touchUp(event, true);  
  27.                     invalidate();  
  28.                     return true;  
  29.                 }  
  30.         }  
  31.   
  32.         return false;  
  33.     }  
  34. ...  
  在processEvent方法中会根据用户手指对屏幕操作的MotionEvent进行处理:

   1). 当MotionEvent事件为ACTION_DOWN时,调用touchDown(MotionEvent event)方法;

   2). 当MotionEvent事件为ACTION_MOVE,且mIsListeningForGestures为true时(执行touchDown时赋值为true),调用touchMove(MotionEvent event)方法;

   3). 当MotionEvent事件为ACTION_UP,且mIsListeningForGestures为true时,调用touchUp(MotionEvent event, boolean cancel)方法;mIsListeningForGestures在执行touchUp时赋值为false;

   4). 当MotionEvent事件为ACTION_CANCEL,且mIsListeningForGestures为true时,调用touchUp(MotionEvent event, boolean cancel)方法;
    接下来逐步分析以上分发处理MotionEvent事件的各个函数的实现:

 ---->touchDown(MotionEvent event),当用户手指点下屏幕时调用该方法,码如下:

[java]  view plain  copy
  1. ...  
  2.  private void touchDown(MotionEvent event) {  
  3.         mIsListeningForGestures = true;  
  4.   
  5.         float x = event.getX();  
  6.         float y = event.getY();  
  7.   
  8.         mX = x;  
  9.         mY = y;  
  10.   
  11.         mTotalLength = 0;  
  12.         mIsGesturing = false;  
  13.   
  14.         if (mGestureStrokeType == GESTURE_STROKE_TYPE_SINGLE || mResetGesture) {  
  15.             if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);  
  16.             mResetGesture = false;  
  17.             mCurrentGesture = null;  
  18.             mPath.rewind();  
  19.         } else if (mCurrentGesture == null || mCurrentGesture.getStrokesCount() == 0) {  
  20.             if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);  
  21.         }  
  22.   
  23.         // if there is fading out going on, stop it.  
  24.         //如果手势已正在淡出,则停止它    
  25.         if (mFadingHasStarted) {  
  26.             cancelClearAnimation();  
  27.         } else if (mIsFadingOut) {  
  28.             setPaintAlpha(255);  
  29.             mIsFadingOut = false;  
  30.             mFadingHasStarted = false;  
  31.             removeCallbacks(mFadingOut);  
  32.         }  
  33.   
  34.         if (mCurrentGesture == null) {  
  35.             mCurrentGesture = new Gesture();  
  36.         }  
  37.   
  38.         mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));  
  39.         mPath.moveTo(x, y);  
  40.   
  41.         //mInvalidateExtraBorder值由设置手势画笔粗细值决定  
  42.         final int border = mInvalidateExtraBorder;  
  43.         mInvalidRect.set((int) x - border, (int) y - border, (int) x + border, (int) y + border);  
  44.   
  45.         mCurveEndX = x;  
  46.         mCurveEndY = y;  
  47.   
  48.         // pass the event to handlers  
  49.         final ArrayList listeners = mOnGestureListeners;  
  50.         final int count = listeners.size();  
  51.         for (int i = 0; i < count; i++) {  
  52.             listeners.get(i).onGestureStarted(this, event);  
  53.         }  
  54.     }  
  55. ...  
   在touchDown中,实现处理当用户手指在点下屏幕时的一些操作,这些操作包括:

     1). 获取用户手指点下屏幕时所在的坐标值x,y,同时将它们分别赋值给全局变量mX,mY;mTotalLength变量代表绘制手势的总长度,在调用touchDown时,手势还没绘制,所以mTotalLength为0;mIsGesturing描述是否正在绘制手势,为false表示不是正在绘制手势;
     2). 根据一些条件判断,设置画笔颜色,处理手势画笔的相关状态,以及创建Gesture对象等。

     3). 将1)得到的x,y坐标值和event.getEventTime()的值作为GesturePoint构造函数的实参创建GesturePoint对象,并将该对象添加进mStrokeBuffer数组集合。

     4). 将1)得到的x,y坐标值作为mPath画笔路径的初始点。

     5). 遍历存放OnGestureListener的集合listeners,调用实现OnGestureListener接口的onGestureStarted()方法;

  ---->touchMove(MotionEvent event)当用户手指在屏幕上滑动时调用该方法,码如下:

[java]  view plain  copy
  1. ...  
  2. private Rect touchMove(MotionEvent event) {  
  3.         //更新区域  
  4.         Rect areaToRefresh = null;  
  5.   
  6.         final float x = event.getX();  
  7.         final float y = event.getY();  
  8.   
  9.         final float previousX = mX;  
  10.         final float previousY = mY;  
  11.   
  12.         final float dx = Math.abs(x - previousX);  
  13.         final float dy = Math.abs(y - previousY);  
  14.   
  15.         //手势在屏幕滑动的两点之间的距离大于GestureStroke.TOUCH_TOLERANCE的值,则显示手势的绘制  
  16.         if (dx >= GestureStroke.TOUCH_TOLERANCE || dy >= GestureStroke.TOUCH_TOLERANCE) {  
  17.             areaToRefresh = mInvalidRect;  
  18.   
  19.             // start with the curve end  
  20.             final int border = mInvalidateExtraBorder;  
  21.             areaToRefresh.set((int) mCurveEndX - border, (int) mCurveEndY - border,  
  22.                     (int) mCurveEndX + border, (int) mCurveEndY + border);  
  23.   
  24.             //设置贝塞尔曲线的操作点为起点和终点的一半  
  25.             float cX = mCurveEndX = (x + previousX) / 2;  
  26.             float cY = mCurveEndY = (y + previousY) / 2;  
  27.   
  28.             //二次贝塞尔,实现平滑曲线;previousX, previousY为操作点,cX, cY为终点  
  29.             mPath.quadTo(previousX, previousY, cX, cY);  
  30.   
  31.             // union with the control point of the new curve  
  32.             /*areaToRefresh矩形扩大了border(宽和高扩大了两倍border), 
  33.              * border值由设置手势画笔粗细值决定 
  34.              */  
  35.             areaToRefresh.union((int) previousX - border, (int) previousY - border,  
  36.                     (int) previousX + border, (int) previousY + border);  
  37.   
  38.             // union with the end point of the new curve  
  39.             areaToRefresh.union((int) cX - border, (int) cY - border,  
  40.                     (int) cX + border, (int) cY + border);  
  41.   
  42.             //第二次执行时,第一次结束调用的坐标值将作为第二次调用的初始坐标值  
  43.             mX = x;  
  44.             mY = y;  
  45.   
  46.             mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));  
  47.   
  48.             //当调用addOnGesturePerformedListener添加手势完成调用的监听器时,mHandleGestureActions为true;  
  49.             if (mHandleGestureActions && !mIsGesturing) {  
  50.                 mTotalLength += (float) Math.sqrt(dx * dx + dy * dy);  
  51.   
  52.                 if (mTotalLength > mGestureStrokeLengthThreshold) {  
  53.                     final OrientedBoundingBox box =  
  54.                             GestureUtils.computeOrientedBoundingBox(mStrokeBuffer);  
  55.   
  56.                     float angle = Math.abs(box.orientation);  
  57.                     if (angle > 90) {  
  58.                         angle = 180 - angle;  
  59.                     }  
  60.   
  61.                     /*这个条件成立时,说明所手势绘制已经在进行 
  62.                      */  
  63.                     if (box.squareness > mGestureStrokeSquarenessTreshold ||  
  64.                             (mOrientation == ORIENTATION_VERTICAL ?  
  65.                                     angle < mGestureStrokeAngleThreshold :  
  66.                                     angle > mGestureStrokeAngleThreshold)) {  
  67.   
  68.                         mIsGesturing = true;  
  69.                         //手势尚未形成的显示颜色  
  70.                         setCurrentColor(mCertainGestureColor);  
  71.   
  72.                         final ArrayList listeners = mOnGesturingListeners;  
  73.                         int count = listeners.size();  
  74.                         for (int i = 0; i < count; i++) {  
  75.                             listeners.get(i).onGesturingStarted(this);  
  76.                         }  
  77.                     }  
  78.                 }  
  79.             }  
  80.   
  81.             // pass the event to handlers  
  82.             final ArrayList listeners = mOnGestureListeners;  
  83.             final int count = listeners.size();  
  84.             for (int i = 0; i < count; i++) {  
  85.                 listeners.get(i).onGesture(this, event);  
  86.             }  
  87.         }  
  88.   
  89.         return areaToRefresh;  
  90.     }  
  91. ...  

   touchMove方法中主要有以下功能的实现:
     1). touchMove方法返回值类型为Rect(定义一个矩形区域),若返回值不会空,则调用invalidate(Rectrect)刷新;

     2). 得到当前的手指滑动所在屏幕位置的x,y坐标值,将x,y值与调用touchDown()时得到的x,y值相减后取绝对值,得到偏移量dx,dy;

     3). dx或dy大于指定的GestureStroke.TOUCH_TOLERANCE时(默认值为3),执行画笔绘制手势的实现流程代码。

     4). mPath画笔路径调用quadTo()方法执行贝塞尔曲线计算,实现得到平滑曲线。   

     5). areaToRefresh矩形区域负责根据手势绘制控制点和结束点的位置不断更新,画出手势画笔轨迹(每次调用touchMove()时,areaToRefresh逐点更新从而汇成一定轨迹的几何图形,即手势的雏形)。

     6). 将第二步得到x,y坐标值和event.getEventTime()的值作为GesturePoint构造函数的实参创建GesturePoint对象,并将该对象添加进mStrokeBuffer数组集合。(保存用户在屏幕上绘制形成手势的相关信息)

    7). 当调用GestureOverlayView的addOnGesturePerformedListener方法添加监听器OnGesturePerformedListener时,mHandleGestureActions为true,这时候会执行计算移动所得的这些点集的最小边界框,然后根据这个最小边界框进行一些条件判断,进而设置mIsGesturering为true,以及设置手势尚未形成绘制手势的显示颜色。

    8). touchMove()的最后,遍历存放OnGestureListener接口的集合listeners,调用实现OnGestureListener接口的onGesture方法。

  ---->touchUp(MotionEvent event, boolean cancel)当用户手指离开屏幕或MotionEvent 事件取消时调用该方法,码如下:

[java]  view plain  copy
  1. ...  
  2. private void touchUp(MotionEvent event, boolean cancel) {  
  3.         mIsListeningForGestures = false;  
  4.   
  5.         // A gesture wasn't started or was cancelled  
  6.         if (mCurrentGesture != null) {  
  7.             // add the stroke to the current gesture  
  8.             /*将之前调用touchDonw和touchMove收集得到GesturePoint的组成的数组集合mStrokeBuffer, 
  9.              * 做为GestureStroke构造函数的实参创建GestureStroke对象, 
  10.              * 然后将GestureStroke对象通过调用addStroke方法添加到mCurrentGesture中 
  11.              */  
  12.             mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));  
  13.   
  14.             if (!cancel) {  
  15.                 // pass the event to handlers  
  16.                 final ArrayList listeners = mOnGestureListeners;  
  17.                 int count = listeners.size();  
  18.                 for (int i = 0; i < count; i++) {  
  19.                     listeners.get(i).onGestureEnded(this, event);  
  20.                 }  
  21.   
  22.                 /*当调用addOnGesturePerformedListener方法时,mHandleGestureActions为true; 
  23.                  * mFadeEnabled默认值为true,可通过setFadeEnabled函数设值 
  24.                  */  
  25.                 clear(mHandleGestureActions && mFadeEnabled, mHandleGestureActions && mIsGesturing,  
  26.                         false);  
  27.             } else {  
  28.                 cancelGesture(event);  
  29.   
  30.             }  
  31.         } else {  
  32.             cancelGesture(event);  
  33.         }  
  34.   
  35.         mStrokeBuffer.clear();  
  36.         mPreviousWasGesturing = mIsGesturing;  
  37.         mIsGesturing = false;  
  38.   
  39.         final ArrayList listeners = mOnGesturingListeners;  
  40.         int count = listeners.size();  
  41.         for (int i = 0; i < count; i++) {  
  42.             listeners.get(i).onGesturingEnded(this);  
  43.         }  
  44.     }  
  45. ...  
   touchUp方法中主要有以下功能的实现:
     1). 首先将mIsListeningForGesture赋值为false;

     2). 判断当前是否存在mCurrentGesture(Gesture类型),该变量在执行touchDown方法时创建Gesture对象赋值的,也可以通过调用setGesture方法赋值;(mCurrentGesture描述的就是当前用户绘制形成的整个手势)

     3). 若mCurrentGesture不为空,则将之前调用touchDonw和touchMove收集得到的GesturePoint组成的数组集合mStrokeBuffer做为GestureStroke构造函数的实参,创建GestureStroke对象。然后将GestureStroke对象通过调用addStroke方法添加到mCurrentGesture中;

     4). 若touchUp方法的第二个参数为false(即执行ACTION_UP事件时),则遍历存放OnGestureListener的集合,调用实现该接口的onGestureEnded()方法。接着调用clear方法,实现将当前绘制形成的手势清除(即手势淡出屏幕;手指离开屏幕时到手势淡出屏幕,这期间是有时间间隔的,且这个时间间隔也是可以设置);

     5). 若touchUp()方法的第二个参数为true(即执行ACTION_CANCEL事件时),调用cancelGesture()方法。在该方法中:首先遍历存放OnGestureListener的集合,调用实现该接口的onGestureCancelled()方法,接着调用clear()方法实现回收mCurrentGesture对象、清除画笔等淡出屏幕处理;

   ---->上面4)中,当touchUp方法的cancel参数为false时,通过调用clear(boolean animated, boolean fireActionPerformed, boolean immediate)处理手势淡出屏幕,我们来看看这个方法的实现,代码如下:

[java]  view plain  copy
  1. ...  
  2. private void clear(boolean animated, boolean fireActionPerformed, boolean immediate) {  
  3.         setPaintAlpha(255);  
  4.         removeCallbacks(mFadingOut);  
  5.         mResetGesture = false;  
  6.         mFadingOut.fireActionPerformed = fireActionPerformed;  
  7.         mFadingOut.resetMultipleStrokes = false;  
  8.   
  9.         if (animated && mCurrentGesture != null) { //调用addOnGesturePerformedListener时animated为true  
  10.             mFadingAlpha = 1.0f;  
  11.             mIsFadingOut = true;  
  12.             mFadingHasStarted = false;  
  13.             /*mFadeOffset定义手势淡出屏幕的时间间隔, 
  14.              * 默认值420,可通过setFadeOffset函数设置 
  15.              */  
  16.             mFadingStart = AnimationUtils.currentAnimationTimeMillis() + mFadeOffset;  
  17.   
  18.             postDelayed(mFadingOut, mFadeOffset);  
  19.         } else {  
  20.             mFadingAlpha = 1.0f;  
  21.             mIsFadingOut = false;  
  22.             mFadingHasStarted = false;  
  23.   
  24.             if (immediate) {  
  25.                 mCurrentGesture = null;  
  26.                 mPath.rewind();  
  27.                 invalidate();  
  28.             } else if (fireActionPerformed) {  
  29.                 postDelayed(mFadingOut, mFadeOffset);  
  30.             } else if (mGestureStrokeType == GESTURE_STROKE_TYPE_MULTIPLE) {  
  31.                 mFadingOut.resetMultipleStrokes = true;  
  32.                 postDelayed(mFadingOut, mFadeOffset);  
  33.             } else {  
  34.                 mCurrentGesture = null;  
  35.                 mPath.rewind();  
  36.                 invalidate();  
  37.             }  
  38.         }  
  39.     }  
  40. ...  

    通过上面的代码,我们知道,在clear函数中,会通过传入的实参来决定如何去进一步处理手势的淡出,有两种处理方式:

    1. 调用mPath.rewind(),将绘制手势的重置清除,然后调用invalidate();

    2. 调用postDelayed(mFadingOut, mFadeOffset),到主线程中处理,mFadeOffset就是决定手势淡出屏幕的时间间隔;

   我们针对第二种在主线程中处理的方式继续跟踪解析代码,mFadingOut是FadeOutRunnable对象,FadeOutRunnable继承Runnable类,该类的实现代码如下:

[java]  view plain  copy
  1. ...   
  2. /*处理手势淡出; 
  3.      * 手势淡出的条件: 
  4.      * 1.前面一次画完手势,且画完的同时没有调用onGesturePerformed, 
  5.      *   则当用户再次画手势时,前面画出的保留在屏幕上的手势将淡出; 
  6.      * 2.当画完手势,且添加OnGesturePerformedListener监听器时, 
  7.      *   在完成手势,调用onGesturePerformed时,将手势轨迹画笔淡出 
  8.      */  
  9.     private class FadeOutRunnable implements Runnable {  
  10.         //调用addOnGesturePerformedListener时为true;  
  11.         boolean fireActionPerformed;  
  12.         //手势设置为多笔画绘制时为true;  
  13.         boolean resetMultipleStrokes;  
  14.   
  15.         public void run() {  
  16.             if (mIsFadingOut) { //fireActionPerformed为true且mCurrentGesture不为空是成立  
  17.                 final long now = AnimationUtils.currentAnimationTimeMillis();  
  18.                 final long duration = now - mFadingStart;  
  19.   
  20.                 //mFadeDuration默认值为150  
  21.                 if (duration > mFadeDuration) {  
  22.                     if (fireActionPerformed) {  
  23.                         //调用onGesturePerformed方法  
  24.                         fireOnGesturePerformed();  
  25.                     }  
  26.   
  27.                     mPreviousWasGesturing = false;  
  28.                     mIsFadingOut = false;  
  29.                     mFadingHasStarted = false;  
  30.                     mPath.rewind();  
  31.                     mCurrentGesture = null;  
  32.                     setPaintAlpha(255);  
  33.                 } else {  
  34.                     mFadingHasStarted = true;  
  35.                     float interpolatedTime = Math.max(0.0f,  
  36.                             Math.min(1.0f, duration / (float) mFadeDuration));  
  37.                     mFadingAlpha = 1.0f - mInterpolator.getInterpolation(interpolatedTime);  
  38.                     setPaintAlpha((int) (255 * mFadingAlpha));  
  39.                     //FADE_ANIMATION_RATE默认值为16  
  40.                     postDelayed(this, FADE_ANIMATION_RATE);  
  41.                 }  
  42.             } else if (resetMultipleStrokes) { //fireActionPerformed为false且手势为多笔画绘制时成立  
  43.                 mResetGesture = true;  
  44.             } else {  
  45.                 //调用实现监听器OnGesturePerformedListener的onGesturePerformed方法  
  46.                 fireOnGesturePerformed();  
  47.   
  48.                 mFadingHasStarted = false;  
  49.                 mPath.rewind();  
  50.                 mCurrentGesture = null;  
  51.                 mPreviousWasGesturing = false;  
  52.                 setPaintAlpha(255);  
  53.             }  
  54.   
  55.             invalidate();  
  56.         }  
  57.     }  
  58. ...  
  值得注意的是,在主线程中处理手势淡出屏幕,当我们绑定了监听器OnGesturePerformedListener,手势淡出屏幕时会调用fireOnGesturePerformed方法,该方法实现遍历存放OnGesturePerformedListener的集合actionListeners,进而调用实现OnGesturePerformedListener接口的函数onGesturePerformed,代码如下:

[java]  view plain  copy
  1. ...  
  2. private void fireOnGesturePerformed() {  
  3.         final ArrayList actionListeners = mOnGesturePerformedListeners;  
  4.         final int count = actionListeners.size();  
  5.         for (int i = 0; i < count; i++) {  
  6.             actionListeners.get(i).onGesturePerformed(GestureOverlayView.this, mCurrentGesture);  
  7.         }  
  8.     }  
  9. ...  

  最后,有一点值得注意,当我们手指在触摸屏上滑动时,在processEvent方法中,每次执行完touchDown、touchMove方法后都会调用invalidate()、invalidate(rect)进行不断的刷新,那么这时候就调用draw方法将用户在触摸屏上绘制的手势轨迹显示出来,代码如下:

[java]  view plain  copy
  1. ...  
  2. @Override  
  3.     public void draw(Canvas canvas) {  
  4.         super.draw(canvas);  
  5.   
  6.         if (mCurrentGesture != null && mGestureVisible) {  
  7.             canvas.drawPath(mPath, mGesturePaint);  
  8.         }  
  9.     }  
  10. ...  

    至此,关于实现手势绘制的视图平台类GestureOverlayView的浅析就结束了!


Android手势源码浅析------手势的形成(Gesture)

前言:上篇介绍了提供手势绘制的视图平台GestureOverlayView,但是在视图平台上绘制出的手势,是需要存储以及在必要的利用时加载取出手势。所以,用户绘制出的一个完整的手势是需要一定的代码机制来完成存储以及必要时加载取出的;那么,在源码中Gesture这个类就是用来描述完整的手势的。一个Gesture就是用户手指在触摸屏上绘制形成的不规则几何图形(A gesture is a hand-drawn shape on a touch screen);

   一. Gesture的组成

     通过前篇文章<<Android手势源码浅析-----手势绘制(GestureOverlayView)>>的介绍,我们知道,当我们在GestureOverlayView上绘制手势时,形成的不规则几何图形是由多数个点形成的,这些点都有其对应的在屏幕上的坐标值和时间戳(event.getEventTime());那么,这些点是如何组成Gesture的呢?针对这个问题,通过对Android手势源码的浅析来寻求答案;

    下图总体上大概描述了Gesture的形成结构,如下:

手势绘制(GestureOverlayView)、手势的形成(Gesture)、手势的保存和加载(GestureLibrary)、手势识别的源码解析_第2张图片

   从上图描述的类关系中,可以知道:

      1. 触摸屏上的点首先是通过GesturePoint这个类来描述的,GesturePoint封装点的x,y轴值和时间戳。

      2. GesturePoint中封装的点的信息会在GestureStroke类中被拆解处理,点对应的x,y值被拆解存放在GestureStroke的float类型数组成员points中,而点对应的时间戳则放在long类型成员数组timestamps中。

      3. GestureStroke表示一个手势行程(用户手指点下屏幕到手势离开屏幕绘制出的轨迹就是一个手势行程)。一个完整的手势由一个或多个手势行程组成(单笔画或多笔画绘制手势)

      4. Gesture由单个或多个GestureStroke组成,Gesture类中的mStrokeBuffer成员为ArrayList类型集合,存放的是GestureStroke;

   二. Gesture的形成过程:

           当我们在GestureOverlayView上绘制手势时,会调用GestureOverlayView的touchDown、touchMove、touchUp方法,然后将通过这个三个方法捕获到的形成手势的多数个点组成Gesture。如下代码:

[java]  view plain  copy
  1. public class GestureOverlayView extends FrameLayout {  
  2. ...  
  3.     private void touchDown(MotionEvent event) {  
  4.     ...  
  5.         mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));  
  6.     ...  
  7.     }  
  8.   
  9.     private Rect touchMove(MotionEvent event) {  
  10.     ...  
  11.         mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));  
  12.     ...  
  13.     }  
  14.   
  15.     private void touchUp(MotionEvent event, boolean cancel) {  
  16.     ...  
  17.          mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));  
  18.     ...  
  19.     }  
  20. ...  
  21. }  
   ---->通过上面的代码可知,当用户正在绘制手势时,会调用touchDown、touchMove,执行mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())),实现将点的x、y、event.getEventTime() 值作为GesturePoint的构造函数的实参创建GesturePoint对象,然后将得到的GesturePoint添加到mStrokeBuffer集合中(mStrokeBuffer为ArrayList类型);

    GesturePoint的源代码如下:

[java]  view plain  copy
  1. /* 
  2.  * Copyright (C) 2008-2009 The Android Open Source Project 
  3.  * 
  4.  * Licensed under the Apache License, Version 2.0 (the "License"); 
  5.  * you may not use this file except in compliance with the License. 
  6.  * You may obtain a copy of the License at 
  7.  * 
  8.  *      http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  * Unless required by applicable law or agreed to in writing, software 
  11.  * distributed under the License is distributed on an "AS IS" BASIS, 
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  * See the License for the specific language governing permissions and 
  14.  * limitations under the License. 
  15.  */  
  16.   
  17. package android.gesture;  
  18.   
  19. import java.io.DataInputStream;  
  20. import java.io.IOException;  
  21.   
  22. /** 
  23.  * A timed point of a gesture stroke. Multiple points form a stroke. 
  24.  */  
  25.   
  26. //一个手势行程的定时点,多个点形成一个手势行程。GesturePoint封装x,y轴和时间戳的值  
  27. public class GesturePoint {  
  28.     public final float x;  
  29.     public final float y;  
  30.   
  31.     public final long timestamp;  
  32.   
  33.     public GesturePoint(float x, float y, long t) {  
  34.         this.x = x;  
  35.         this.y = y;  
  36.         timestamp = t;  
  37.     }  
  38.   
  39.     //从输入流中读取之前保存在文件中的数据  
  40.     static GesturePoint deserialize(DataInputStream in) throws IOException {  
  41.         // Read X and Y  
  42.         final float x = in.readFloat(); //从输入流中读出对应x轴的坐标值 (来自通过调用GestureStroke的函数serialize保存的,下同)  
  43.         final float y = in.readFloat(); //从输入流中读出对应y轴的坐标值  
  44.         // Read timestamp  
  45.         final long timeStamp = in.readLong(); //从输入流中读出对应的时间戳  
  46.         return new GesturePoint(x, y, timeStamp);  
  47.     }  
  48.       
  49.     @Override  
  50.     public Object clone() {  
  51.         return new GesturePoint(x, y, timestamp);  
  52.     }  
  53. }  
    通过源码可知,在GesturePoint的构造函数中,将传进来的点的各个信息值分别赋值给自身的成员变量x、y、timestamp;所以GesturePoint描述的就是组成完成手势中的一个点元素;而GestureOverlayView中的mStrokeBuffer集合保存着组成手势的多数个点

   ---->紧接着,当用户完成手势绘制手指离开屏幕时,会调用touchUp,执行 mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer)),实现将之前绘制手势得到的mStrokeBuffer集合作为GestureStroke构造函数的实参创建GestureStroke对象,然后将GestureStroke对象作为mCurrentGesture(Gesture对象)的方法addStroke的实参,实现将GestureStroke添加到Gesture中;

    GesturePoint的部分源代码如下:

[java]  view plain  copy
  1. /** 
  2.  * A gesture stroke started on a touch down and ended on a touch up. A stroke 
  3.  * consists of a sequence of timed points. One or multiple strokes form a gesture. 
  4.  */  
  5.   
  6. public class GestureStroke {  
  7. ...  
  8.     public final float length;  //length为手势行程的长度     
  9.     public final float[] points; //保存组成手势行程的多数个点的x,y坐标值   
  10.     private final long[] timestamps;//保存组成手势行程的多数个点的时间戳  
  11.   
  12.     /** 
  13.      * A constructor that constructs a gesture stroke from a list of gesture points. 
  14.      *  
  15.      * @param points 
  16.      */  
  17.     public GestureStroke(ArrayList points) {  
  18.         final int count = points.size();  
  19.         final float[] tmpPoints = new float[count * 2];  
  20.         final long[] times = new long[count];  
  21.   
  22.         RectF bx = null;  
  23.         float len = 0;  
  24.         int index = 0;  
  25.   
  26.         for (int i = 0; i < count; i++) {  
  27.             final GesturePoint p = points.get(i);  
  28.             tmpPoints[i * 2] = p.x; //偶数位置保存x值  
  29.             tmpPoints[i * 2 + 1] = p.y; //奇数位置保存x值  
  30.             times[index] = p.timestamp;  
  31.   
  32.             if (bx == null) {  
  33.                 bx = new RectF();  
  34.                 bx.top = p.y;  
  35.                 bx.left = p.x;  
  36.                 bx.right = p.x;  
  37.                 bx.bottom = p.y;  
  38.                 len = 0;  
  39.             } else {  
  40.                 //Math.pow(a,b)为a的b次方,如Maht.pow(3,2)等于9。下面的公式相当于平方和的根号值  
  41.                 len += Math.sqrt(Math.pow(p.x - tmpPoints[(i - 1) * 2], 2)  
  42.                         + Math.pow(p.y - tmpPoints[(i -1 ) * 2 + 1], 2));  
  43.                 //放大bx覆盖到指定的p.x, p.y点  
  44.                 bx.union(p.x, p.y);  
  45.             }  
  46.             index++;  
  47.         }  
  48.           
  49.         timestamps = times;  
  50.         this.points = tmpPoints;  
  51.         boundingBox = bx;  
  52.         length = len;  
  53.     }  
  54. ...  
  55. }  
     通过上面的代码可知,当我们创建GestureStroke的对象时,会执行GestureStroke的构造函数。而在GestureStroke的构造函数中,实现将传进来的mStrokeBuffer集合中封存的多个点进行遍历拆解出来,然后分别赋值给GestureStroke的数组成员变量points,timestamps,同时也根据点的坐标值计算出手势行程的长度length;

   ---->接着,将创建得到的GestureStroke对象通过调用Gesture的addStroke方法添加到Gesture类的mStrokes中,Gesture的addStroke方法源码实现如下:

[java]  view plain  copy
  1. /** 
  2.  * A gesture is a hand-drawn shape on a touch screen. It can have one or multiple strokes. 
  3.  * Each stroke is a sequence of timed points. A user-defined gesture can be recognized by  
  4.  * a GestureLibrary.  
  5.  */  
  6.   
  7. /*手势时是触摸屏上手势绘制的形状,它可以单笔画或者多笔画, 
  8.  * 每一个笔画是一个计时点序列,用户绘制定义的手势可以通过GestureLibrary来识别 
  9.  */  
  10. public class Gesture implements Parcelable {  
  11. ...  
  12.     private final ArrayList mStrokes = new ArrayList();  
  13.     ...  
  14.     /** 
  15.      * Adds a stroke to the gesture. 
  16.      *  
  17.      * @param stroke 
  18.      */  
  19.     public void addStroke(GestureStroke stroke) {  
  20.         mStrokes.add(stroke);  
  21.         ...  
  22.     }  
  23.     ...  
  24. ...  
  25. }  
      所以,在Gesture的成员mStrokes中存放着是用户在触摸屏上绘制形成的当前手势相关信息。在Gesture中会根据得到的mStrokes中这些信息去进行一些重要的处理,如将其序列化存储(serialize)、手势转化成bitmap显示(toBitmap)、还原手势的绘制路径(toPath)等;

    最后,针对手势组成类之间的关系进行一个小结:

    1). GesturePoint: 描述用户手指在屏幕位置的一个定时点,封装用户手指在屏幕上的点坐标值以及时间戳,时间戳由event.getEventTime()决定。

    2). GestureStroke:描述用户手指在屏幕上滑动到手指离开屏幕时所产生的轨迹线(由多个时间序列点形成),一个GestureStroke由多个GesturePoint组成。

    3). Gesture:实现Parcelable接口,描述用户完成绘制的完整手势,一个Gesture由单个或多个GestureStroke组成。手势绘制可通过GestureOverlayView.setGestureStrokeType(inttype)来设置单笔和多笔画。


Android手势源码浅析-----手势的保存和加载(GestureLibrary)

前言:在《Android手势源码浅析------手势的形成(Gesture)》文章中,介绍了手势Gesture的形成。那么,有的时候,用户绘制的手势是需要保存的,以便用户需要时加载出来进行相关的手势识别处理;接下来将结合一个Demo重点介绍源码中手势的保存和加载流程机制;

    一. 关于手势保存和加载的Demo       

         手势保存概要:

           1. 在绘制完手势后,需要将手势存入手势库中,手势最终会被解析存放在指定路径创建的文件中。

           2.  一般是GestureOverlayView添加实现监听器OnGesturePerformedListener,当绘制完手势时,会调用监听器的onGesturePerformed(GestureOverlayViewoverlay, Gesture gesture);

           3. onGesturePerformed方法的第二个参数geture(Gesture对象)就代表用户绘制完成后形成的手势;

           4.  将得到的Gesture对象通过调用GestureLibrary的addGesture方法存入手势库创建的指定文件中;

        以下举一个简单的Demo来说明第三方应用开发实现手势的保存和加载:

           主类代码如下:

[java]  view plain  copy
  1. package com.stevenhu.hu.dgt;  
  2.   
  3. import java.io.File;  
  4.   
  5. import android.app.Activity;  
  6. import android.app.AlertDialog;  
  7. import android.app.AlertDialog.Builder;  
  8. import android.content.DialogInterface;  
  9. import android.content.DialogInterface.OnClickListener;  
  10. import android.content.Intent;  
  11. import android.gesture.Gesture;  
  12. import android.gesture.GestureLibraries;  
  13. import android.gesture.GestureLibrary;  
  14. import android.gesture.GestureOverlayView;  
  15. import android.gesture.GestureOverlayView.OnGesturePerformedListener;  
  16. import android.graphics.Bitmap;  
  17. import android.os.Bundle;  
  18. import android.os.Environment;  
  19. import android.view.Menu;  
  20. import android.view.MenuItem;  
  21. import android.view.MenuItem.OnMenuItemClickListener;  
  22. import android.view.View;  
  23. import android.widget.EditText;  
  24. import android.widget.ImageView;  
  25. import android.widget.Toast;  
  26.   
  27. public class DrawGestureTest extends Activity implements OnGesturePerformedListener  
  28. {  
  29.       
  30.     private GestureOverlayView mDrawGestureView;  
  31.     private static GestureLibrary sStore;  
  32.       
  33.     /** Called when the activity is first created. */  
  34.     @Override  
  35.     public void onCreate(Bundle savedInstanceState)  
  36.     {  
  37.         super.onCreate(savedInstanceState);  
  38.         setContentView(R.layout.main);  
  39.           
  40.         mDrawGestureView = (GestureOverlayView)findViewById(R.id.gesture);  
  41.           
  42.         //设置手势可多笔画绘制,默认情况为单笔画绘制  
  43.         mDrawGestureView.setGestureStrokeType(GestureOverlayView.GESTURE_STROKE_TYPE_MULTIPLE);  
  44.         //设置手势的颜色(蓝色)  
  45.         mDrawGestureView.setGestureColor(gestureColor(R.color.gestureColor));  
  46.         //设置还没未能形成手势绘制是的颜色(红色)  
  47.         mDrawGestureView.setUncertainGestureColor(gestureColor(R.color.ungestureColor));  
  48.         //设置手势的粗细  
  49.         mDrawGestureView.setGestureStrokeWidth(4);  
  50.         /*手势绘制完成后淡出屏幕的时间间隔,即绘制完手指离开屏幕后相隔多长时间手势从屏幕上消失; 
  51.          * 可以理解为手势绘制完成手指离开屏幕后到调用onGesturePerformed的时间间隔 
  52.          * 默认值为420毫秒,这里设置为0.5秒 
  53.          */  
  54.         mDrawGestureView.setFadeOffset(500);  
  55.           
  56.         //绑定监听器  
  57.         mDrawGestureView.addOnGesturePerformedListener(this);  
  58.         //创建保存手势的手势库  
  59.         createStore();  
  60.     }  
  61.       
  62.     private void createStore()  
  63.     {  
  64.         File mStoreFile = null;   
  65.         /*判断mStoreFile是为空。 
  66.          * 判断手机是否插入SD卡,并且应用程序是否具有访问SD卡的权限 
  67.          */  
  68.         if (mStoreFile == null && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))  
  69.         {  
  70.             mStoreFile = new File(Environment.getExternalStorageDirectory(), "mygesture");  
  71.         }   
  72.           
  73.         if (sStore == null)  
  74.         {  
  75.             /* 另外三种创建保存手势文件的方式如下: 
  76.             //保存手势的文件在手机SD卡中 
  77.             sStore = GestureLibraries.fromFile(Environment.getExternalStorageDirectory().getAbsolutePath() + "mygesture"); 
  78.             sStore = GestureLibraries.fromPrivateFile(this, Environment.getExternalStorageDirectory().getAbsolutePath + "mygesture"); 
  79.             //保存手势的文件在应用程序的res/raw文件下 
  80.             sStore = GestureLibraries.fromRawResource(this, R.raw.gestures); 
  81.             */    
  82.             sStore = GestureLibraries.fromFile(mStoreFile);  
  83.         }  
  84.         testLoad();  
  85.     }  
  86.       
  87.     //测试保存手势的文件是否创建成功  
  88.     private void testLoad()  
  89.     {  
  90.         if (sStore.load())  
  91.         {  
  92.             showMessage("手势文件装载成功");  
  93.         }  
  94.         else  
  95.         {  
  96.             showMessage("手势文件装载失败");  
  97.         }  
  98.     }  
  99.       
  100.     public static GestureLibrary getStore()  
  101.     {  
  102.         return sStore;  
  103.     }  
  104.       
  105.     //手势绘制完成时调用  
  106.     @Override  
  107.     public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture)   
  108.     {  
  109.         // TODO Auto-generated method stub  
  110.         creatDialog(gesture);  
  111.     }  
  112.       
  113.     private void creatDialog(final Gesture gesture)  
  114.     {  
  115.         View dialogView = getLayoutInflater().inflate(R.layout.show_gesture, null);  
  116.         //imageView用于显示绘制的手势  
  117.         ImageView imageView = (ImageView) dialogView.findViewById(R.id.show);  
  118.         //获取用户保存手势的名字  
  119.         EditText editText = (EditText)dialogView.findViewById(R.id.name);  
  120.         final String name = editText.getText().toString();  
  121.         // 调用Gesture的toBitmap方法形成对应手势的位图  
  122.         final Bitmap bitmap = gesture.toBitmap(12812810, gestureColor(R.color.showColor));  
  123.         imageView.setImageBitmap(bitmap);  
  124.           
  125.         Builder dialogBuider = new AlertDialog.Builder(DrawGestureTest.this);  
  126.         dialogBuider.setView(dialogView);  
  127.         //绑定对话框的确认按钮监听事件  
  128.         dialogBuider.setPositiveButton(  
  129.                 "保存"new OnClickListener()  
  130.                 {  
  131.   
  132.                     @Override  
  133.                     public void onClick(DialogInterface dialog, int which)  
  134.                     {  
  135.                         // 添加手势  
  136.                         sStore.addGesture(name, gesture);  
  137.                         // 保存添加的手势  
  138.                         sStore.save();      
  139.                     }  
  140.                 });  
  141.         //绑定对话框的取消按钮监听事件  
  142.         dialogBuider.setNegativeButton("取消"new OnClickListener()  
  143.                 {  
  144.   
  145.                     @Override  
  146.                     public void onClick(DialogInterface dialog, int which)  
  147.                     {  
  148.                         // TODO Auto-generated method stub  
  149.                                                        
  150.                     }  
  151.                 });  
  152.         //显示对话框  
  153.         dialogBuider.show();  
  154.     }  
  155.       
  156.     private int gestureColor(int resId)  
  157.     {  
  158.         return getResources().getColor(resId);  
  159.     }  
  160.       
  161.     private void showMessage(String s)  
  162.     {  
  163.         Toast.makeText(this, s, Toast.LENGTH_SHORT).show();  
  164.     }  
  165.           
  166.     @Override  
  167.     protected void onDestroy()   
  168.     {  
  169.         // TODO Auto-generated method stub  
  170.         super.onDestroy();  
  171.         //移除绑定的监听器  
  172.         mDrawGestureView.removeOnGesturePerformedListener(this);  
  173.     }  
  174.   
  175. }  
         main.xml的代码如下:

[html]  view plain  copy
  1. xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="fill_parent"  
  4.     android:layout_height="fill_parent"  
  5.     android:orientation="vertical" >  
  6.   
  7.    <android.gesture.GestureOverlayView   
  8.        android:id="@+id/gesture"  
  9.        android:layout_width="fill_parent"  
  10.        android:layout_height="fill_parent"  
  11.        >      
  12.    android.gesture.GestureOverlayView>  
  13.   
  14. LinearLayout>  

         对话框对应的布局文件show_gesture.xml代码如下:

[html]  view plain  copy
  1. xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="fill_parent"  
  4.     android:layout_height="fill_parent"  
  5.     android:orientation="vertical" >  
  6.       
  7.     <LinearLayout   
  8.         android:orientation="horizontal"  
  9.         android:layout_width="fill_parent"  
  10.         android:layout_height="wrap_content">  
  11.           
  12.         <TextView   
  13.             android:layout_width="wrap_content"  
  14.             android:layout_height="wrap_content"  
  15.             android:layout_marginRight="8dip"  
  16.             android:text="@string/set_gesture_name"/>  
  17.         <EditText   
  18.             android:id="@+id/name"  
  19.             android:layout_width="wrap_content"  
  20.             android:layout_height="wrap_content"/>  
  21.             
  22.     LinearLayout>  
  23.   
  24.     <ImageView   
  25.         android:id="@+id/show"  
  26.         android:layout_gravity="center"  
  27.         android:layout_width="128dp"  
  28.         android:layout_height="128dp"  
  29.         android:layout_marginTop="10dp"/>  
  30. LinearLayout>  

        通过上面Demo代码的实现,可以知道手势库创建保存手势的文件有以下四种方式:

          1. GestureLibraries.fromFile(String path): GestureLibraries静态方法,参数path为文件的指定存放路径。返回的是FileGestureLibrary类型的对象;

          2. GestureLibraries.fromPrivateFile(Context context, String name):GestureLibraries静态方法,参数path为文件的指定存放路径;返回的是FileGestureLibrary类型的对象;

          3. GestureLibraries.fromFile(File path):GestureLibraries的静态方法,参数path为File对象,返回的是FileGestureLibrary类型的对象;

          4. GestureLibraries.romRawResource(Contextcontext, int resourceId):GestureLibraries的静态方法, 参数resourceId为文件所在的资源id,返回的是ResourceGestureLibrary类型的对象;

    二. 手势保存和加载源码浅析

         在分析源码之前,我们先来看看有关涉及到手势保存和加载源码类之间的关系,如下图:

手势绘制(GestureOverlayView)、手势的形成(Gesture)、手势的保存和加载(GestureLibrary)、手势识别的源码解析_第3张图片

    通过上图可以知道:

     1. GestureLibrary为抽象类,ResourceGestureLibrary和FileGestureLibrary均继承它;

     2. ResourceGestureLibrary和FileGestureLibrary又作为GestureLibraries的内部类;

     3. GestureLibrary类中的save和load方法为抽象方法,它们的具体实现在子类ResourceGestureLibrary和FileGestureLibrary中;

      通过上文Demo的介绍,我们知道,要想保持用户绘制的手势,前提是需要通过创建相应的手势库来实现;如下步骤:sStore = GestureLibraries.fromFile(mStoreFile)-->sStore.addGesture(name, gesture)-->sStore.save()

       接下来根据上面的保存手势步骤来分析源码中的实现:

        Step1: GestureLibraries.fromFile(mStoreFile):

[java]  view plain  copy
  1. public final class GestureLibraries {  
  2. ...  
  3. public static GestureLibrary fromFile(File path) {  
  4.         return new FileGestureLibrary(path);  
  5.     }  
  6. ...  
  7. }  
   该方法返回的是FileGestureLibrary对象,FileGestureLibrary为GestureLibraries内部类;

    FileGestureLibrary类的代码如下:

[java]  view plain  copy
  1. public final class GestureLibraries {  
  2. ...  
  3. private static class FileGestureLibrary extends GestureLibrary {  
  4.         private final File mPath;  
  5.   
  6.         public FileGestureLibrary(File path) {  
  7.             mPath = path;  
  8.         }  
  9.   
  10.         //手势库只读  
  11.         @Override  
  12.         public boolean isReadOnly() {  
  13.             return !mPath.canWrite();  
  14.         }  
  15.   
  16.         public boolean save() {  
  17.             if (!mStore.hasChanged()) return true;  
  18.   
  19.             final File file = mPath;  
  20.   
  21.             final File parentFile = file.getParentFile();  
  22.             if (!parentFile.exists()) {  
  23.                 if (!parentFile.mkdirs()) {  
  24.                     return false;  
  25.                 }  
  26.             }  
  27.   
  28.             boolean result = false;  
  29.             try {  
  30.                 //noinspection ResultOfMethodCallIgnored  
  31.                 file.createNewFile();  
  32.                 //通过文件输出流保存手势的相关信息  
  33.                 mStore.save(new FileOutputStream(file), true);  
  34.                 result = true;  
  35.             } catch (FileNotFoundException e) {  
  36.                 Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e);  
  37.             } catch (IOException e) {  
  38.                 Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e);  
  39.             }  
  40.   
  41.             return result;  
  42.         }  
  43.   
  44.         public boolean load() {  
  45.             boolean result = false;  
  46.             final File file = mPath;  
  47.             if (file.exists() && file.canRead()) {  
  48.                 try {  
  49.                     //通过文件输出流加载之前保存的手势信息  
  50.                     mStore.load(new FileInputStream(file), true);  
  51.                     result = true;  
  52.                 } catch (FileNotFoundException e) {  
  53.                     Log.d(LOG_TAG, "Could not load the gesture library from " + mPath, e);  
  54.                 } catch (IOException e) {  
  55.                     Log.d(LOG_TAG, "Could not load the gesture library from " + mPath, e);  
  56.                 }  
  57.             }  
  58.   
  59.             return result;  
  60.         }  
  61.     }  
  62. ...  
  63. }  

   FileGestureLibrary类中的代码实现简介:

   1).  isReadOnly():该方法实现判断所创建的保存手势文件是否可读;

    2). save():实现保存手势的重要方法,在该方法中,实例化所创建文件的输出流,然后根据输出流调用GestureStore的save(OutputStream stream, Boolean closeStream)方法,然后将GestureStore得到的有关手势的信息通过输出流写入文件;

    3). Load():该方法实现加载当前已保存手势的文件,当我们需要取出已保存的手势和当前手势进行相似度匹配时,就需要通过手势库加载之前保存的手势文件;

  Step2: FileGestureLibrary类没有addGesture方法,所以sStore.addGesture(name, gesture)方法的实现应该在它的父类GestureLibrary中,代码如下:

[java]  view plain  copy
  1. public abstract class GestureLibrary {  
  2. ...  
  3.     protected final GestureStore mStore;  
  4. ...  
  5.     //调用执行该方法后,接着要调用执行save(),否则添加不成功  
  6.     public void addGesture(String entryName, Gesture gesture) {  
  7.         mStore.addGesture(entryName, gesture);  
  8.     }  
  9. ...  
  10. }  
    Step3: 接着调用到GestureStore中的addGesture方法,如下:

[java]  view plain  copy
  1. public class GestureStore {  
  2. ...  
  3.     private final HashMap> mNamedGestures =  
  4.             new HashMap>();  
  5.   
  6.     private Learner mClassifier;  
  7. ...  
  8.     /** 
  9.      * Add a gesture for the entry 
  10.      *  
  11.      * @param entryName entry name 
  12.      * @param gesture 
  13.      */  
  14.       
  15.     //手势保存在一个ArrayList集合里,ArrayList又以entryName为key值保存在HashMap集合里  
  16.     public void addGesture(String entryName, Gesture gesture) {  
  17.         if (entryName == null || entryName.length() == 0) {  
  18.             return;  
  19.         }  
  20.         ArrayList gestures = mNamedGestures.get(entryName);  
  21.         if (gestures == null) {  
  22.             gestures = new ArrayList();  
  23.             mNamedGestures.put(entryName, gestures);  
  24.         }  
  25.         gestures.add(gesture);  
  26.         //通过gesture得到的Instance对象,存放到mClassifier对象的成员mInstances集合中  
  27.         mClassifier.addInstance(  
  28.                 Instance.createInstance(mSequenceType, mOrientationStyle, gesture, entryName));  
  29.         mChanged = true;  
  30.     }  
  31. ...  
  32. }  
     GestureStore的addGesture方法中代码实现如下:

       1). 实现将用户绘制的手势存放到mNamedGestures(HashMap类型)中;

       2). 通过用户绘制的gesture得到的Instance类型的对象(Instance.createInstance);

       3). 将Instance类型的对象存放到mClassifier对象(Learner类型)的成员mInstances集合中;
   Step4: 执行完sStore.addGesture(name, gesture)添加手势后,我们接着执行sStore.save()保存所添加的手势相关的信息。sStore.save()方法的实现在FileGestureLibrary中,代码如下:

[java]  view plain  copy
  1. public final class GestureLibraries {  
  2. ...  
  3.     private static class FileGestureLibrary extends GestureLibrary {  
  4.         private final File mPath;  
  5.     ...  
  6.         public boolean save() {  
  7.             if (!mStore.hasChanged()) return true;  
  8.   
  9.             final File file = mPath;  
  10.   
  11.             final File parentFile = file.getParentFile();  
  12.             if (!parentFile.exists()) {  
  13.                 if (!parentFile.mkdirs()) {  
  14.                     return false;  
  15.                 }  
  16.             }  
  17.   
  18.             boolean result = false;  
  19.             try {  
  20.                 //noinspection ResultOfMethodCallIgnored  
  21.                 file.createNewFile();  
  22.                 //通过文件输出流保存手势的相关信息  
  23.                 mStore.save(new FileOutputStream(file), true);  
  24.                 result = true;  
  25.             } catch (FileNotFoundException e) {  
  26.                 Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e);  
  27.             } catch (IOException e) {  
  28.                 Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e);  
  29.             }  
  30.   
  31.             return result;  
  32.         }  
  33.     ...  
  34.     }  
  35. ...  
  36. }  

      FileGestureLibrary的save方法中的代码实现:
      1). 通过传进来的File对象创建其对应的输出流(new FileOutputStream(file))
      2). 通过创建的输出流执行调用GestureStore的save方法(mStore.save(new FileOutputStream(file), true))
   Step5: GestureStore的save方法代码实现如下:

[java]  view plain  copy
  1. public class GestureStore {  
  2. ...  
  3.     private static final short FILE_FORMAT_VERSION = 1;  
  4.     private final HashMap> mNamedGestures =  
  5.             new HashMap>();  
  6. ...  
  7.     public void save(OutputStream stream, boolean closeStream) throws IOException {  
  8.         DataOutputStream out = null;  
  9.   
  10.         try {  
  11.             long start;  
  12.             if (PROFILE_LOADING_SAVING) {  
  13.                 start = SystemClock.elapsedRealtime();  
  14.             }  
  15.   
  16.             final HashMap> maps = mNamedGestures;  
  17.   
  18.             out = new DataOutputStream((stream instanceof BufferedOutputStream) ? stream :  
  19.                     new BufferedOutputStream(stream, GestureConstants.IO_BUFFER_SIZE));  
  20.             // Write version number  
  21.             //往文件中写入FILE_FORMAT_VERSION  
  22.             out.writeShort(FILE_FORMAT_VERSION);  
  23.             // Write number of entries  
  24.             //将ArrayList在mNamedGestures集合中的个数通过输出流写入文件  
  25.             out.writeInt(maps.size());  
  26.   
  27.             //遍历maps  
  28.             for (Map.Entry> entry : maps.entrySet()) {  
  29.                 final String key = entry.getKey();  
  30.                 final ArrayList examples = entry.getValue();  
  31.                 final int count = examples.size();  
  32.   
  33.                 // Write entry name  
  34.                 out.writeUTF(key); //将key值通过输出流写入文件  
  35.                 // Write number of examples for this entry  
  36.                 out.writeInt(count); //将rrayList集合中Gesture的个数通过输出流写入文件  
  37.   
  38.                 //遍历ArrayList中的Gesture且调用Gesture的serialize函数进行序列化写入相关信息  
  39.                 for (int i = 0; i < count; i++) {  
  40.                     examples.get(i).serialize(out);  
  41.                 }  
  42.             }  
  43.   
  44.             out.flush();  
  45.   
  46.             if (PROFILE_LOADING_SAVING) {  
  47.                 long end = SystemClock.elapsedRealtime();  
  48.                 Log.d(LOG_TAG, "Saving gestures library = " + (end - start) + " ms");  
  49.             }  
  50.   
  51.             mChanged = false;  
  52.         } finally {  
  53.             if (closeStream) GestureUtils.closeStream(out);  
  54.         }  
  55.     }  
  56. ...  
  57. }  

     GestureStore的save方法中代码实现如下:

      1). 将执行Step3中得到的mNamedGestures赋值给maps;

      2). 通过传进来的输出流创建对应的DataOutputStream类型对象out;

      3). 将FILE_FORMAT_VERSION和maps.size()写入out中;

      4). 遍历maps,将遍历出的每个ArrayList在maps中的key值和自身存放Gesture的个数count值,分别写入out中;

      5). 遍历ArrayList中的Gesture,然后将out作为实参调用执行Gesture的serialize方法;

     Step6:继续跟踪到 Gesture的serialize方法,代码如下:

[java]  view plain  copy
  1. public class Gesture implements Parcelable {  
  2. ...  
  3.     private long mGestureID;  
  4.     private final ArrayList mStrokes = new ArrayList();  
  5. ...  
  6.     public Gesture() {  
  7.         mGestureID = GESTURE_ID_BASE + sGestureCount.incrementAndGet();  
  8.     }  
  9. ...  
  10.     void serialize(DataOutputStream out) throws IOException {  
  11.         final ArrayList strokes = mStrokes;  
  12.         final int count = strokes.size();  
  13.   
  14.         // Write gesture ID  
  15.         out.writeLong(mGestureID); //写入GestureID  
  16.         // Write number of strokes   
  17.         out.writeInt(count);  //写入ArrayList集合中GestureStroke的个数  
  18.   
  19.         /*遍历ArrayList集合, 
  20.          * 同时调用GestureStroke的serialize函数向输出流中进行序列化写入相关信息 
  21.          */  
  22.         for (int i = 0; i < count; i++) {  
  23.             strokes.get(i).serialize(out);  
  24.         }  
  25.     }  
  26. ...  
  27. }  
      Gesture的serialize方法中代码实现如下:

      1). 将Gesture对应的mStrokes赋值给strokes;

      2). 将Gesture的mGestureID和GestureStroke在strokes中的个数count分别写入DataOutputStream类型的对象out;

      3). 遍历strokes中的GestureStroke,然后将out作为实参调用执行GestureStroke的serialize方法;

   Step7: 继续跟踪到 GestureStroke的serialize方法,代码如下:

[java]  view plain  copy
  1. public class GestureStroke {  
  2. ...  
  3.     public final float[] points; //保存组成手势行程的多数个点的x,y坐标值   
  4.     private final long[] timestamps;//保存组成手势行程的多数个点的时间戳  
  5. ...  
  6.     void serialize(DataOutputStream out) throws IOException {  
  7.         //points、timestamps分别由ArrayList中拆分得到  
  8.         final float[] pts = points;  
  9.         final long[] times = timestamps;  
  10.         final int count = points.length;  
  11.   
  12.         // Write number of points  
  13.         out.writeInt(count / 2);  
  14.   
  15.         for (int i = 0; i < count; i += 2) {  
  16.             // Write X  
  17.             out.writeFloat(pts[i]); //写入x轴对应的坐标值  
  18.             // Write Y  
  19.             out.writeFloat(pts[i + 1]); //写入y轴对应的坐标值  
  20.             // Write timestamp  
  21.             out.writeLong(times[i / 2]); //写入时间戳  
  22.         }  
  23.     }  
  24. ...  
  25. }  
     GestureStroke的serialize方法中代码实现如下:

     1). 将GestureStroke中对应的点数组points和时间戳数组timestamps分别赋值给数组pts和times

     2). 将GestureStroke中组成手势的点数count / 2写入DataOutputStream类型的对象out;(pts数组中每两个元素保存一个点对应的x,y值,所以,总点数为数组所有元素个数count除以2)

     3). 遍历数组pts,将每个点对应的x,y轴坐标值和时间戳分别写入out;

  关于手势保存源码的浅析就到此结束了,至于手势的加载sStore.load(),其实和手势的保存就是一个逆过程(一个是写入信息,一个读取加载信息)。如果熟悉了手势的保存机制,那么手势的加载机制就不言而喻了!


Android手势源码浅析----手势识别

 前言:手势识别在Android手势中是最重要的部分,基本上算是手势的精髓;手势识别的算法有点类似人脸识别;手势识别的利用很普遍,涉及到用户安全操作的领域也比较多;比如可以通过手势识别来实现手机的解锁,安全启动用户设置的用户模式应用等;

    一. 基于第三方开发的手势识别    

        一般情况下,如果需要将用户当前绘制手势和已保存的手势进行匹配。那么,在用户绘制完当前手势时就可以进行匹配处理。这个过程可以在手势监听器GesturePerformedListener的onGesturePerformed方法中处理。

       如下示例代码片段:

[java]  view plain  copy
  1. ...  
  2.             @Override  
  3.             public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture)  
  4.             {  
  5.                 // TODO Auto-generated method stub  
  6.                 final GestureLibrary store = MainActivity.getStore();  
  7.                 store.setOrientationStyle(4);  
  8.                 //识别用户刚刚所绘制的手势  
  9.                 ArrayList predictions = store.recognize(gesture);  
  10.                 //遍历所有找到的Prediction对象  
  11.                 for(Prediction pred : predictions)  
  12.                 {  
  13.                     //只有相似度大于2.0的手势才会被输出  
  14.                     if (pred.score > 2.0)  
  15.                     {  
  16.                         //testPActivityName();    
  17.                         Log.d("RecogniseGesture""name-->" + pred.name);  
  18.                         startApp(pred.name);  
  19.                         return;  
  20.                     }  
  21.                     else  
  22.                     {  
  23.                         //Log.d("FxRecogniseGesture", "无匹配手势");  
  24.                         new AlertDialog.Builder(RecogniseGesture.this).setMessage("不存在该手势").  
  25.                         setPositiveButton("确定"null).show();  
  26.                     }  
  27.                 }  
  28.                   
  29.             }  
  30. ...  

       通过上面的代码片段可知,调用GestureLibrary的recognize对当前用户绘制的手势进行匹配操作,recognize的参数gesture对应的是当前用户绘制的手势。recognize方法返回类型为ArrayList集合,该集合中的元素Prediction类的源代码如下:

[java]  view plain  copy
  1. package android.gesture;  
  2.   
  3. public class Prediction {  
  4.     public final String name;  
  5.   
  6.     public double score;  
  7.   
  8.     Prediction(String label, double predictionScore) {  
  9.         name = label;  
  10.         score = predictionScore;  
  11.     }  
  12.   
  13.     @Override  
  14.     public String toString() {  
  15.         return name;  
  16.     }  
  17. }  

        Prediction的属性name为被匹配的手势名字,score为手势的匹配分数(匹配分数越高,说明对手势的相似度匹配要求就越高)。所以,通过调用GestureLibrary的recognize方法返回的Prediction,就可以知道当前手势和已保存的手势匹配的相似度。

    二. 手势匹配源码实现

        在分析手势匹配源码实现之前,先总体来看看有关涉及到手势匹配相关的源码类之间的关系,如下图:

手势绘制(GestureOverlayView)、手势的形成(Gesture)、手势的保存和加载(GestureLibrary)、手势识别的源码解析_第4张图片

       上图中的相关类简介:

        GestureLibrary:手势库类,对外提供recognize函数,是实现手势匹配的关键入口。

        GestureStore:GestureLibrary的recognize函数真正的源码内部实现类。

        Instance:封装手势通过时间采样或空间采样后的离散点。该类的vector属性描述的就是当前被采样后的手势对应的多个离散点(这些点是有方向的,所以又可以称为向量)。

        Learner:抽象类,内部提供了对Instance进行添加、获取、移除操作的方法。同时提供了抽象方法classify。

        InstanceLearner:继承Learner,覆盖实现Learner中的抽象方法classify。

        GestureUtils:手势源码中的工具类。手势匹配源码实现,基本上最终都会通过调用该工具类中提供的方法来实现。

        GestureUtils类中相关的重点方法实现介绍:

         1). spatialSampling:对连续点进行空间采样(gesture由多个连续点组成)

         2). temporalSampling:  对连续点进行时间采样(gesture由多个连续点组成)

         3). computeCentroid:计算一组点的质心

         4). computeCoVariance: 计算一组点的方差-协方差矩阵

         5). computeTotalLength:  计算一组点的总长度

         6). computeStraightness:  计算一组点的直线度

         7). squaredEuclideanDistance:计算两个向量之间的平方欧式距离

         8). cosineDistance:  计算两个向量之间的余弦值,返回的是0到π之间的值

         9). minimumCosineDistance: 计算两个向量之前最小的余弦距离,参数vector1为之前保存的向量,vector2为当前输入的向量。

         10). computeOrientedBoundingBox:计算一个点集的最小边界框

         11). rotate:  旋转一组点

         12). translate:  移动一组点

         13). scale:  缩放一组点


       在前篇文章《手势的保存和加载》中,可以知道,用户绘制的手势是通过调用GestureLibrary的addGesture添加到手势库中,而该函数最终是通过调用GestureStore对象的addGesture来实现的。

        回顾一下GestureStore类中的addGesture方法实现代码,如下:

[java]  view plain  copy
  1. public class GestureStore {  
  2. ...  
  3.     private final HashMap> mNamedGestures =  
  4.             new HashMap>();  
  5.   
  6.     private Learner mClassifier;  
  7.   
  8.     private boolean mChanged = false;  
  9.   
  10.     public GestureStore() {  
  11.         mClassifier = new InstanceLearner();  
  12.     }  
  13.     //手势保存在一个ArrayList集合里,ArrayList又以entryName为key值保存在HashMap集合里  
  14.     public void addGesture(String entryName, Gesture gesture) {  
  15.         if (entryName == null || entryName.length() == 0) {  
  16.             return;  
  17.         }  
  18.         ArrayList gestures = mNamedGestures.get(entryName);  
  19.         if (gestures == null) {  
  20.             gestures = new ArrayList();  
  21.             mNamedGestures.put(entryName, gestures);  
  22.         }  
  23.         gestures.add(gesture);  
  24.         //通过gesture得到的Instance对象,存放到mClassifier对象(Learner类型)的成员mInstances集合中  
  25.         mClassifier.addInstance(  
  26.                 Instance.createInstance(mSequenceType, mOrientationStyle, gesture, entryName));  
  27.         mChanged = true;  
  28.     }  
  29. ...  
  30. }  
        在addGesture方法中:

         Step1. 根据保存的手势及手势名字通过调用Instance的静态方法createInstance,创建对应的Instance对象(参数),然后将创建的Instance对象通过调用Learner的addInstance方法保存到Learner的成员变量mInstance集合中(ArrayList类型)。

         Step2. 因此,Learner中的mInstance集合将保存着各手势对应的Instance对象。这样的话,在进行手势匹配时,就可以通过执行Learner的getInstances方法取出保存手势对应的Instance对象,然后将保存的Instance对象和当前绘制的手势创建的Instance对象进行匹配;

         对当前手势进行匹配是通过调用GestureLibrary的recognize方法实现的,该法返回描述匹配相似度的ArrayList集合。接下来对该方法的源码实现进行分析;

        --->GestureLibraryrecognize方法实现代码如下:

[java]  view plain  copy
  1. public abstract class GestureLibrary {  
  2.     protected final GestureStore mStore;  
  3.   
  4.     protected GestureLibrary() {  
  5.         mStore = new GestureStore();  
  6.     }  
  7.     ...  
  8.     public ArrayList recognize(Gesture gesture) {  
  9.         return mStore.recognize(gesture);  
  10.     }  
  11.     ...  
  12. }  
      通过上面代码可知,GestureLibraryrecognize方法通过调用GestureStore对象的recognize方法来实现的。  

      --->GestureStorerecognize方法实现代码如下:

[java]  view plain  copy
  1. public class GestureStore {  
  2.     ...  
  3.     public static final int SEQUENCE_SENSITIVE = 2;  
  4.     ...  
  5.     public static final int ORIENTATION_SENSITIVE = 2;  
  6.     ...  
  7.     private int mSequenceType = SEQUENCE_SENSITIVE;  
  8.     private int mOrientationStyle = ORIENTATION_SENSITIVE;  
  9.     ...  
  10.     private Learner mClassifier;  
  11.     ...  
  12.     public GestureStore() {  
  13.         mClassifier = new InstanceLearner();  
  14.     }  
  15.     ...  
  16.     public ArrayList recognize(Gesture gesture) {  
  17.         //根据gesture创建Instance对象  
  18.         Instance instance = Instance.createInstance(mSequenceType,  
  19.                 mOrientationStyle, gesture, null);  
  20.         //此处的instance.vector已经过时间采样或空间采样处理  
  21.         return mClassifier.classify(mSequenceType, mOrientationStyle, instance.vector);  
  22.     }  
  23.     ...  
  24. }  

      在GestureStorerecognize方法中:

        Step1.根据得到的mSequenceType值(默认值为SEQUENCE_SENSITIVE)、mOrientationStyle值(默认值为ORIENTATION_SENSITIVE)、gesture对象(当前手势),通过调用Instance的静态方法createInstance创建相应的Instance对象instance。

       Step2. 根据得到的mSequenceType值值、mOrientationStyle值、instance对象的属性vector,通过调用InstanceLearner对象的classify方法,方法返回的是ArrayList类型。

      --->Instance的静态方法createInstance代码实现如下:

[java]  view plain  copy
  1. class Instance {  
  2.     ...  
  3.     /** 
  4.      * create a learning instance for a single stroke gesture 
  5.      *  
  6.      * @param gesture 
  7.      * @param label 
  8.      * @return the instance 
  9.      */  
  10.     static Instance createInstance(int sequenceType, int orientationType, Gesture gesture, String label) {  
  11.         float[] pts;  
  12.         Instance instance;  
  13.         if (sequenceType == GestureStore.SEQUENCE_SENSITIVE) {  
  14.             //通过时间采样得到对应手势的样品pts  
  15.             pts = temporalSampler(orientationType, gesture);  
  16.             instance = new Instance(gesture.getID(), pts, label);  
  17.             //对pts手势样品进行正常化  
  18.             instance.normalize();  
  19.         } else {  
  20.             //通过空间采样得到对应手势的样品  
  21.             pts = spatialSampler(gesture);  
  22.             instance = new Instance(gesture.getID(), pts, label);  
  23.         }  
  24.         return instance;  
  25.     }  
  26.     ...  
  27. }  

     在Instance的静态方法createInstance中:

      Step1.根据得到的gesture中的第一个GestureStroke对象(一个Gesture对象由单个或多个GestureStroke组成)、SEQUENCE_SAMPLE_SIZE值(默认值为16),通过调用GestureUtils的静态方法temporalSampling将GestureStroke对象中封装的多个连续点进行时间采样成多个离散点。然后将得到的多个离散点赋值给浮点型数组pts。

      Step2.对通过时间采样得到的多个离散点进行一些处理操作(计算质心、移动、旋转等),然后将这些通过处理的离散点返回给Instance的createInstance静态方法中的局部变量pts。最终,通过执行temporalSampler(orientationType, gesture)后,局部变量pts存放的是,当前用户绘制的手势中的第一个GestureStroke(单笔画识别)对象经过时间采样生成的多个离散点。

      Step3.执行完temporalSampler(orientationType, gesture)得到pts后,接着,根据得到的当前手势的ID、pts、label(为空)创建Instance对象instance。在Instance的构造函数中,会将ID、pts、label分别赋值给Instance的属性id、vector、label。所以instance. vector即为当前用户绘制的手势经过时间采样后的离散点。

      Step4.在执行创建Instance对象后,通过调用Instance对象的normalize对离散点进行正常化。

       到此,Instance的temporalSampler方法就执行完了,接着会返回创建得到的Instance对象instance。

       --->回到GestureStore的recognize方法中:

       在执行完Instance的静态方法createInstance返回得到的instance对象后,接着继续执行mClassifier.classify(mSequenceType, mOrientationStyle, instance.vector),instance.vector就是已经经过时间采样或空间采样后得到的当前手势对应的多个离散点。mClassifier为InstanceLearner对象,InstanceLearner的classify方法代码实现如下:

[java]  view plain  copy
  1. class InstanceLearner extends Learner {  
  2.     ...  
  3.     //分类识别手势  
  4.     @Override  
  5.     ArrayList classify(int sequenceType, int orientationType, float[] vector) {  
  6.         ArrayList predictions = new ArrayList();  
  7.         ArrayList instances = getInstances();  
  8.         int count = instances.size();  
  9.         TreeMap label2score = new TreeMap();  
  10.         for (int i = 0; i < count; i++) {  
  11.             //取出之前保存的Instance和当前的Instance进行比较识别  
  12.             Instance sample = instances.get(i);  
  13.             if (sample.vector.length != vector.length) {  
  14.                 continue;  
  15.             }  
  16.             double distance;  
  17.             if (sequenceType == GestureStore.SEQUENCE_SENSITIVE) {     
  18.                 //最小的余弦值,此处的vector是经过时间采样的时间序列点  
  19.                 distance = GestureUtils.minimumCosineDistance(sample.vector, vector, orientationType);  
  20.             } else {  
  21.                 /*平方欧氏距离,欧式距离就是两点之间的距离:如a(x1,y1),b(x2,y2),则欧式距离为d = sqrt((x1-x2)^ + (x2-y2)^) 
  22.                  * vector是经过空间采样的点序列 
  23.                  */  
  24.                 distance = GestureUtils.squaredEuclideanDistance(sample.vector, vector);  
  25.             }  
  26.             double weight;  
  27.             if (distance == 0) {  
  28.                 weight = Double.MAX_VALUE;  
  29.             } else {  
  30.                 weight = 1 / distance;  
  31.             }  
  32.             Double score = label2score.get(sample.label);  
  33.             if (score == null || weight > score) {  
  34.                 label2score.put(sample.label, weight);  
  35.             }  
  36.         }  
  37.   
  38. //        double sum = 0;  
  39.         for (String name : label2score.keySet()) {  
  40.             double score = label2score.get(name);  
  41. //            sum += score;  
  42.             predictions.add(new Prediction(name, score));  
  43.         }  
  44.   
  45.         // normalize  
  46. //        for (Prediction prediction : predictions) {  
  47. //            prediction.score /= sum;  
  48. //        }  
  49.   
  50.         Collections.sort(predictions, sComparator);  
  51.   
  52.         return predictions;  
  53.     }  
  54.     ...  
  55. }  
    InstanceLearner的classify中做了如下处理:

     Step1.创建ArrayList集合对象predictions。获取保存在手势库中的所有Instance对象instances(ArrayList集合)。创建TreeMap类型映射表label2score,以保存手势的名字为键值,对应保存当前手势与保存手势之间的匹配分数score。

     Step2. 遍历手势库中已保存的instances集合中的每个Instance对象,将遍历出的每个Instance对象中封装的vector和当前Instance对象的vector(classify方法传进来的实参instance.vector)进行处理。

     Step3. 在遍历instances的处理中,当classify方法传进来的sequenceType为GestureStore.SEQUENCE_SENSITIVE(默认值)时,则根据遍历出的每个Instance对象中封装的vector和当前Instance对象的vector,调用GestureUtils的minimumCosineDistance方法,计算这两个vector的最小的余弦值。minimumCosineDistance返回distance(double类型)。

     Step4. 在Step3中,当sequenceType不为GestureStore.SEQUENCE_SENSITIVE时,则调用GestureUtils的squaredEuclideanDistance方法,计算这两个vector平方欧氏距离。squaredEuclideanDistance返回distance(double类型)。

     Step4. 通过Step3或Step4得到distance转化为手势匹配分数score(权重weight = 1/distance即为score), 然后将其以对应被遍历的Instance对象的名字(即已保存的某一手势对应的名字)为键值,保存到集合label2score中。

     Step5. 遍历完instances,得到label2score后,接着将label2score保存的信息进行遍历,将根据遍历得到的每个key值name和value值score,创建对应的Prediction对象,然后将创建得到的Prediction对象添加到集合predictions中。

     Step6. 对predictions集合中的内容进行排序,然后返回predictions。

     所以,调用GestureLibrary的recognize方法进行手势匹配操作,最终返回的是ArrayList集合predictions。遍历predictions中的Prediction对象,通过Prediction对象的score值就可以知道当前手势和已保存手势之间的匹配相似度(即手势匹配分数)。

转自CSDN博客 stevenhu_223博主,感谢博主的源码分析!

你可能感兴趣的:(Android)