Android基础系列 - 手势识别运用

Posted SingleShu888

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android基础系列 - 手势识别运用相关的知识,希望对你有一定的参考价值。

android开发中,几乎所有的事件都会和用户进行交互,而最多的交互形式就是手势了。也有很多手势三方的库。比如 android-gesture-detectorsAndroidPdfViewer(这个库支持pdf文档显示,支持手势),这些三方库都是不错的手势识别。不过今天我们来看看谷歌提供的原生手势识别的相关类和使用方式,以及一些简单的运用。

手势呢,大概分为两个大类别,一种是左滑右滑,Google提供了手势检测并提供了相应的监听器,另一种就是画个圆圈,正方形登特殊手势。这种手势需要开发者自己添加手势识别,并提供了相关的API识别用户手势。GestureDetector,提供了简单手势识别和监听回调。GestureDetector谷歌官方文档地址:https://developer.android.com/reference/android/view/GestureDetector.html。GestureDetector主要使用在于手势的的回调监听, GestureDetector.OnGestureListener, GestureDetector.SimpleOnGestureListener,GestureDetector.OnDoubleTapListener。这些监听器才是关键,后面慢慢会讲到。

那么先看看GestureDetector的方法。

  • isLongpressEnabled (),是否允许长点击

  • onTouchEvent(MotionEvent ev) ,拦截事件进行处理。比如在哪个Activity设置手势识别,那么就需要重写该Activity的onTouchEvent。将事件处理转交给GestureDetetor。

  • setIsLongpressEnabled(boolean isLongpressEnabled) ,设置是否允许长点击。

  • setOnDoubleTapListener(GestureDetector.OnDoubleTapListener onDoubleTapListener) ,设置双点击事件回调监听。

GestureDetector的方法,大概也就这些了。关键在于这些手势识别的接口,那么对这些接口的触发以及意义,都挨着使用一次。


GestureDetector.OnGestureListener

这个是常用的接口,作用手势识别的回调。它所有的方法以及意义。

  • OnDown(MotionEvent e):用户按下屏幕就会触发;

  • onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) ,滑屏,用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE, 1个ACTION_UP触发 参数解释: e1:第1个ACTION_DOWN MotionEvent e2:最后一个ACTION_MOVE MotionEvent velocityX:X轴上的移动速度,像素/秒 velocityY:Y轴上的移动速度,像素/秒。

  • onLongPress(MotionEvent e) ,长按触摸屏,超过一定时长,就会触发这个事件 触发顺序: onDown->onShowPress->onLongPress。

  • onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) ,在屏幕上拖动事件。无论是用手拖动view,或者是以抛的动作滚动,都会多次触发,这个方法 在ACTION_MOVE动作发生时就会触发 滑屏:手指触动屏幕后,稍微滑动后立即松开 onDown—–》onScroll—-》onScroll—-》onScroll—-》………—–>onFling 拖动 onDown——》onShowPress —》onScroll—-》onScroll——》onFiling 可见,无论是滑屏,还是拖动,影响的只是中间OnScroll触发的数量多少而已,最终都会触发onFling事件!

  • onShowPress(MotionEvent e) ,如果是按下的时间超过瞬间,而且在按下的时候没有松开或者是拖动的,那么onShowPress就会执行。这个时间是底层封装,设定的。开发者在应用层只需应用,不必知晓时间设定以及原理。

  • onSingleTapUp(MotionEvent e) ,顾名思义,一次单独的轻击抬起操作,也就是轻击一下屏幕,立刻抬起来,才会有这个触发,当然,如果除了Down以外还有其它操作,那就不再算是Single操作了,所以也就不会触发这个事件 触发顺序: 点击一下非常快的(不滑动)Touchup: onDown->onSingleTapUp->onSingleTapConfirmed 点击一下稍微慢点的(不滑动)Touchup: onDown->onShowPress->onSingleTapUp->onSingleTapConfirmed。

这些方法就是在用户一些基本手势交互的时候回调。那么写一个简单实例来看看。

