自定义PhotoView事件分发项目实战(二):手势(GestureDetector ScaleGestureDetector)

既然问题出现了,那要怎么解决呢。既然是缺少活动中的 Looper ,那么将活动中的 Looper 传入就是。观察 detector 的构造方法,发现其一共有种方法,其中我们常用的方法有两种,首先是我们在主线程中用的那种,另外一种就是我们现在要用的,在子线程中,能传入 Looper 的 构造方法:

| public GestureDetector(Context context, OnGestureListener listener) |

| — |

| GestureDetector(Context context, GestureDetector.OnGestureListener listener, Handler handler) |

方案一

传入一个保有 Looper 对象的 Hander

new Thread(){

@Override

public void run() {

super.run();

detector = new GestureDetector(GestureDetectorActivity.this, listener, new Handler(Looper.getMainLooper()));

}

}.start();

方案二

跟方法已一样,只是把 Hander 拿出来单独创建罢了

new Thread(){

@Override

public void run() {

super.run();

Handler handler = new Handler(Looper.getMainLooper());

detector = new GestureDetector(GestureDetectorActivity.this, listener, handler);

}

}.start();

方案三

在主线程中创建 Hander ,这样就不用在创建 Hander 时,传入主线程的 Looper

final Handler handler = new Handler();

new Thread(){

@Override

public void run() {

super.run();

detector = new GestureDetector(GestureDetectorActivity.this, listener, handler);

}

}.start();

方案四

和上面几个方法一样,只不过在子线称里提前准备好 Lopper ,这样子线称就和主线程一样了

new Thread(){

@Override

public void run() {

super.run();

Looper.prepare();

detector = new GestureDetector(GestureDetectorActivity.this, listener);

}

}.start();


重点:手势监听

==================================================================

刚刚我们已经通过双击效果,讲过 onDoubleTapEvent 了,那么 GestureDetecotr 还有哪些厉害的回调方法呢?

  1. OnDoubleTapListener :也就是双击事件,双击事件除了 onDoubleTapEvent 这个回调方法之外,还有 SingleTapConfirmed 和 DoubleTap 这两个回调方法

  2. OnGestureListener :这里集合了众多手势的监听器:主要有:按下(Down)、 扔(Fling)、长按(LongPress)、滚动(Scroll)、触摸反馈(ShowPress) 和 单击抬起(SingleTapUp)

  3. SimpleOnGestureListener :上述接口的空实现,用的频率比较多


OnDoubleTapListener


我们先来讲讲 OnDoubleTapListener,大家可能要问:刚刚不是已经讲过双击事件监听了吗,这里又来不是浪费时间?废话不说,让我详细介绍下这类的方法:

单击回调 SingleTapConfirmed

有人就会很好奇,对于单击事件的回调,直接去用 onClickListener 不就好了么,干嘛要用 SingleTapConfirmed 呢?

首先,这两个方法是冲突的,这里就涉及到了事件分发机制,这点我后期会专门给大家总结下,这里就不详解了。

其二,更具 onClickListener 的机制,我们不难发现,如果是用 onClickListener 的话,当我们双击时,我们也会调用单击事件,也就是单击了两次,这明显是不符合我们意图的。那么该如何调用呢?very easy !

final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){

@Override

public boolean onSingleTapConfirmed(MotionEvent e) {

MyToast.makeToast(GestureDetectorActivity.this, “single click!”);

return super.onSingleTapConfirmed(e);

}

};

DoubleTap 与 onDoubleTapEvent

我打算把这两个方法放在一起将,一则他两都属于双击的范畴,二则他两有着极高相似和细微却重要的区别。

大家可以尝试着在 onTouchEvent 和 DoubleTap 中,对点击的 Down move 和 up 进行打印,你就会发现,对于 DoubleTap 而言,它是在第二次点击按下是,发生的回调,而对于 onDoubleTapEvent 而言,则是在第二次点击后,手指抬起离开了屏幕时,发生的回调。这就是他两最重要的区别。

