不知道大家平時(shí)在開發(fā) Android 項(xiàng)目時(shí)有沒有好奇我們經(jīng)常使用的控件如 Button先馆、TextView 等是怎么響應(yīng)我們手指點(diǎn)擊的读规?或者滑動(dòng)控件如 RecyclerView摇展、ViewPager 等是怎么隨著我們手指滑動(dòng)的硫惕?當(dāng)然叁巨,諸如此類場(chǎng)景很多很多财岔,比如下拉刷新啊、拖動(dòng)排序啊等等龟虎,可以說璃谨,只要是手機(jī)屏幕上產(chǎn)生的效果是通過用戶手指觸發(fā)的,都要涉及到 Android 的事件機(jī)制,今天佳吞,我們就來理理 Android 的事件機(jī)制到底是怎么回事拱雏,走起!
首先我們通過一張流程圖來大體了解下 Android 的一個(gè)事件是怎樣分發(fā)下去的:
事件序列
接下來我們說下什么叫事件序列底扳,通過上面一張圖铸抑,我們不難看出一個(gè)事件由開始到結(jié)束經(jīng)歷了很多的成員及方法,那么一個(gè)事件序列就是指從手指接觸屏幕的那一刻起衷模,到手指離開屏幕的那一刻結(jié)束時(shí)這個(gè)過程中產(chǎn)生的一系列動(dòng)作鹊汛。一個(gè)事件序列中包含多個(gè)動(dòng)作(按下、滑動(dòng)等)算芯,如果一個(gè)動(dòng)作不被一個(gè) View 所消耗,那么這個(gè)事件序列后續(xù)的動(dòng)作也不會(huì)再交由這個(gè) View 處理凳宙,例如一個(gè) View 不消耗按下動(dòng)作時(shí)熙揍,那么后續(xù)的滑動(dòng)等動(dòng)作也不會(huì)收到了
示例代碼
事件監(jiān)聽器及事件處理器
class EventActivity : AppCompatActivity() {
private val DEBUG_TAG = "EventActivity"
private lateinit var mView:View
private lateinit var mViewGroup:ViewGroup
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mViewGroup = object : ViewGroup(this){
override fun onLayout(changed: Boolean,
l: Int,
t: Int,
r: Int,
b: Int) {}
}
/**
* View 事件監(jiān)聽器
*/
mView = View(this)
mView.setOnClickListener{view ->}
mView.setOnLongClickListener{view -> false}
mView.setOnTouchListener{view,motionevent-> false}
mView.setOnFocusChangeListener { view, hasFocus -> }
mView.setOnContextClickListener { view -> false }
mView.setOnCreateContextMenuListener{menu, view, menuInfo -> }
}
/**
* View 中也有此方法
*/
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
// ViewParent 接口方法,子 View 要求父容器不要攔截我的事件
// 注意自定義 ViewGroup 時(shí)一定不要攔截 ACTION_DOWN 事件
// 不然后續(xù)事件永遠(yuǎn)無法傳遞到子 View
mView.parent.requestDisallowInterceptTouchEvent(true)
return super.dispatchTouchEvent(ev)
}
/**
* 返回 true 表示攔截事件氏涩,ViewGroup 及其子類才有此方法
*/
/*override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
return super.onInterceptTouchEvent(ev)
}*/
/**
* View 中也有此方法
* 注意下 MotionEventCompat 類不是 MotionEvent 的替代届囚,
* 而是操作 MotionEvent 的輔助類
*/
override fun onTouchEvent(event: MotionEvent): Boolean {
// 以下的 3 種方法均可獲取事件動(dòng)作類型
val action = event.actionMasked
// val action = event.action
// val action = MotionEventCompat.getActionMasked(event)
return when (action) {
MotionEvent.ACTION_DOWN -> {
Log.d(DEBUG_TAG, "Action was DOWN")
true
}
MotionEvent.ACTION_MOVE -> {
Log.d(DEBUG_TAG, "Action was MOVE")
true
}
MotionEvent.ACTION_UP -> {
Log.d(DEBUG_TAG, "Action was UP")
true
}
MotionEvent.ACTION_CANCEL -> {
Log.d(DEBUG_TAG, "Action was CANCEL")
true
}
MotionEvent.ACTION_OUTSIDE -> {
Log.d(DEBUG_TAG, "Movement occurred outside bounds of current screen element")
true
}
else -> super.onTouchEvent(event)
}
}
/**
* View 中也有此方法
*/
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
return super.onKeyDown(keyCode, event)
}
/**
* View 中也有此方法
*/
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
return super.onKeyUp(keyCode, event)
}
/**
* View 中也有此方法
*/
override fun onKeyLongPress(keyCode: Int, event: KeyEvent?): Boolean {
return super.onKeyLongPress(keyCode, event)
}
/**
* View 中也有此方法
*/
override fun onKeyMultiple(keyCode: Int,
repeatCount: Int,
event: KeyEvent?): Boolean {
return super.onKeyMultiple(keyCode, repeatCount, event)
}
/**
* OnCreateContextMenuListener 也有此方法
*/
override fun onCreateContextMenu(menu: ContextMenu?,
v: View?,
menuInfo: ContextMenu.ContextMenuInfo?) {
super.onCreateContextMenu(menu, v, menuInfo)
}
/**
* View 有一 onFocusChanged 方法
*/
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
}
}
我們一般將以 XxxListener 結(jié)尾的類中的方法稱作事件監(jiān)聽方法,其它與事件處理相關(guān)的方法叫做事件處理方法
GestureDetectorCompat 使用
private const val DEBUG_TAG = "Gestures"
/**
* 如果只想覆寫 GestureDetector.OnGestureListener 部分方法是尖,
* 可實(shí)現(xiàn) GestureDetector.SimpleOnGestureListener 接口
*/
class MainActivity :
Activity(),
GestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener,
GestureDetector.OnContextClickListener{
private lateinit var mDetector: GestureDetectorCompat
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mDetector = GestureDetectorCompat(this, this)
mDetector.setOnDoubleTapListener(this)
mDetector.setContextClickListener(this)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
return if (mDetector.onTouchEvent(event)) {
true
} else {
super.onTouchEvent(event)
}
}
/**
* 注意此方法需返回 true意系,不然不會(huì)有后續(xù)方法的回調(diào)
*/
override fun onDown(event: MotionEvent): Boolean {
Log.d(DEBUG_TAG, "onDown: $event")
return true
}
override fun onFling(
event1: MotionEvent,
event2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
Log.d(DEBUG_TAG, "onFling: $event1 $event2")
return true
}
override fun onLongPress(event: MotionEvent) {
Log.d(DEBUG_TAG, "onLongPress: $event")
}
override fun onScroll(
event1: MotionEvent,
event2: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
Log.d(DEBUG_TAG, "onScroll: $event1 $event2")
return true
}
override fun onShowPress(event: MotionEvent) {
Log.d(DEBUG_TAG, "onShowPress: $event")
}
override fun onSingleTapUp(event: MotionEvent): Boolean {
Log.d(DEBUG_TAG, "onSingleTapUp: $event")
return true
}
override fun onDoubleTap(event: MotionEvent): Boolean {
Log.d(DEBUG_TAG, "onDoubleTap: $event")
return true
}
override fun onDoubleTapEvent(event: MotionEvent): Boolean {
Log.d(DEBUG_TAG, "onDoubleTapEvent: $event")
return true
}
override fun onSingleTapConfirmed(event: MotionEvent): Boolean {
Log.d(DEBUG_TAG, "onSingleTapConfirmed: $event")
return true
}
override fun onContextClick(e: MotionEvent?): Boolean {
Log.d(DEBUG_TAG, "onContextClick: $event")
return true
}
}
手指移動(dòng)速度監(jiān)測(cè)
private const val TAG_VELOCITY = "velocity"
class VelocityActivity: AppCompatActivity(){
private var mVelocityTracker:VelocityTracker? = null
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
mVelocityTracker?.clear()
mVelocityTracker = mVelocityTracker ?: VelocityTracker.obtain()
mVelocityTracker?.addMovement(event)
}
MotionEvent.ACTION_MOVE -> {
mVelocityTracker?.apply {
val pointerId: Int = event.getPointerId(event.actionIndex)
addMovement(event)
// 計(jì)算速度,參數(shù)單位為毫秒饺汹,即每多少秒的速度
// 調(diào)用了這個(gè)方法后才能分別拿到 x蛔添、y 的速度
computeCurrentVelocity(1000)
val xV = VelocityTrackerCompat.getXVelocity(mVelocityTracker,pointerId)
val yV = VelocityTrackerCompat.getYVelocity(mVelocityTracker,pointerId)
Log.d(TAG_VELOCITY, "X velocity: $xV")
Log.d(TAG_VELOCITY, "Y velocity: $yV")
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
mVelocityTracker?.recycle()
mVelocityTracker = null
}
}
return true
}
}
相關(guān)方法
方法名 | 所在類 | 備注 |
---|---|---|
getHistoricalX | MotionEvent | MotionEvent 類還有很多 getHistoricalXXX 方法可以獲取歷史事件數(shù)據(jù),例如還有歷史手指壓力兜辞、時(shí)間數(shù)據(jù)等 |
getScaledTouchSlop | ViewConfiguration | ViewConfiguration 類為我們提供了很多有用的系統(tǒng)常量迎瞧,例如:被認(rèn)為是一次滑動(dòng)操作的最小距離、最大滑動(dòng)速度等逸吵,用法如下: ViewConfiguration.get(context).getScaledTouchSlop() |
總結(jié)
本篇文章在編寫時(shí)參考了官方文檔的 觸摸事件 介紹及一些自己的總結(jié)凶硅,希望能對(duì)大家理解 Android 事件機(jī)制有所幫助