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

    前言:在《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;
	
    /** 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>

         对话框对应的布局文件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 {
                //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(),其实和手势的保存就是一个逆过程(一个是写入信息,一个读取加载信息)。如果熟悉了手势的保存机制,那么手势的加载机制就不言而喻了!

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值