final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){

@Override

public boolean onDoubleTap(MotionEvent e) {

MyToast.makeToast(GestureDetectorActivity.this, “double click down!”);

return super.onDoubleTap(e);

}

@Override

public boolean onDoubleTapEvent(MotionEvent e) {

switch (e.getActionMasked()){

case MotionEvent.ACTION_UP:

MyToast.makeToast(GestureDetectorActivity.this, “double click up!”);

break;

}

return super.onDoubleTapEvent(e);

}

};

所以,有了这两个方法,我们就可以更具目的性的满足两种需求。

讲到这里,单击双击事件就告一段落了,下面我们进入 OnGestureListener 的学习


OnGestureListener

这可以说是整个手势监测中,最核心的部分了,前面都是引入,现在才是正题,这里我主要向大家介绍一下手势:

  1. 按下(Down)

  2. 一扔(Fling)

  3. 长按(LongPress)

  4. 滚动(Scroll)

  5. 触摸反馈(ShowPress)

  6. 单击抬起(SingleTapUp)


onDown

onDown 事件很好理解,他在一个 View 被按下时执行。也正是如此,要想能执行 onDown ,首先要保证这个 View 是可以点击的,也就是 onClickable 的值为 true 。

private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){

@Override

public boolean onDown(MotionEvent e) {

MyToast.makeToast(GestureDetectorActivity.this, “onDown”);

// 后续事件

return super.onDown(e);

}

};

onFling

对于 onFling 我个人感觉这是个最常用的方法,就像它的名字,翻译过来是拖、拽、扔的意思。举个例子 RecyclerView 或者 ListView 我们都有用过,当我们快速上拉后会滚动一定距离停止,我们可爱的 onFling 就是用于检测这种手势的。

private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){

@Override

public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {

mSpeedX = velocityX;

mSpeedY = velocityY;

handler.postDelayed(runnable, 30);

return super.onFling(e1, e2, velocityX, velocityY);

}

};

从代码中,我们不难发现:该方法有四个参数

| 参数 | 意义 |

| — | — |

| e1 | 手指按下时的 Event。 |

| e2 | 手指抬起时的 Event。 |

| velocityX | 在 X 轴上的运动速度(像素/秒)。 |

| velocityY | 在 Y 轴上的运动速度(像素/秒)。 |

通过前两个 MotionEvent 参数,我们可以获得点击发生的位置等,通过后两个 float 参数,我们可以获得手指滑动的速度。

具体使用其实还是蛮多的,比如我们可以想象下台球游戏,球杆击球后,就有这样一个初速度递减的效果。

onLongPress

onLongPress 很简单,就是长按事件的回调,比如说长按复制,长按弹窗等等,它不但应用广泛,同时使用也非常简单,这里就不唠叨了

private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){

@Override

public void onLongPress(MotionEvent e) {

MyToast.makeToast(GestureDetectorActivity.this, “onLongPress”);

// 后续工作

super.onLongPress(e);

}

};

onScroll

onScroll 方法和 onFling 很像,唯一的区别在于,onFling 的参数是滑动的速度,而 onScroll 的后两个参数则是滑动的距离:

| 参数 | 意义 |

| — | — |

| e1 | 手指按下时的 MotionEvent |

| e2 | 手指抬起时的 MotionEvent |

| distanceX | 在 X 轴上划过的距离 |

| distanceY | 在 Y 轴上划过的距离 |

private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){

@Override

public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {

MyToast.makeToast(GestureDetectorActivity.this, "onScroll X = " +

distanceX + " Y = " + distanceY);

return super.onScroll(e1, e2, distanceX, distanceY);

}

};

onShowPress

这个方法我其实觉得作用不是很大,因为它是在 View 被点击(按下)是调用,其作用是给用户一个视觉反馈,让用户知道我这个控件被点击了,这样的效果我们完全可以用 Material design 的 ripple 实现,或者直接 drawable 写个背景也行。

