Jul 6

Android手势识别的两种姿势

Lrdcq , 2016/07/06 15:30 , 程序 , 閱讀(3972) , Via 本站原創
在做比较复杂的界面交互的时候,不可避免的会遇到用户手势识别和处理。最简单的触控事件处理当然是对touch事件获得的MotionEvent对象进行记录与分析即可。当然,这种较为原始的方法并不适合复杂的交互处理,应该安卓sdk也提供了比较高级的手势(Gesture)处理机制。

1.GestureDetector

GestureDetector是最方便的处理手势的方式,创建它的时候需要实现一个listener,这是GestureDetector的核心处理接口。这个listener有两种,一种是:
public interface OnGestureListener {
    boolean onDown(MotionEvent var1);
    //当按下的时候
    void onShowPress(MotionEvent var1);
    //当按下之后且未立刻滑动
    boolean onSingleTapUp(MotionEvent var1);
  //轻触,当按下后立刻起来
    boolean onScroll(MotionEvent var1, MotionEvent var2, float var3, float var4);
    //滑动,手指在屏幕上移动
    void onLongPress(MotionEvent var1);
    //长按,按下后久未动
    boolean onFling(MotionEvent var1, MotionEvent var2, float var3, float var4);
    //划,手指在屏幕上滑动并离开时
}

可以看到通过这个listenr,可以基本清楚的处理大部分单点触摸和滑动的操作。另一个是:
public interface OnDoubleTapListener {
    boolean onSingleTapConfirmed(MotionEvent var1);
    //双击判断超时,确认是单击
    boolean onDoubleTap(MotionEvent var1);
    //双击
    boolean onDoubleTapEvent(MotionEvent var1);
    //双击第二次点击的event
}

可以清晰的判断出这个点击是不是双击并且处理双击中的事件。
当然,这两个listener也可以同时使用,sdk提供了一个合并的SimpleOnGestureListener作为基类且同时实现了这两个listener,只需要挑自己要使用的处理方法自己去实现内容就可以了。

不过注意的是,GestureDetector的事件总结起来都是单点操作的识别,对于多点操作怎么处理呢?
缩放,我们有ScaleGestureDetector可以处理两个手指精细缩放的手势。它提供的listener是:
public interface OnScaleGestureListener {
    boolean onScale(ScaleGestureDetector var1);
    //当缩放改变时
    boolean onScaleBegin(ScaleGestureDetector var1);
    //当开始缩放
    void onScaleEnd(ScaleGestureDetector var1);
    //当结束缩放
}

要注意的时,在onScaleBegin和onScale返回false会强制中断本次缩放手势识别与处理,别处就显而易见啦。
另一个多点识别手势是旋转,不过很遗憾,并没有RotateGestureDetector这样的东西,只有自己拆MotionEvent的内容来实现了,判断两个点相对位置的偏差来识别旋转角度了。MotionEvent中多点相关的api是:
getPointerCount();//获得触控点个数  
getPointerId(int pointerIndex);//返回触摸点的id 
getX(int pointerIndex);//返回触摸点X坐标
getY(int pointerIndex);//返回触摸点Y坐标

2.GestureLibrary

更复杂的手势,比如音乐播放器实现划左右的箭头"<"">"来实现上一首下一首切换,或者浏览器通过各种方向的L型手势来进行操作,或者通过打勾打叉画圈圈在确认操作,这比较注重手势交互的设计中,不可避免的会遇到这样复杂的手势场景。这类手势有一个特点:它是一个复杂的图案,它是一个完整的触控操作,从手指按下到抬起完成才能形成一个完整的手势图案。这种复杂的东西就交给GestureLibrary处理好了。

话说回来GestureLibrary是干嘛的呢,它可以用一个文件中读取一个或者一些列手势模版,Gesture对象,然后我们提供一些用户绘制的Gesture对象进行比对,得出一个相似度,我们再设一个阈值判断这个相似度就可以匹配到当前这个Gesture对象是不是某一个手势了。那么有两个问题:1.手势模版哪儿来。2.用户绘制操作怎么变成Gesture对象。事实上都是Gesture对象,而且来源只有一个——GestureOverlayView。

GestureOverlayView是一个framelayout的子类,它可以显示出在它上面正在绘制的手势并通过OnGesturePerformedListener返回一个Gesture对象给代码,这就是代码中Gesture对象的主要来源。GestureOverlayView和触控相关的属性主要都是和界面绘制手势的样式相关的,不过一般情况下我们会把绘制的线条隐藏掉,所以并没有什么卵用。

那么第二个问题,我们怎么储存模版文件呢?回到GestureLibrary,它的功能除了比对,最主要的是,从sd卡或者raw中读取一个模版文件,或者向sd卡中写入一个模版文件。这就很清晰了,事实上我们开发中要做的是,手动绘制出模版的手势并且通过GestureLibrary存储到sd卡中。从开发机的sd卡中把模版文件拷出来并放到项目的raw进行使用。为此,事实上android的sample中就有一个名为GestureBuilder的工程就是一个模版绘制和管理工具,用它就可以方便的准备好手势模版了。入下图:
點擊在新視窗中瀏覽此圖片

下面在项目中实际使用的地方就简单了,比如这样:
GestureOverlayView gestureOverlayView = (GestureOverlayView) findViewById(R.id.main_gol);
final GestureLibrary library = GestureLibraries.fromRawResource(MainActivity.this, R.raw.gestures);//获取手势文件
library.load();

gestureOverlayView.addOnGesturePerformedListener(new GestureOverlayView.OnGesturePerformedListener() {
    @Override
    public void onGesturePerformed(GestureOverlayView arg0, Gesture gesture) {
        //读出手势库中内容 识别手势
        ArrayList<Prediction> mygesture = library.recognize(gesture);
        Prediction predction = mygesture.get(0);
        if (predction.score >= 3.5) {//阈值匹配
            if (predction.name.equals("test_down")) {
                Toast.makeText(MainActivity.this, "向下手势", Toast.LENGTH_SHORT).show();
            }
        } else {
            Toast.makeText(MainActivity.this, "没有该手势", Toast.LENGTH_SHORT).show();
        }
    }
});

其中要注意的是,通过library.recognize(gesture)是将当前的手势和这个手势模版文件中所有的手势进行匹配,并且把所有手势的相似度作为Prediction对象(分数和手势名字)的list返回回来。list是安装相似度分数从高到低排列的,所以一般来说我们只看第一个即分数最高的就可以了。然后我们看看我们最相似的这个手势——的相似度达到我们的阈值没用——经过实测,阈值在3.5到5左右比较合适。当然,除了相似度,还要判断当前这个手势到底是哪一个,就通过名字判断吧,最终得到结果。
logo