public class MainActivity extends AppCompatActivity implements GestureDetector.OnGestureListener 

    GestureDetector detector;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        detector = new GestureDetector(this,this);
        detector.setIsLongpressEnabled(true);
    

    //重写Activity的onTouchEvent,将Activity的事件
    @Override
    public boolean onTouchEvent(MotionEvent event) 
        return detector.onTouchEvent(event);
    

    // 用户轻触触摸屏,由1个MotionEvent ACTION_DOWN触发
    @Override
    public boolean onDown(MotionEvent motionEvent) 
        Log.i("tag00", "================================");
        Log.i("tag00", "onDown");
        return false;
    

    /*
    * 用户轻触触摸屏,尚未松开或拖动,由一个1个MotionEvent ACTION_DOWN触发
    * 注意和onDown()的区别,强调的是没有松开或者拖动的状态
    *
    * 而onDown也是由一个MotionEventACTION_DOWN触发的,但是他没有任何限制,
    * 也就是说当用户点击的时候,首先MotionEventACTION_DOWN,onDown就会执行,
    * 如果在按下的瞬间没有松开或者是拖动的时候onShowPress就会执行,如果是按下的时间超过瞬间
    * (这块我也不太清楚瞬间的时间差是多少,一般情况下都会执行onShowPress),拖动了,就不执行onShowPress。
    */
    @Override
    public void onShowPress(MotionEvent motionEvent) 
        Log.i("tag00", "onShowPress");
    

    // 用户(轻触触摸屏后)松开,由一个1个MotionEvent ACTION_UP触发
    // 轻击一下屏幕,立刻抬起来,才会有这个触发
    // 从名子也可以看出,一次单独的轻击抬起操作,当然,如果除了Down以外还有其它操作,那就不再算是Single操作了,所以这个事件 就不再响应
    @Override
    public boolean onSingleTapUp(MotionEvent motionEvent) 
        Log.i("tag00", "onSingleTapUp");
        return false;
    

    // 用户按下触摸屏,并拖动,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE触发
    @Override
    public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) 
        Log.i("tag00", "onScroll");
        return false;
    

    // 用户长按触摸屏,由多个MotionEvent ACTION_DOWN触发
    @Override
    public void onLongPress(MotionEvent motionEvent) 
        Log.i("tag00", "onLongPress");
    

    // 用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE, 1个ACTION_UP触发
    @Override
    public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) 
        Log.i("tag00", "onFling");
        return false;
    

我的手势依次是:
1、轻触,快速拿起。(1~2S)
2、稍长按,再拿起。(4~6S)
3、长按,再拿起。(8S~)
4、从左至右滑动,再拿起,类似翻页。
5、画圆圈。

I/tag00: ================================
I/tag00: onDown
I/tag00: onSingleTapUp
I/tag00: ================================
I/tag00: onDown
I/tag00: onShowPress
I/tag00: onSingleTapUp
I/tag00: ================================
I/tag00: onDown
I/tag00: onShowPress
I/tag00: onLongPress
I/tag00: ================================
I/tag00: onDown
I/tag00: onScroll
I/tag00: onScroll
I/tag00: onScroll
I/tag00: onScroll
I/tag00: onScroll
I/tag00: onScroll
I/tag00: onScroll
I/tag00: onFling
I/tag00: ================================
I/tag00: onDown
I/tag00: onShowPress
I/tag00: onScroll
I/tag00: onScroll
I/tag00: onFling

GestureDetector.OnDoubleTapListener

双击监听接口,方法如下

  • onSingleTapConfirmed(MotionEvent e):单 击事件。用来判定该次点击是SingleTap而不是DoubleTap,如果连续点击两次就是DoubleTap手势,如果只点击一次,系统等待一段时 间后没有收到第二次点击则判定该次点击为SingleTap而不是DoubleTap,然后触发 SingleTapConfirmed事件。触发顺序 是:OnDown->OnsingleTapUp->OnsingleTapConfirmed ,关于 onSingleTapConfirmed和onSingleTapUp的一点区别: OnGestureListener有这样的一个方法onSingleTapUp,和onSingleTapConfirmed容易混淆。二者的区别 是:onSingleTapUp,只要手抬起就会执行,而对于onSingleTapConfirmed来说,如果双击的话,则 onSingleTapConfirmed不会执行。

  • onDoubleTap(MotionEvent e):双击事件

  • onDoubleTapEvent(MotionEvent e):双击间隔中发生的动作。指触发onDoubleTap以后,在双击之间发生的其它动作,包含down、up和move事件。

    双击所对应的触发事件顺序:

轻轻单击一下,对应的事件触发顺序为:


GestureDetector.SimpleOnGestureListener

它与前两个不同的是:
1、这是一个类,在它基础上新建类的话,要用extends派生而不是用implements继承!
2、 OnGestureListener和OnDoubleTapListener接口里的函数都是强制必须重写的,即使用不到也要重写出来一个空函数但在 SimpleOnGestureListener类的实例或派生类中不必如此,可以根据情况,用到哪个函数就重写哪个函数,因为 SimpleOnGestureListener类本身已经实现了这两个接口的所有函数,只是里面全是空的而已。

介绍的差不多了,那么先来小试牛刀。

package com.example.administrator.myapplication;