如果说它有什么特别指出的话,它是一种延时回调,延迟时间是 180 ms。也就是说用户手指按下后,如果立即抬起或者事件立即被拦截,时间没有超过 180 ms的话,这条消息会被 remove 掉,也就不会触发这个回调。

private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){

@Override

public void onShowPress(MotionEvent e) {

MyToast.makeToast(GestureDetectorActivity.this, “onShowPress”);// >150ms 时调用

super.onShowPress(e);

}

};

onSingleTapUp

对于 onSingleTapUp 网上有很多分析,但我觉得过于复杂了,其实这东西很简单。举个例子你就懂了:

之前我们讲过双击事件,那好 onSingleTapUp 就是在 双击事件的第一次点击时回调。也就是说但你点击了一个控件时(双击第一下),这个回调马上会被调用,然后迅速点第二下(双击事件的第二下),则其不会被调用。

| 类型 | 触发次数 | 摘要 |

| — | — | — |

| onSingleTapUp | 1 | 在双击的第一次抬起时触发 |

| onSingleTapConfirmed | 0 | 双击发生时不会触发。 |

| onClick | 2 | 在双击事件时触发两次。 |

它和 onSingleTapConfirmed 的区别也就很明显了,onSingleTapConfirmed 在发生双击时,会回调两次,而 onSingleTapUp 只会在双击的的第一次回调。

private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){

@Override

public boolean onSingleTapUp(MotionEvent e) {// 双击第一次抬起触发,第二次不触发

Log.d(“onSingleTapUp”, “onSingleTapUp”);// >150ms 时调用

return super.onSingleTapUp(e);

}

};

SimpleOnGestureListener


SimpleOnGestureListener 中包含了以上所有方法的空实现,之所以在文末再一次提及他,主要是想讲下它的方便之处。

我们以监听 OnDoubleTapListener 为例,如果想要使用 OnDoubleTapListener 接口则需要这样进行设置:

GestureDetector detector = new GestureDetector(this, new GestureDetector

.SimpleOnGestureListener());

detector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {

@Override public boolean onSingleTapConfirmed(MotionEvent e) {

Toast.makeText(MainActivity.this, “onSingleTapConfirmed”, Toast.LENGTH_SHORT).show();

return false;

}

@Override public boolean onDoubleTap(MotionEvent e) {

Toast.makeText(MainActivity.this, “onDoubleTap”, Toast.LENGTH_SHORT).show();

return false;

}

@Override public boolean onDoubleTapEvent(MotionEvent e) {

Toast.makeText(MainActivity.this,“onDoubleTapEvent”,Toast.LENGTH_SHORT).show();

return false;

}

});

我们不难发现一个问题,既然在 GestureDetector 实例化时,已经实例化了一个 SimpleOnGestureListener 了,那么在舍近求远的去使用 OnGestureListener 的话,会多出几个无用的空实现,显然很浪费,所以在一般情况下,乖乖的使用 SimpleOnGestureListener 就好了。

最后

=============================================================

由于手势监听的方法有点多,大家一时难以记住,所以我打算把所有方法,在 SimpleOnGestureListener 中重写一遍,方便大家进行查阅、记忆:

private final GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener(){

@Override

public boolean onSingleTapConfirmed(MotionEvent e) {

MyToast.makeToast(GestureDetectorActivity.this, “single click!”);

最后

代码真的是重质不重量,质量高的代码,是当前代码界提倡的,当然写出高质量的代码肯定需要一个相当高的专业素养,这需要在日常的代码书写中逐渐去吸收掌握,谁不是每天都在学习呀,目的还不是为了一个,为实现某个功能写出高质量的代码。

所以,长征路还长,大家还是好好地做个务实的程序员吧。

最后,小编这里有一系列Android提升学习资料,有兴趣的小伙伴们可以来看下哦~
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值