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自定義滑動(dòng)接聽(tīng)電話控件組實(shí)例
這篇文章主要介紹了Android自定義滑動(dòng)接聽(tīng)電話控件組,接聽(tīng)電話可以左右滑動(dòng),感興趣的小伙伴們可以參考一下。2016-10-10使用Android系統(tǒng)提供的DownloadManager來(lái)下載文件
本篇文章主要介紹了使用Android系統(tǒng)提供的DownloadManager來(lái)下載文件,可以將長(zhǎng)時(shí)間的下載任務(wù)交給系統(tǒng),完全由系統(tǒng)管理,有需要的可以了解下。2016-11-11Android 實(shí)現(xiàn)滑動(dòng)方法總結(jié)
這篇文章主要介紹了Android 實(shí)現(xiàn)滑動(dòng)方法總結(jié)的相關(guān)資料,需要的朋友可以參考下2017-07-07Android 修改Preferences默認(rèn)樣式的步驟
這篇文章主要介紹了Android 修改Preferences默認(rèn)樣式的步驟,幫助大家更好的理解和學(xué)習(xí)使用Android開(kāi)發(fā),感興趣的朋友可以了解下2021-04-04Android手勢(shì)密碼實(shí)現(xiàn)實(shí)例代碼
本篇文章主要介紹了Android手勢(shì)密碼實(shí)現(xiàn)實(shí)例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04自定義View系列之kotlin繪制手勢(shì)設(shè)置溫度控件的方法
這篇文章主要給大家介紹了關(guān)于自定義View系列之kotlin繪制手勢(shì)設(shè)置溫度控件的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07Android7.0中關(guān)于ContentProvider組件詳解
本文描述了Android7.0中關(guān)于ContentProvider組件實(shí)現(xiàn)原理以及ContentProvider發(fā)布者和調(diào)用者這兩在Framework層是如何實(shí)現(xiàn)的。2017-11-11