View觸發(fā)機(jī)制API實(shí)現(xiàn)GestureDetector OverScroller詳解
前言
前一篇文章講了View的觸發(fā)反饋機(jī)制的原理,對(duì)于一個(gè)自定義View而言,手勢(shì)的處理都是重寫onTouchEvent函數(shù),或者通過(guò)setOnTouchEventListener方法捕捉手勢(shì)。但是手勢(shì)的處理,如滑動(dòng)、觸摸、雙擊等檢測(cè)對(duì)應(yīng)的檢測(cè)也并不是那么簡(jiǎn)單,自己一個(gè)個(gè)造輪子也過(guò)于麻煩,萬(wàn)幸的是google早已經(jīng)給開(kāi)發(fā)者提供了手勢(shì)捕捉的類- GestureDetector。通過(guò)這個(gè)類我們可以識(shí)別很多的手勢(shì),主要是通過(guò)他的onTouchEvent(event)方法完成了不同手勢(shì)的識(shí)別。雖然他能識(shí)別手勢(shì),但是不同的手勢(shì)要怎么處理,應(yīng)該是提供給程序員實(shí)現(xiàn)的。
GestureDetector
在GestureDetector 中一共有三種主要的回調(diào)接口 ,OnGestureListener 、OnDoubleTapListener、OnContextClickListener
這三個(gè)接口的方法如下。
public interface OnGestureListener {
boolean onDown(MotionEvent e);
void onShowPress(MotionEvent e);
boolean onSingleTapUp(MotionEvent e);
boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
void onLongPress(MotionEvent e);
boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
}
public interface OnDoubleTapListener {
boolean onSingleTapConfirmed(MotionEvent e);
boolean onDoubleTap(MotionEvent e);
boolean onDoubleTapEvent(MotionEvent e);
}
public interface OnContextClickListener {
boolean onContextClick(MotionEvent e);
}
GestureDetector 使用
GestureDector 負(fù)責(zé)監(jiān)聽(tīng)手勢(shì),而 OnDoubleTapListener、OnGestureListener 用于開(kāi)發(fā)者自己去處理對(duì)應(yīng)手勢(shì)的反饋
package com.example.androidtemp.view;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.OverScroller;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
public class TouchView extends View implements GestureDetector.OnGestureListener,GestureDetector.OnDoubleTapListener{
private static final String TAG = "TouchView";
GestureDetector gestureDetector = null;
public TouchView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
gestureDetector = new GestureDetector(context,this);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
Log.i(TAG, "onSingleTapConfirmed: ");
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
Log.i(TAG, "onDoubleTap: ");
return false;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
Log.i(TAG, "onDoubleTapEvent: ");
return false;
}
@Override
public boolean onDown(MotionEvent e) {
Log.d(TAG, "onDown: ");
return true;
}
@Override
public void onShowPress(MotionEvent e) {
Log.i(TAG, "onShowPress: ");
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
Log.i(TAG, "onSingleTapUp: ");
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
Log.i(TAG, "onScroll: ");
return false;
}
@Override
public void onLongPress(MotionEvent e) {
Log.i(TAG, "onLongPress: ");
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Log.i(TAG, "onFling: ");
return false;
}
}
onDown方法
onDown 方法是在ACTION_DOWN 事件時(shí)被調(diào)用的,其的返回值決定了View是否消費(fèi)該事件,一般我們肯定是需要消費(fèi)該事件的,因此其值為true.
public boolean onDown() {
return true;
}
onShowPress方法
@Override
public void onShowPress(MotionEvent e) {
//進(jìn)行控件顏色的改變或其他一些動(dòng)作
}
onShowPress 是用戶按下時(shí)的一種回調(diào),主要作用是用于給用戶一種按壓下的狀態(tài),可以在該回調(diào)中讓控件顏色改變或進(jìn)行一些動(dòng)作。需要注意的是,onShowPress 方法不是立即回調(diào)的,在手指觸碰后,在100ms左右后才會(huì)回調(diào)。在這100ms內(nèi)如果手指抬起或滾動(dòng),該回調(diào)方法不會(huì)被觸發(fā)。在前一篇文章View事件分發(fā)機(jī)制 中提到過(guò)自定義View 默認(rèn)的super.onTouchEvent 實(shí)現(xiàn)中,按壓狀態(tài)也是有一個(gè)預(yù)按壓狀態(tài)的檢測(cè),此處的onShowPress的回調(diào)機(jī)制也是同理。
onLongPress 方法
用于檢測(cè)長(zhǎng)按事件的,即手指按下后不抬起,在一段時(shí)間后會(huì)觸發(fā)該事件。
@Override
public void onLongPress(MotionEvent e) {
}
onLongPress 回調(diào)被觸發(fā)前 onShowPress 一定會(huì)被觸發(fā)。
需要注意的是 onLongPress一旦被觸發(fā),其他事件都不會(huì)被觸發(fā)了。
不過(guò),onLongPress事件可以被禁止使用,通過(guò)如下代碼設(shè)置,即不會(huì)觸發(fā)長(zhǎng)按事件
gestureDetector.setIsLongpressEnabled(false);
onSingleTapUp 方法
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
onSingleTapUP的返回值不是太重要,不過(guò)一般消費(fèi)了就還是返回ture吧。
onSingleTapUp的意思顧名思義,即在 手指抬起時(shí)觸發(fā),不過(guò)他跟一般的onClick、以及onSingleTapConfirmed有一定區(qū)別
單擊事件觸發(fā):
GCS: onSingleTapUp GCS: onClick GCS: onSingleTapConfirmed
| 類型 | 觸發(fā)次數(shù) | 摘要 |
|---|---|---|
| onSingleTapUp | 1 | 單擊抬起 |
| onSingleTapConfirmed | 1 | 單擊確認(rèn) |
| onClick | 1 | 單擊事件 |
雙擊事件觸發(fā):
onSingleTapUp onClick onDoubleTap onClick
| 類型 | 觸發(fā)次數(shù) | 摘要 |
|---|---|---|
| onSingleTapUp | 1 | 在雙擊的第一次抬起時(shí)觸發(fā) |
| onSingleTapConfirmed | 0 | 雙擊發(fā)生時(shí)不會(huì)觸發(fā)。 |
| onClick | 2 | 在雙擊事件時(shí)觸發(fā)兩次。 |
可以看出來(lái)這三個(gè)事件還是有所不同的,根據(jù)自己實(shí)際需要進(jìn)行使用即可
onScroll
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float
distanceY) {
return true;
}
onScroll 方法是用于監(jiān)聽(tīng)手指的滑動(dòng)的,e1是第一次ACTION_DOWN的事件,e2是當(dāng)前滾動(dòng)事件。distanceX、distanceY記錄了手指在x、y軸滑動(dòng)的距離。
需要注意的時(shí),該滑動(dòng)距離記錄的是上次滑動(dòng)回調(diào)與這次回調(diào)之間的距離差值。且還有一個(gè)有意思的注意事項(xiàng),該差值是 lastEvent-curEvent 得到的,這與正常的邏輯行為不太一致,不過(guò)google就這樣干了,所以當(dāng)我們?cè)谟?jì)算滑動(dòng)偏移量時(shí)需要對(duì) distanceX、distancesY進(jìn)行一個(gè) 相減的操作而不是相加。
onFling
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
return true;
}
用戶手指在屏幕快速滑動(dòng)后,在抬起時(shí)(ACTION_UP)觸發(fā)該事件。
Fling 中文直接翻譯過(guò)來(lái)就是一扔、拋、甩,最常見(jiàn)的場(chǎng)景就是在 ListView 或者 RecyclerView 上快速滑動(dòng)時(shí)手指抬起后它還會(huì)滾動(dòng)一段時(shí)間才會(huì)停止。onFling 就是檢測(cè)這種手勢(shì)的。
四個(gè)參數(shù)的介紹如下
| 參數(shù) | 簡(jiǎn)介 |
|---|---|
| e1 | 手指按下時(shí)的 Event。 |
| e2 | 手指抬起時(shí)的 Event。 |
| velocityX | 在 X 軸上的運(yùn)動(dòng)速度(像素/秒)。 |
| velocityY | 在 Y 軸上的運(yùn)動(dòng)速度(像素/秒)。 |
利用 velocityX、velocityY 參數(shù)可以實(shí)現(xiàn)一個(gè)具有一定初速度的滑動(dòng),之后該速度隨著滑動(dòng)衰減,直到停止。
一般onFling 可以結(jié)合 OverScroller 實(shí)現(xiàn)一個(gè)均勻減速的滑動(dòng)效果。
overScroller的用法在后方介紹。
onSingleTapConfirmed 和onDoubleTap
public boolean onSingleTapConfirmed(MotionEvent e) {
return false;
}
public boolean onDoubleTap(MotionEvent e) {
return false;
}
public boolean onDoubleTapEvent(MotionEvent e) {
return false;
}
onSingleTapConfirmed用于監(jiān)聽(tīng)單擊事件,而onDoubleTap用于監(jiān)聽(tīng)雙擊事件。這兩個(gè)回調(diào)函數(shù)是互斥的。
onSingleTapConfigrmed的調(diào)用是延遲的,其在 手指按下300ms后觸發(fā)。
onSingleTapConfigrmed 適合于在 既檢測(cè)單擊事件也檢測(cè)雙擊時(shí)間時(shí)使用。
但是如果只是檢測(cè)單擊事件,onSingleTapUp更合適,onSingleTapConfigrmed會(huì)讓用戶明顯感覺(jué)到延遲。
需要注意的是 onDoubleTap 事件并不是第二次抬起時(shí)觸發(fā)的,而是第二次手觸摸到屏幕時(shí)即(第二次ACTION_DOWN)事件時(shí)就會(huì)觸發(fā)該事件,如果要保證在第二次抬起時(shí)才觸發(fā)該事件,就需要使用onDoubleTapEvent方法了
onDoubleTapEvent
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
Log.i(TAG, "onDoubleTapEvent: event:" + e.getActionMasked());
switch (e.getActionMasked()) {
case MotionEvent.ACTION_UP:
Log.i(TAG, "onDoubleTapEvent: ACTION_UP");
break;
}
return true;
}
雙擊時(shí),onDoubleTapEvent 將會(huì)在onDoubleTap 后觸發(fā).
雙擊觸發(fā)日志:
TouchView: onDown: TouchView: onSingleTapUp: TouchView: onDoubleTap: TouchView: onDoubleTapEvent: event:0(ACTION_DOWN) TouchView: onDown: TouchView: onDoubleTapEvent: event:2(ACTION_MOVE) TouchView: onDoubleTapEvent: event:2(ACTION_MOVE) TouchView: onDoubleTapEvent: event:1(ACTION_UP) TouchView: onDoubleTapEvent: ACTION_UP
需要注意的是不論是雙擊還是單擊,只要按下長(zhǎng)時(shí)間未動(dòng)且未抬起,都會(huì)觸發(fā)onLongPress。
第二次按下后常按再抬起日志
TouchView: onDown: TouchView: onSingleTapUp: TouchView: onDoubleTap: TouchView: onDoubleTapEvent: event:0 TouchView: onDown: TouchView: onDoubleTapEvent: event:2 TouchView: onDoubleTapEvent: event:2 TouchView: onDoubleTapEvent: event:2 TouchView: onShowPress: TouchView: onDoubleTapEvent: event:2 TouchView: onDoubleTapEvent: event:2 TouchView: onDoubleTapEvent: event:2 TouchView: onLongPress: ouchView: onDoubleTapEvent: event:1 TouchView: onDoubleTapEvent: ACTION_UP
OverScroller
在 onFling 方法中,曾說(shuō)過(guò) 使用velocityX ,velocityY 兩個(gè)參數(shù)可以實(shí)現(xiàn) View的滑動(dòng)效果.
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
return true;
}
示例
此處用一個(gè)可拖拉滑動(dòng)的小圓球作為示例.
scroll效果圖

