網(wǎng)站首頁 編程語言 正文
View觸發(fā)機(jī)制API實現(xiàn)GestureDetector?OverScroller詳解_Android
作者:陳序猿_Android ? 更新時間: 2022-12-21 編程語言前言
前一篇文章講了View的觸發(fā)反饋機(jī)制的原理,對于一個自定義View而言,手勢的處理都是重寫onTouchEvent函數(shù),或者通過setOnTouchEventListener方法捕捉手勢。但是手勢的處理,如滑動、觸摸、雙擊等檢測對應(yīng)的檢測也并不是那么簡單,自己一個個造輪子也過于麻煩,萬幸的是google早已經(jīng)給開發(fā)者提供了手勢捕捉的類- GestureDetector
。通過這個類我們可以識別很多的手勢,主要是通過他的onTouchEvent(event)方法完成了不同手勢的識別。雖然他能識別手勢,但是不同的手勢要怎么處理,應(yīng)該是提供給程序員實現(xiàn)的。
GestureDetector
在GestureDetector
中一共有三種主要的回調(diào)接口 ,OnGestureListener
、OnDoubleTapListener
、OnContextClickListener
這三個接口的方法如下。
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)聽手勢,而 OnDoubleTapListener
、OnGestureListener
用于開發(fā)者自己去處理對應(yīng)手勢的反饋
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
事件時被調(diào)用的,其的返回值決定了View
是否消費該事件,一般我們肯定是需要消費該事件的,因此其值為true.
public boolean onDown() { return true; }
onShowPress方法
@Override public void onShowPress(MotionEvent e) { //進(jìn)行控件顏色的改變或其他一些動作 }
onShowPress
是用戶按下時的一種回調(diào),主要作用是用于給用戶一種按壓下的狀態(tài),可以在該回調(diào)中讓控件顏色改變或進(jìn)行一些動作。需要注意的是,onShowPress 方法不是立即回調(diào)的,在手指觸碰后,在100ms左右后才會回調(diào)。在這100ms內(nèi)如果手指抬起或滾動,該回調(diào)方法不會被觸發(fā)。在前一篇文章View事件分發(fā)機(jī)制
中提到過自定義View
默認(rèn)的super.onTouchEvent
實現(xiàn)中,按壓狀態(tài)也是有一個預(yù)按壓狀態(tài)的檢測,此處的onShowPress
的回調(diào)機(jī)制也是同理。
onLongPress 方法
用于檢測長按事件的,即手指按下后不抬起,在一段時間后會觸發(fā)該事件。
@Override public void onLongPress(MotionEvent e) { }
onLongPress
回調(diào)被觸發(fā)前 onShowPress
一定會被觸發(fā)。
需要注意的是 onLongPress
一旦被觸發(fā),其他事件都不會被觸發(fā)了。
不過,onLongPress
事件可以被禁止使用,通過如下代碼設(shè)置,即不會觸發(fā)長按事件
gestureDetector.setIsLongpressEnabled(false);
onSingleTapUp 方法
@Override public boolean onSingleTapUp(MotionEvent e) { return false; }
onSingleTapUP
的返回值不是太重要,不過一般消費了就還是返回ture吧。
onSingleTapUp
的意思顧名思義,即在 手指抬起時觸發(fā),不過他跟一般的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 | 在雙擊的第一次抬起時觸發(fā) |
onSingleTapConfirmed | 0 | 雙擊發(fā)生時不會觸發(fā)。 |
onClick | 2 | 在雙擊事件時觸發(fā)兩次。 |
可以看出來這三個事件還是有所不同的,根據(jù)自己實際需要進(jìn)行使用即可
onScroll
@Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return true; }
onScroll
方法是用于監(jiān)聽手指的滑動的,e1是第一次ACTION_DOWN
的事件,e2是當(dāng)前滾動事件。distanceX、distanceY記錄了手指在x、y軸滑動的距離。
需要注意的時,該滑動距離記錄的是上次滑動回調(diào)與這次回調(diào)之間的距離差值。且還有一個有意思的注意事項,該差值是 lastEvent-curEvent 得到的,這與正常的邏輯行為不太一致,不過google就這樣干了,所以當(dāng)我們在計算滑動偏移量時需要對 distanceX、distancesY進(jìn)行一個 相減的操作而不是相加。
onFling
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return true; }
用戶手指在屏幕快速滑動后,在抬起時(ACTION_UP
)觸發(fā)該事件。
Fling 中文直接翻譯過來就是一扔、拋、甩,最常見的場景就是在 ListView 或者 RecyclerView 上快速滑動時手指抬起后它還會滾動一段時間才會停止。onFling 就是檢測這種手勢的。
四個參數(shù)的介紹如下
參數(shù) | 簡介 |
---|---|
e1 | 手指按下時的 Event。 |
e2 | 手指抬起時的 Event。 |
velocityX | 在 X 軸上的運動速度(像素/秒)。 |
velocityY | 在 Y 軸上的運動速度(像素/秒)。 |
利用 velocityX
、velocityY
參數(shù)可以實現(xiàn)一個具有一定初速度的滑動,之后該速度隨著滑動衰減,直到停止。
一般onFling
可以結(jié)合 OverScroller
實現(xiàn)一個均勻減速的滑動效果。
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)聽單擊事件,而onDoubleTap
用于監(jiān)聽雙擊事件。這兩個回調(diào)函數(shù)是互斥的。
onSingleTapConfigrmed
的調(diào)用是延遲的,其在 手指按下300ms后觸發(fā)。
onSingleTapConfigrmed
適合于在 既檢測單擊事件也檢測雙擊時間時使用。
但是如果只是檢測單擊事件,onSingleTapUp
更合適,onSingleTapConfigrmed
會讓用戶明顯感覺到延遲。
需要注意的是 onDoubleTap
事件并不是第二次抬起時觸發(fā)的,而是第二次手觸摸到屏幕時即(第二次ACTION_DOWN)事件時就會觸發(fā)該事件,如果要保證在第二次抬起時才觸發(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; }
雙擊時,onDoubleTapEvent
將會在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
需要注意的是不論是雙擊還是單擊,只要按下長時間未動且未抬起,都會觸發(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
方法中,曾說過 使用velocityX
,velocityY
兩個參數(shù)可以實現(xiàn) View
的滑動效果.
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return true; }
示例
此處用一個可拖拉滑動的小圓球作為示例.
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 //球直徑長度 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) //中間位置畫個圓 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 //移動不能超過圓的一半 offsetX = min(offsetX,width.toFloat()/2) offsetX = max(offsetX,-width.toFloat()/2) //移動不能超過圓的一半 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 { //限制滑動不能超過一小圓的一半 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) { //實現(xiàn)邏輯省略,有興趣的可以自己去看代碼 }
參數(shù) | 簡介 |
---|---|
startX、startY | 開始滑動的X(Y)軸位置 |
velocityX、velocityY | 在 X(Y) 軸上的運動速度(像素/秒)。 |
minX、maxX | 滑動時X軸的兩個邊界值,滑動時一旦到達(dá)邊界值,則立刻停止 |
minY、maxY | 滑動時Y軸的兩個邊界值,滑動時一旦到達(dá)邊界值,則立刻停止 |
overX、overY | 在滑動時,可超出的滑動值,可超過邊界值,不過超過邊界值后,又會重新滑動回來 |
-
startScroll
方法
startScroll
的滾動默認(rèn)以一種粘性液體的效果進(jìn)行滾動。
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ù) | 簡介 |
---|---|
startX、startY | 開始滑動的X(Y)軸位置 |
dx、dy | 滾動到達(dá)的目標(biāo)位置 |
duration | 滾動花費時間(單位ms),如果不指定默認(rèn)時250ms |
原文鏈接:https://juejin.cn/post/7167720906540711966
相關(guān)推薦
- 2022-11-03 anaconda?部署Jupyter?Notebook服務(wù)器過程詳解_python
- 2022-11-15 Flutter有無狀態(tài)類與State及生命周期詳細(xì)介紹_Android
- 2021-12-19 Redis中緩存穿透/擊穿/雪崩問題和解決方法_Redis
- 2022-09-27 C#校驗時間格式的場景分析_C#教程
- 2022-03-16 Linux環(huán)境下安裝nginx教程_nginx
- 2022-10-09 C#實現(xiàn)插入排序_C#教程
- 2022-08-16 Hive導(dǎo)入csv文件示例_數(shù)據(jù)庫其它
- 2022-05-13 vscode代碼格式化 保存時候自動修改為合規(guī)代碼
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支