Android自定义View-手势检测(GestureDetector)

本文详细介绍了Android中GestureDetector的使用,包括构造函数、监听器和常见手势的回调顺序。强调了在子线程中使用GestureDetector时Handler必须带有Looper的重要性,以及为何在监听单击和双击事件时,推荐使用onSingleTapConfirmed而不是OnClickListener。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Google为了方便我们进行手势检测,早就封装了相关的内容提供我们使用。即Android手势检测,主要就是对GestureDetector类的使用和说明。

1.GestureDetector

GestureDetector可以使用MotionEvents检测各种手势和事件。其内部包含四个监听器(三个接口以及一个外部类):

接口含义
OnGestureListener手势检测:包括 按下(Down) 扔(Fling) 长按(LongPress) 滚动(Scroll) 触摸反馈(ShowPress) 单击抬起(SingleTapUp)
OnDoubleTapListener双击事件:有三个回调方法 双击(DoubleTap) 单击确认(SingleTapConfirmed) 双击事件回调(DoubleTapEvent)
OnContextClickListener它是在Android6.0(API 23)才添加的一个选项,是用于检测外部设备上的按钮是否按下的,例如蓝牙触控笔上的按钮,一般情况下,忽略即可。
SimpleOnGestureListener三个接口的空实现,使用较为方便

GestureDetector常用的构造函数:

构造函数
public GestureDetector(Context context, OnGestureListener listener, Handler handler)
public GestureDetector(Context context, OnGestureListener listener)

第二种需要传递上下文和手势监听器,没什么好讲的;关于第一种,传递一个Handler只是为了给GestureDetector手势监听器提供一个Looper。通常不需要传递,因为在内部会自己生成一个Handler用来传递消息,如果在主线程创建GestureDetector则会自己获取主线程中的Looper。如果在没有创建Looper的子线程中创建GestureDetector则会报错。

第二种构造函数的使用以及注意事项:

方式一

	    //在主线程中创建Handler,Handler会自动获取主线程的Looper
        final Handler handler = new Handler();
        new Thread(new Runnable() {
            @Override
            public void run() {
                GestureDetector detector = new GestureDetector(MainActivity.this,new GestureDetector.SimpleOnGestureListener(){

                },handler);
            }
        }).start();

方式二

        //方式二 子线程中创建Handler并指定Looper
        new Thread(new Runnable() {
            @Override
            public void run() {
                Handler handler1 = new Handler(Looper.getMainLooper());
                GestureDetector detector = new GestureDetector(MainActivity.this,new GestureDetector.SimpleOnGestureListener(){

                },handler1);
            }
        }).start();

第一种构造函数在子线程种的使用

子线程中准备了Looper可使用第一种构造函数

 new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                GestureDetector detector = new GestureDetector(MainActivity.this,new GestureDetector.SimpleOnGestureListener(){

                });
            }
        }).start();

注意: 传递的Handler必须带有Looper

2.GestureDetector使用

  1. 创建一个监听回调
  2. 创建一个检测器
  3. 给检测器设置数据源

例子:

//监听回调
 GestureDetector.OnGestureListener listener = new GestureDetector.OnGestureListener() {
            @Override
            public boolean onDown(MotionEvent e) {
                Log.e(TAG,"onDown");
                return true;
            }

            @Override
            public void onShowPress(MotionEvent e) {
                Log.e(TAG,"onShowPress");
            }

            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                Log.e(TAG,"onSingleTapUp");
                return true;
            }

            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                Log.e(TAG,"onScroll");
                return true;
            }

            @Override
            public void onLongPress(MotionEvent e) {
                Log.e(TAG,"onLongPress");
            }

            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
                Log.e(TAG,"onFling");
                return true;
            }
        };

		//创建监听器
        final GestureDetector detector = new GestureDetector(this,listener);
      

		//设置数据源
        view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return detector.onTouchEvent(event);
            }
        });

如果想单独设置OnDoubleTapListener的话直接调用:

  detector.setOnDoubleTapListener(new DoubleTapListener());

前面提到SimpleOnGestureListener实现了三个接口,因此我们使用时通常都是使用它来构建监听器的。。

接下来通过各种不同的手势动作来观察事件的回调顺序。使用SimpleOnGestureListener重写全部的空方法,并添加日志。