Fling效果圖

代碼如下
package com.example.androidtemp.view
import android.view.View
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.util.Log
import android.view.GestureDetector
import android.view.MotionEvent
import android.widget.OverScroller
import kotlin.math.max
import kotlin.math.min
private const val TAG = "SmallBallView"
class SmallBallView(context: Context?, attrs:AttributeSet?) :View(context,attrs) ,GestureDetector.OnGestureListener{
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val BALL_DIAMETER_SIZE = 100 //球直徑長(zhǎng)度
private var originOffsetX = 0f
private var originOffsetY = 0f
private var offsetX = 0f
private var offsetY = 0f
private val gestureDetector = GestureDetector(this.context,this)
private val scroller = OverScroller(this.context)
override fun onTouchEvent(event: MotionEvent): Boolean {
return gestureDetector.onTouchEvent(event);
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
originOffsetX = (w - BALL_DIAMETER_SIZE)/2f
originOffsetY = (h - BALL_DIAMETER_SIZE)/2f
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// 偏移
canvas.translate(offsetX,offsetY)
//中間位置畫個(gè)圓
canvas.drawArc(originOffsetX,originOffsetY,originOffsetX + BALL_DIAMETER_SIZE.toFloat(),originOffsetY + BALL_DIAMETER_SIZE.toFloat(),0f,360f,false,paint)
}
override fun onDown(e: MotionEvent?): Boolean = true
override fun onShowPress(e: MotionEvent?) {}
override fun onSingleTapUp(e: MotionEvent?): Boolean {
return false
}
override fun onLongPress(e: MotionEvent?) {}
override fun onScroll(
e1: MotionEvent?,
e2: MotionEvent?,
distanceX: Float,
distanceY: Float
): Boolean {
Log.i(TAG, "onScroll: ")
offsetX -= distanceX
offsetY -= distanceY
//移動(dòng)不能超過(guò)圓的一半
offsetX = min(offsetX,width.toFloat()/2)
offsetX = max(offsetX,-width.toFloat()/2)
//移動(dòng)不能超過(guò)圓的一半
offsetY = min(offsetY,height.toFloat()/2)
offsetY = max(offsetY,-height.toFloat()/2)
invalidate()
return true;
}
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent?,
velocityX: Float,
velocityY: Float
): Boolean {
//限制滑動(dòng)不能超過(guò)一小圓的一半
scroller.fling(offsetX.toInt(),offsetY.toInt(),velocityX.toInt(),velocityY.toInt(),-width/2,width/2,-height/2,height/2)
postOnAnimation(scrollerRunnable)
return true;
}
private val scrollerRunnable = object :Runnable {
override fun run() {
if (scroller.computeScrollOffset()) {
offsetX = scroller.currX.toFloat()
offsetY = scroller.currY.toFloat()
invalidate()
postOnAnimation(this)
}
}
}
}
OverScroller方法介紹
fling方法
public void fling(int startX, int startY, int velocityX, int velocityY,
int minX, int maxX, int minY, int maxY) {
fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
}
public void fling(int startX, int startY, int velocityX, int velocityY,
int minX, int maxX, int minY, int maxY, int overX, int overY) {
//實(shí)現(xiàn)邏輯省略,有興趣的可以自己去看代碼
}
| 參數(shù) | 簡(jiǎn)介 |
|---|---|
| startX、startY | 開(kāi)始滑動(dòng)的X(Y)軸位置 |
| velocityX、velocityY | 在 X(Y) 軸上的運(yùn)動(dòng)速度(像素/秒)。 |
| minX、maxX | 滑動(dòng)時(shí)X軸的兩個(gè)邊界值,滑動(dòng)時(shí)一旦到達(dá)邊界值,則立刻停止 |
| minY、maxY | 滑動(dòng)時(shí)Y軸的兩個(gè)邊界值,滑動(dòng)時(shí)一旦到達(dá)邊界值,則立刻停止 |
| overX、overY | 在滑動(dòng)時(shí),可超出的滑動(dòng)值,可超過(guò)邊界值,不過(guò)超過(guò)邊界值后,又會(huì)重新滑動(dòng)回來(lái) |
startScroll方法
startScroll的滾動(dòng)默認(rèn)以一種粘性液體的效果進(jìn)行滾動(dòng)。
public void startScroll(int startX, int startY, int dx, int dy) {
startScroll(startX, startY, dx, dy, DEFAULT_DURATION);//DEFAULT_DURATION 250 ms
}
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mMode = SCROLL_MODE;
mScrollerX.startScroll(startX, dx, duration);
mScrollerY.startScroll(startY, dy, duration);
}
| 參數(shù) | 簡(jiǎn)介 |
|---|---|
| startX、startY | 開(kāi)始滑動(dòng)的X(Y)軸位置 |
| dx、dy | 滾動(dòng)到達(dá)的目標(biāo)位置 |
| duration | 滾動(dòng)花費(fèi)時(shí)間(單位ms),如果不指定默認(rèn)時(shí)250ms |
以上就是View觸發(fā)機(jī)制API實(shí)現(xiàn)GestureDetector OverScroller詳解的詳細(xì)內(nèi)容,更多關(guān)于View觸發(fā)機(jī)制API的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
在Android項(xiàng)目中使用AspectJ的詳細(xì)攻詻
AspectJ是實(shí)現(xiàn)AOP的其中一款框架,內(nèi)部通過(guò)處理字節(jié)碼實(shí)現(xiàn)代碼注入,文章給大家提到AspectJ基礎(chǔ)語(yǔ)法和集成AspectJ的方式,對(duì)AspectJ在android中使用教程感興趣的朋友跟隨小編一起看看吧2021-06-06
Android簡(jiǎn)單實(shí)現(xiàn)文件下載
這篇文章主要為大家詳細(xì)介紹了Android簡(jiǎn)單實(shí)現(xiàn)文件下載,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
Android實(shí)現(xiàn)靜音檢測(cè)功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)靜音檢測(cè)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
一文帶你了解Android?Flutter中Transform的使用
flutter的強(qiáng)大之處在于,可以對(duì)所有的widget進(jìn)行Transform,因此可以做出非??犰诺男Ч?。本文就來(lái)大家了解一下Transform的具體使用,感興趣的可以了解一下2023-01-01
Android xmlns 的作用及其自定義實(shí)例詳解
這篇文章主要介紹了 Android xmlns 的作用及其自定義實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-06-06
Android view更改背景資源與padding消失的問(wèn)題解決辦法
這篇文章主要介紹了Android view更改背景資源與padding消失的問(wèn)題解決辦法的相關(guān)資料,需要的朋友可以參考下2017-04-04

