前言:在《Android手势源码浅析------手势的形成(Gesture)》文章中,介绍了手势Gesture的形成。那么,有的时候,用户绘制的手势是需要保存的,以便用户需要时加载出来进行相关的手势识别处理;接下来将结合一个Demo重点介绍源码中手势的保存和加载流程机制;
一. 关于手势保存和加载的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;
-
-
- @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);
-
-
-
-
- mDrawGestureView.setFadeOffset(500);
-
-
- mDrawGestureView.addOnGesturePerformedListener(this);
-
- createStore();
- }
-
- private void createStore()
- {
- File mStoreFile = null;
-
-
-
- if (mStoreFile == null && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
- {
- mStoreFile = new File(Environment.getExternalStorageDirectory(), "mygesture");
- }
-
- if (sStore == null)
- {
-
-
-
-
-
-
-
- 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)
- {
-
- creatDialog(gesture);
- }
-
- private void creatDialog(final Gesture gesture)
- {
- View dialogView = getLayoutInflater().inflate(R.layout.show_gesture, null);
-
- ImageView imageView = (ImageView) dialogView.findViewById(R.id.show);
-
- EditText editText = (EditText)dialogView.findViewById(R.id.name);
- final String name = editText.getText().toString();
-
- 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)
- {
-
-
- }
- });
-
- 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()
- {
-
- 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>
对话框对应的布局文件show_gesture.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" >
-
- <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 {
-
- 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;
- ...
-
- public void addGesture(String entryName, Gesture gesture) {
- mStore.addGesture(entryName, gesture);
- }
- ...
- }
Step3: 接着调用到GestureStore中的addGesture方法,如下:
- public class GestureStore {
- ...
- private final HashMap> mNamedGestures =
- new HashMap>();
-
- private Learner mClassifier;
- ...
-
-
-
-
-
-
-
-
- public void addGesture(String entryName, Gesture gesture) {
- if (entryName == null || entryName.length() == 0) {
- return;
- }
- ArrayList gestures = mNamedGestures.get(entryName);
- if (gestures == null) {
- gestures = new ArrayList();
- mNamedGestures.put(entryName, gestures);
- }
- gestures.add(gesture);
-
- 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 {
-
- 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> mNamedGestures =
- new HashMap>();
- ...
- public void save(OutputStream stream, boolean closeStream) throws IOException {
- DataOutputStream out = null;
-
- try {
- long start;
- if (PROFILE_LOADING_SAVING) {
- start = SystemClock.elapsedRealtime();
- }
-
- final HashMap> maps = mNamedGestures;
-
- out = new DataOutputStream((stream instanceof BufferedOutputStream) ? stream :
- new BufferedOutputStream(stream, GestureConstants.IO_BUFFER_SIZE));
-
-
- out.writeShort(FILE_FORMAT_VERSION);
-
-
- out.writeInt(maps.size());
-
-
- for (Map.Entry> entry : maps.entrySet()) {
- final String key = entry.getKey();
- final ArrayList examples = entry.getValue();
- final int count = examples.size();
-
-
- out.writeUTF(key);
-
- out.writeInt(count);
-
-
- 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在maps中的key值和自身存放Gesture的个数count值,分别写入out中;
5). 遍历ArrayList中的Gesture,然后将out作为实参调用执行Gesture的serialize方法;
Step6:继续跟踪到 Gesture的serialize方法,代码如下:
- public class Gesture implements Parcelable {
- ...
- private long mGestureID;
- private final ArrayList mStrokes = new ArrayList();
- ...
- public Gesture() {
- mGestureID = GESTURE_ID_BASE + sGestureCount.incrementAndGet();
- }
- ...
- void serialize(DataOutputStream out) throws IOException {
- final ArrayList strokes = mStrokes;
- final int count = strokes.size();
-
-
- out.writeLong(mGestureID);
-
- out.writeInt(count);
-
-
-
-
- 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;
- private final long[] timestamps;
- ...
- void serialize(DataOutputStream out) throws IOException {
-
- final float[] pts = points;
- final long[] times = timestamps;
- final int count = points.length;
-
-
- out.writeInt(count / 2);
-
- for (int i = 0; i < count; i += 2) {
-
- out.writeFloat(pts[i]);
-
- out.writeFloat(pts[i + 1]);
-
- 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(),其实和手势的保存就是一个逆过程(一个是写入信息,一个读取加载信息)。如果熟悉了手势的保存机制,那么手势的加载机制就不言而喻了!