final GestureDetector detector = new GestureDetector(this,new GestureDetector.SimpleOnGestureListener(){
            @Override
            public boolean onDown(MotionEvent e) {
                Log.e(TAG,"onDown");
                return true;
            }

            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
                Log.e(TAG,"onFling");
                return false;
            }

            @Override
            public void onLongPress(MotionEvent e) {
                Log.e(TAG,"onLongPress");
            }

            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                Log.e(TAG,"onScroll");
                return false;
            }

            @Override
            public void onShowPress(MotionEvent e) {
                super.onShowPress(e);
                Log.e(TAG,"onShowPress");
            }

            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                Log.e(TAG,"onSingleTapUp");
                return super.onSingleTapUp(e);
            }

            @Override
            public boolean onDoubleTap(MotionEvent e) {
                Log.e(TAG,"onDoubleTap");
                return false;
            }

            @Override
            public boolean onSingleTapConfirmed(MotionEvent e) {
                Log.e(TAG,"onSingleTapConfirmed");
                return false;
            }

            @Override
            public boolean onDoubleTapEvent(MotionEvent e) {
                Log.e(TAG,"onDoubleTapEvent");
                return false;
            }
        });
        view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return detector.onTouchEvent(event);
            }
        });

在这里插入图片描述

在这里插入图片描述

从上面的图大概可以看出每个回调方法的作用以及调用时刻:

方法时间
onDown(MotionEvent e)按下屏幕就会触发
onShowPress(MotionEvent e)如果是按下的时间超过瞬间,而且在按下的时候没有松开或者是拖动
onLongPress(MotionEvent e)长按触摸时触发
onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)滑动触发,x y 表示移动的距离
onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)快速移动时松开触发 x,y是XY轴的移动速度
onSingleTapUp(MotionEvent e)单击松开触发
onDoubleTap(MotionEvent e)双击第二次按下就会触发
onSingleTapConfirmed(MotionEvent e)单击事件
onDoubleTapEvent(MotionEvent e)双击事件触发(可判断move up down 情况)

如果你只想监听双击事件,那么只用关注 onDoubleTap 就行了,如果你同时要监听单击事件则需要关注 onSingleTapConfirmed 这个回调函数。

有人可能会有疑问,监听单击事件为什么要使用 onSingleTapConfirmed,使用 OnClickListener 不行吗?主要有两个原因:
1.它们两个是存在一定冲突的,如果想要两者同时被触发,则 setOnTouchListener 不能消费事件,如果 onTouchListener 消费了事件,就可能导致 OnClick 无法正常触发。
2.需要同时监听单击和双击,则说明单击和双击后响应逻辑不同,然而使用 OnClickListener 会在双击事件发生时触发两次,这显然不是我们想要的结果。而使用 onSingleTapConfirmed 就不用考虑那么多了,你完全可以把它当成单击事件来看待,而且在双击事件发生时,onSingleTapConfirmed 不会被调用,这样就不会引发冲突。

假如说我们想要在第二次按下抬起后才判定这是一个双击操作,触发后续的内容,则不能使用 onDoubleTap 了,需要使用 onDoubleTapEvent 来进行更细微的控制:

final GestureDetector detector = new GestureDetector(MainActivity.this, new GestureDetector.SimpleOnGestureListener() {
    @Override public boolean onDoubleTap(MotionEvent e) {
        Logger.e("第二次按下时触发");
        return super.onDoubleTap(e);
    }

    @Override public boolean onDoubleTapEvent(MotionEvent e) {
        switch (e.getActionMasked()) {
            case MotionEvent.ACTION_UP:
                Logger.e("第二次抬起时触发");
                break;
        }
        return super.onDoubleTapEvent(e);
    }
});

down 在事件分发体系中是一个较为特殊的事件,为了保证事件被唯一的 View 消费,哪个 View 消费了 down 事件,后续的内容就会传递给该 View。如果我们想让一个 View 能够接收到事件,有两种做法:

1、让该 View 可以点击,因为可点击状态会默认消费 down 事件。

 view.setClickable(true);

2、手动消费掉 down 事件。

@Override
            public boolean onDown(MotionEvent e) {
                Log.e(TAG,"onDown");
                return true;
            }

参考:
https://www.cnblogs.com/ldq2016/p/7000300.html
http://www.gcssloop.com/customview/gestruedector

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值