import android.app.Activity;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.widget.ImageView;
/**
 * 手势识别类
 * 图片切换显示,实现接口OnGestureListener
 * @author Administrator
 *
 */
public class MyGestrueTest extends Activity implements OnGestureListener
    /**手势管理器*/
    private GestureDetector detector;
    /**图片资源数组*/
    private int [] imageId;

    /**图片视图*/
    private ImageView imageView ;
    private ImageView[] imgView;

    /**图片下标计数器*/
    private int count=0;

    @Override
    protected void onCreate(Bundle savedInstanceState) 

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my_gestrue_test);
        //创建手势管理器
        detector = new GestureDetector(this);
        //得到图片视图
        imageView=(ImageView) findViewById(R.id.image_gestrue);

        //得到图片资源的Id
        imageId = new int[]
                R.drawable.timg,
                R.drawable.index,
                R.drawable.index1,
                R.drawable.img3,
                R.drawable.img4;
        //得到图片视图 ImageView
        imgView = new ImageView []
                (ImageView) findViewById(R.id.small_image1),
                (ImageView) findViewById(R.id.small_image2),
                (ImageView) findViewById(R.id.small_image3),
                (ImageView) findViewById(R.id.small_image4),
                (ImageView) findViewById(R.id.small_image5);

    
    @Override
    public boolean onTouchEvent(MotionEvent event) 
        //将touch事件交给手势管理器来处理
        return detector.onTouchEvent(event);
    
    @Override
    public boolean onDown(MotionEvent e) 
        return false;
    
    /**
     * 手势滑动触发的事件
     */
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                           float velocityY) 
        //从右往左移动
        if(e1.getX()-e2.getX()>50)
            //下标++
            count++;
            if(count<=4)
                //切换到下一张
                imageView.setImageResource(imageId[count]);

                imgView[count].setImageResource(R.drawable.ic_launcher1);

                imgView[count-1].setImageResource(R.mipmap.ic_launcher);
            else
                //滑动到最后一张,该方向不会再滑动,就停留在最后一张
                count=4;
                imageView.setImageResource(imageId[count]);
            
        
        //从左往右移动
        if(e1.getX()-e2.getX()<50)

            count--;

            if(count>=0)
                imageView.setImageResource(imageId[count]);
                imgView[count].setImageResource(R.drawable.ic_launcher1);
                imgView[count+1].setImageResource(R.mipmap.ic_launcher);
            else
                count=0;
                imageView.setImageResource(imageId[count]);
            
        
        return false;
    

    @Override
    public void onLongPress(MotionEvent e) 
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
                            float distanceY) 
        return false;
    
    @Override
    public void onShowPress(MotionEvent e) 
    @Override
    public boolean onSingleTapUp(MotionEvent e) 
        return false;
    

布局代码:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000">
    <LinearLayout
        android:id="@+id/my_geture_test_1"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <ImageView
            android:id="@+id/image_gestrue"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src="@drawable/timg" >
        </ImageView>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:gravity="center_horizontal"
        android:layout_marginBottom="30dp"
        >
        <ImageView
            android:id="@+id/small_image1"
            android:padding="5dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher"
            >
        </ImageView>

        <ImageView
            android:id="@+id/small_image2"
            android:padding="5dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher" >
        </ImageView>

        <ImageView
            android:id="@+id/small_image3"
            android:padding="5dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher" >
        </ImageView>

        <ImageView
            android:id="@+id/small_image4"
            android:padding="5dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher" >
        </ImageView>
        <ImageView
            android:id="@+id/small_image5"
            android:padding="5dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher" >
        </ImageView>
    </LinearLayout>
</RelativeLayout>

效果:

可以发现,并没有使用ViewPager这样的控件,虽然没有左右滑动的效果,那个可以在后期优化加上,这里只是做个简单的例子。所以没做太多的优化,毕竟时间有限啊。这是简单的手势识别了。谷歌还提供了自定义手势,通过GestureOverlayView和GestureLibrary增加自定义手势。还提供了缩放手势的识别类ScaleGestureDetector 。之后有时间会继续介绍手势相关。觉得有用,顶一下呗,整理这些内容也不容易~挺花时间的。谢谢大家。

以上是关于Android基础系列 - 手势识别运用的主要内容,如果未能解决你的问题,请参考以下文章

Android应用开发基础篇(13)-----GestureDetector(手势识别)

快速推迟手势识别器(UISwipeGestureRecognizer)

智能座舱系列一:智能化基础平台及架构

Android ViewFlipper + 手势检测器

Android手部检测和手势识别(含训练代码+Android源码+手势识别数据集)

Android手部检测和手势识别(含训练代码+Android源码+手势识别数据集)