前言:在《Android手势源码浅析------手势的形成(Gesture)》文章中,介绍了手势Gesture的形成。那么,有的时候,用户绘制的手势是需要保存的,以便用户需要时加载出来进行相关的手势识别处理;接下来将结合一个Demo重点介绍源码中手势的保存和加载流程机制;
手势保存概要:
1. 在绘制完手势后,需要将手势存入手势库中,手势最终会被解析存放在指定路径创建的文件中。
2. 一般是GestureOverlayView添加实现监听器OnGesturePerformedListener,当绘制完手势时,会调用监听器的onGesturePerformed(GestureOverlayViewoverlay, Gesture gesture);
3. onGesturePerformed方法的第二个参数geture(Gesture对象)就代表用户绘制完成后形成的手势;
4. 将得到的Gesture对象通过调用GestureLibrary的addGesture方法存入手势库创建的指定文件中;
以下举一个简单的Demo来说明第三方应用开发实现手势的保存和加载:
主类代码如下:
package com.stevenhu.hu.dgt; import java.io.File; import android.app.Activity; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; 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.os.Bundle; import android.os.Environment; import android.view.Menu; import android.view.MenuItem; import android.view.MenuItem.OnMenuItemClickListener; import android.view.View; import android.widget.EditText; import android.widget.ImageView; import android.widget.Toast; public class DrawGestureTest extends Activity implements OnGesturePerformedListener { private GestureOverlayView mDrawGestureView; private static GestureLibrary sStore; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mDrawGestureView = (GestureOverlayView)findViewById(R.id.gesture); //设置手势可多笔画绘制,默认情况为单笔画绘制 mDrawGestureView.setGestureStrokeType(GestureOverlayView.GESTURE_STROKE_TYPE_MULTIPLE); //设置手势的颜色(蓝色) mDrawGestureView.setGestureColor(gestureColor(R.color.gestureColor)); //设置还没未能形成手势绘制是的颜色(红色) mDrawGestureView.setUncertainGestureColor(gestureColor(R.color.ungestureColor)); //设置手势的粗细 mDrawGestureView.setGestureStrokeWidth(4); /*手势绘制完成后淡出屏幕的时间间隔,即绘制完手指离开屏幕后相隔多长时间手势从屏幕上消失; * 可以理解为手势绘制完成手指离开屏幕后到调用onGesturePerformed的时间间隔 * 默认值为420毫秒,这里设置为0.5秒 */ mDrawGestureView.setFadeOffset(500); //绑定监听器 mDrawGestureView.addOnGesturePerformedListener(this); //创建保存手势的手势库 createStore(); } private void createStore() { File mStoreFile = null; /*判断mStoreFile是为空。 * 判断手机是否插入SD卡,并且应用程序是否具有访问SD卡的权限 */ if (mStoreFile == null && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { mStoreFile = new File(Environment.getExternalStorageDirectory(), "mygesture"); } if (sStore == null) { /* 另外三种创建保存手势文件的方式如下: //保存手势的文件在手机SD卡中 sStore = GestureLibraries.fromFile(Environment.getExternalStorageDirectory().getAbsolutePath() + "mygesture"); sStore = GestureLibraries.fromPrivateFile(this, Environment.getExternalStorageDirectory().getAbsolutePath + "mygesture"); //保存手势的文件在应用程序的res/raw文件下 sStore = GestureLibraries.fromRawResource(this, R.raw.gestures); */ sStore = GestureLibraries.fromFile(mStoreFile); } testLoad(); } //测试保存手势的文件是否创建成功 private void testLoad() { if (sStore.load()) { showMessage("手势文件装载成功"); } else { showMessage("手势文件装载失败"); } } public static GestureLibrary getStore() { return sStore; } //手势绘制完成时调用 @Override public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) { // TODO Auto-generated method stub creatDialog(gesture); } private void creatDialog(final Gesture gesture) { View dialogView = getLayoutInflater().inflate(R.layout.show_gesture, null); //imageView用于显示绘制的手势 ImageView imageView = (ImageView) dialogView.findViewById(R.id.show); //获取用户保存手势的名字 EditText editText = (EditText)dialogView.findViewById(R.id.name); final String name = editText.getText().toString(); // 调用Gesture的toBitmap方法形成对应手势的位图 final Bitmap bitmap = gesture.toBitmap(128, 128, 10, gestureColor(R.color.showColor)); imageView.setImageBitmap(bitmap); Builder dialogBuider = new AlertDialog.Builder(DrawGestureTest.this); dialogBuider.setView(dialogView); //绑定对话框的确认按钮监听事件 dialogBuider.setPositiveButton( "保存", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 添加手势 sStore.addGesture(name, gesture); // 保存添加的手势 sStore.save(); } }); //绑定对话框的取消按钮监听事件 dialogBuider.setNegativeButton("取消", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub } }); //显示对话框 dialogBuider.show(); } private int gestureColor(int resId) { return getResources().getColor(resId); } private void showMessage(String s) { Toast.makeText(this, s, Toast.LENGTH_SHORT).show(); } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); //移除绑定的监听器 mDrawGestureView.removeOnGesturePerformedListener(this); } }main.xml的代码如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <android.gesture.GestureOverlayView android:id="@+id/gesture" android:layout_width="fill_parent" android:layout_height="fill_parent" > </android.gesture.GestureOverlayView> </LinearLayout>
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <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/set_gesture_name"/> <EditText android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout> <ImageView android:id="@+id/show" android:layout_gravity="center" android:layout_width="128dp" android:layout_height="128dp" android:layout_marginTop="10dp"/> </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类型的对象;
在分析源码之前,我们先来看看有关涉及到手势保存和加载源码类之间的关系,如下图:
通过上图可以知道:
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):
public final class GestureLibraries { ... public static GestureLibrary fromFile(File path) { return new FileGestureLibrary(path); } ... }该方法返回的是FileGestureLibrary对象,FileGestureLibrary为GestureLibraries内部类;
FileGestureLibrary类的代码如下:
public final class GestureLibraries { ... private static class FileGestureLibrary extends GestureLibrary { private final File mPath; public FileGestureLibrary(File path) { mPath = path; } //手势库只读 @Override public boolean isReadOnly() { return !mPath.canWrite(); } public boolean save() { if (!mStore.hasChanged()) return true; final File file = mPath; final File parentFile = file.getParentFile(); if (!parentFile.exists()) { if (!parentFile.mkdirs()) { return false; } } boolean result = false; try { //noinspection ResultOfMethodCallIgnored file.createNewFile(); //通过文件输出流保存手势的相关信息 mStore.save(new FileOutputStream(file), true); result = true; } catch (FileNotFoundException e) { Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e); } catch (IOException e) { Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e); } return result; } public boolean load() { boolean result = false; final File file = mPath; if (file.exists() && file.canRead()) { try { //通过文件输出流加载之前保存的手势信息 mStore.load(new FileInputStream(file), true); result = true; } catch (FileNotFoundException e) { Log.d(LOG_TAG, "Could not load the gesture library from " + mPath, e); } catch (IOException e) { Log.d(LOG_TAG, "Could not load the gesture library from " + mPath, e); } } return result; } } ... }
FileGestureLibrary类中的代码实现简介:
1). isReadOnly():该方法实现判断所创建的保存手势文件是否可读;
2). save():实现保存手势的重要方法,在该方法中,实例化所创建文件的输出流,然后根据输出流调用GestureStore的save(OutputStream stream, Boolean closeStream)方法,然后将GestureStore得到的有关手势的信息通过输出流写入文件;
3). Load():该方法实现加载当前已保存手势的文件,当我们需要取出已保存的手势和当前手势进行相似度匹配时,就需要通过手势库加载之前保存的手势文件;
Step2: FileGestureLibrary类没有addGesture方法,所以sStore.addGesture(name, gesture)方法的实现应该在它的父类GestureLibrary中,代码如下:
public abstract class GestureLibrary { ... protected final GestureStore mStore; ... //调用执行该方法后,接着要调用执行save(),否则添加不成功 public void addGesture(String entryName, Gesture gesture) { mStore.addGesture(entryName, gesture); } ... }Step3: 接着调用到GestureStore中的addGesture方法,如下:
public class GestureStore { ... private final HashMap<String, ArrayList<Gesture>> mNamedGestures = new HashMap<String, ArrayList<Gesture>>(); private Learner mClassifier; ... /** * Add a gesture for the entry * * @param entryName entry name * @param gesture */ //手势保存在一个ArrayList集合里,ArrayList又以entryName为key值保存在HashMap集合里 public void addGesture(String entryName, Gesture gesture) { if (entryName == null || entryName.length() == 0) { return; } ArrayList<Gesture> gestures = mNamedGestures.get(entryName); if (gestures == null) { gestures = new ArrayList<Gesture>(); mNamedGestures.put(entryName, gestures); } gestures.add(gesture); //通过gesture得到的Instance对象,存放到mClassifier对象的成员mInstances集合中 mClassifier.addInstance( Instance.createInstance(mSequenceType, mOrientationStyle, gesture, entryName)); mChanged = true; } ... }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中,代码如下:
public final class GestureLibraries { ... private static class FileGestureLibrary extends GestureLibrary { private final File mPath; ... public boolean save() { if (!mStore.hasChanged()) return true; final File file = mPath; final File parentFile = file.getParentFile(); if (!parentFile.exists()) { if (!parentFile.mkdirs()) { return false; } } boolean result = false; try { //noinspection ResultOfMethodCallIgnored file.createNewFile(); //通过文件输出流保存手势的相关信息 mStore.save(new FileOutputStream(file), true); result = true; } catch (FileNotFoundException e) { Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e); } catch (IOException e) { Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e); } return result; } ... } ... }
FileGestureLibrary的save方法中的代码实现:
1). 通过传进来的File对象创建其对应的输出流(new FileOutputStream(file))
2). 通过创建的输出流执行调用GestureStore的save方法(mStore.save(new FileOutputStream(file), true))
Step5: GestureStore的save方法代码实现如下:
public class GestureStore { ... private static final short FILE_FORMAT_VERSION = 1; private final HashMap<String, ArrayList<Gesture>> mNamedGestures = new HashMap<String, ArrayList<Gesture>>(); ... public void save(OutputStream stream, boolean closeStream) throws IOException { DataOutputStream out = null; try { long start; if (PROFILE_LOADING_SAVING) { start = SystemClock.elapsedRealtime(); } final HashMap<String, ArrayList<Gesture>> maps = mNamedGestures; out = new DataOutputStream((stream instanceof BufferedOutputStream) ? stream : new BufferedOutputStream(stream, GestureConstants.IO_BUFFER_SIZE)); // Write version number //往文件中写入FILE_FORMAT_VERSION out.writeShort(FILE_FORMAT_VERSION); // Write number of entries //将ArrayList<Gesture>在mNamedGestures集合中的个数通过输出流写入文件 out.writeInt(maps.size()); //遍历maps for (Map.Entry<String, ArrayList<Gesture>> entry : maps.entrySet()) { final String key = entry.getKey(); final ArrayList<Gesture> examples = entry.getValue(); final int count = examples.size(); // Write entry name out.writeUTF(key); //将key值通过输出流写入文件 // Write number of examples for this entry out.writeInt(count); //将rrayList<Gesture>集合中Gesture的个数通过输出流写入文件 //遍历ArrayList<Gesture>中的Gesture且调用Gesture的serialize函数进行序列化写入相关信息 for (int i = 0; i < count; i++) { examples.get(i).serialize(out); } } out.flush(); if (PROFILE_LOADING_SAVING) { long end = SystemClock.elapsedRealtime(); Log.d(LOG_TAG, "Saving gestures library = " + (end - start) + " ms"); } mChanged = false; } finally { if (closeStream) GestureUtils.closeStream(out); } } ... }
GestureStore的save方法中代码实现如下:
1). 将执行Step3中得到的mNamedGestures赋值给maps;
2). 通过传进来的输出流创建对应的DataOutputStream类型对象out;
3). 将FILE_FORMAT_VERSION和maps.size()写入out中;
4). 遍历maps,将遍历出的每个ArrayList<Gesture>在maps中的key值和自身存放Gesture的个数count值,分别写入out中;
5). 遍历ArrayList<Gesture>中的Gesture,然后将out作为实参调用执行Gesture的serialize方法;
Step6:继续跟踪到 Gesture的serialize方法,代码如下:
public class Gesture implements Parcelable { ... private long mGestureID; private final ArrayList<GestureStroke> mStrokes = new ArrayList<GestureStroke>(); ... public Gesture() { mGestureID = GESTURE_ID_BASE + sGestureCount.incrementAndGet(); } ... void serialize(DataOutputStream out) throws IOException { final ArrayList<GestureStroke> strokes = mStrokes; final int count = strokes.size(); // Write gesture ID out.writeLong(mGestureID); //写入GestureID // Write number of strokes out.writeInt(count); //写入ArrayList<GestureStroke>集合中GestureStroke的个数 /*遍历ArrayList<GestureStroke>集合, * 同时调用GestureStroke的serialize函数向输出流中进行序列化写入相关信息 */ for (int i = 0; i < count; i++) { strokes.get(i).serialize(out); } } ... }Gesture的serialize方法中代码实现如下:
1). 将Gesture对应的mStrokes赋值给strokes;
2). 将Gesture的mGestureID和GestureStroke在strokes中的个数count分别写入DataOutputStream类型的对象out;
3). 遍历strokes中的GestureStroke,然后将out作为实参调用执行GestureStroke的serialize方法;
Step7: 继续跟踪到 GestureStroke的serialize方法,代码如下:
public class GestureStroke { ... public final float[] points; //保存组成手势行程的多数个点的x,y坐标值 private final long[] timestamps;//保存组成手势行程的多数个点的时间戳 ... void serialize(DataOutputStream out) throws IOException { //points、timestamps分别由ArrayList<GesturePoint>中拆分得到 final float[] pts = points; final long[] times = timestamps; final int count = points.length; // Write number of points out.writeInt(count / 2); for (int i = 0; i < count; i += 2) { // Write X out.writeFloat(pts[i]); //写入x轴对应的坐标值 // Write Y out.writeFloat(pts[i + 1]); //写入y轴对应的坐标值 // Write timestamp out.writeLong(times[i / 2]); //写入时间戳 } } ... }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(),其实和手势的保存就是一个逆过程(一个是写入信息,一个读取加载信息)。如果熟悉了手势的保存机制,那么手势的加载机制就不言而喻了!