Android 事件機(jī)制

不知道大家平時(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ā)下去的:


android_event_dispatch.png


事件序列

接下來我們說下什么叫事件序列底扳,通過上面一張圖铸抑,我們不難看出一個(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ī)制有所幫助

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市扫皱,隨后出現(xiàn)的幾起案子足绅,更是在濱河造成了極大的恐慌,老刑警劉巖韩脑,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件氢妈,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡段多,警方通過查閱死者的電腦和手機(jī)允懂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來衩匣,“玉大人蕾总,你說我怎么就攤上這事粥航。” “怎么了生百?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵递雀,是天一觀的道長。 經(jīng)常有香客問我蚀浆,道長缀程,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任市俊,我火速辦了婚禮杨凑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘摆昧。我一直安慰自己撩满,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布绅你。 她就那樣靜靜地躺著伺帘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪忌锯。 梳的紋絲不亂的頭發(fā)上伪嫁,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音偶垮,去河邊找鬼张咳。 笑死,一個(gè)胖子當(dāng)著我的面吹牛似舵,可吹牛的內(nèi)容都是我干的晶伦。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼啄枕,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼婚陪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起频祝,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤绞吁,失蹤者是張志新(化名)和其女友劉穎乖阵,沒想到半個(gè)月后规脸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鼎兽,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年漓糙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铣缠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蝗蛙,靈堂內(nèi)的尸體忽然破棺而出蝇庭,到底是詐尸還是另有隱情,我是刑警寧澤捡硅,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布哮内,位于F島的核電站,受9級(jí)特大地震影響壮韭,放射性物質(zhì)發(fā)生泄漏北发。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一喷屋、第九天 我趴在偏房一處隱蔽的房頂上張望琳拨。 院中可真熱鬧,春花似錦屯曹、人聲如沸狱庇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽僵井。三九已至陕截,卻和暖如春驳棱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背农曲。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工社搅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人乳规。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓形葬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親暮的。 傳聞我的和親對(duì)象是個(gè)殘疾皇子笙以,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容