結合原始碼,重溫 Android View 的事件處理知多少 ?
前言
- Android View 的 事件處理在我們的程式設計中,可謂是無處不在了。但對於大多數人而言,一直都是簡單的使用,對其原理缺乏深入地認識。
- 學 Android 有一段時間了,最近發現,很多基礎知識開始有些遺忘了,所以從新複習了 View 的事件分發。特地整理成了這篇文章分享給大家。
- 本文不難,可以作為大家茶餘飯後的休閒。
祝大家閱讀愉快!
方便大家學習,我在 GitHub 上建立個 倉庫
倉庫內容與部落格同步更新。由於我在
稀土掘金
簡書
CSDN
部落格園
等站點,都有新內容釋出。所以大家可以直接關注該倉庫,即使獲得精彩內容倉庫地址:
超級乾貨!精心歸納Android
JVM
、演算法等,各位帥氣的老鐵支援一下!給個 Star !
一、View 的事件回撥
- 我們結合原始碼看看
View
的事件分發是個怎樣的過程,首先我們建立一個類MyButton
類繼承AppCompatButton
用於測試:
public class MyButton extends AppCompatButton { private final String TAG = "DeBugMyButton"; public MyButton(Context context) { super(context); } public MyButton(Context context, AttributeSet attrs) { super(context, attrs); } public MyButton(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } }
1.1 事件分發流程
- 我們都知道有一個方法叫做
public boolean dispatchTouchEvent(MotionEvent event)
。首先我們要知道,對於我們這個自定義控制元件,他的觸控事件都是從我們dispatchTouchEvent
這個方法開始往下去分發的。所以可以說:這個方法是一個入口方法。
1.1.1 onTouchEvent 作用
- 現在我們重寫該方法和另一個方法:
onTouchEvent
,並且列印一行日誌:
@Override public boolean dispatchTouchEvent(MotionEvent event) { Log.d(TAG, "----on dispatch Touch Event----"); return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "----on touch event----"); } return super.onTouchEvent(event); }
- 然後我們在
MainActivity
中,設定一個例項化一個MyButton
控制元件物件用於測試,並且給他新增一個onClickListenter
和setOnTouchListener
public class MainActivity extends AppCompatActivity {
private final String TAG = "DeBugMainActivity";
/**
* 自定義控制元件 MyButton
*/
private MyButton mMyButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iniView();
}
/**
* 例項化控制元件
*/
private void iniView() {
mMyButton = findViewById(R.id.my_button);
mMyButton.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "----on touch----");
break;
default:
break;
}
return false;
}
});
mMyButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "----on click----");
}
});
}
}
- 然後我們執行這個
Demo
,點選MyButton
按鈕,會的到如下日誌:
我們可以看到首先回調了這個
dispatchTouchEvent
,然後是它的監聽器OnTouch
,接著是它的onTouchEvent
,最後又執行了dispatchTouchEvent
,那麼這是為什麼呢?這是因為我們這兒只監聽了
ACTION_DOWN
而當手指抬起時它同樣還回去回撥dispatchTouchEvent
,最後我們列印OnClick
的回撥。總結一下就是:
dispatchTouchEvent
->setOnTouchListener
->onTouchEvent
->setOnClickListener
說明我們
setOnClickListener
是通過onTouchEvent
處理,產生了OnClick
。一會我們再來看看其中的原理。既然說
dispatchTouchEvent
像一個入口,就先讓我們來看下它是怎麼處理和操作的: 首先,既然我們呼叫了super.dispatchTouchEvent(event)
,那麼我們就來看看它父類中是怎麼實現該方法的。不信的是,它的父類AppCompatButton
也沒有實現該方法 ,最後經過層層搜尋,我們發現這個方法是屬於View
的方法。
1.1.2 dispatchTouchEvent 的實現
- 那麼現在我們來看看
View
的dispatchTouchEvent
怎麼實現的:
public boolean dispatchTouchEvent(MotionEvent event) {
......
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
- 在
dispatchTouchEvent
中,我們可以發現下面這樣一個程式碼塊
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
不難看出:如果執行了這個程式碼段,那麼後面的方法就不會執行了,並且
dispatchTouchEvent
會返回true
。我們再仔細觀察下其中的條件:在if
條件中我們發現:只有當其滿足li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event))
時才會執行if
內的操作經過上面分析,我們可以知道:
onTouch
事件必須返回true
時,才會執行該方法塊。那麼我們就回到MainActivity
中。我們發現setOnTouchListener
的onTouch
預設返回值是false
( 不滿足返回值為true
), 這就表明他會繼續去執行下一個程式碼塊:
if (!result && onTouchEvent(event)) {
result = true;
}
執行這個
if
語句的過程中。首先呼叫了onTouchEvent
方法。這就解釋了,為什麼它先執行了mOnTouchListener
,然後再執行onTouchEvent
。現在我們就可以總結一下:首先我們回調了
dispatchTouchEvent
,然後回撥OnTouchListener
。這個時候,如果TouchListener
沒有return true
,那麼就會接著去執行onTouchEvent
( 當然,如果return true
後面的層級就不會執行了 。一句話說就是:到那個層級return true
那麼哪個層級就消費掉了這個事件 )。
1.1.3 onTouchEvent 的處理
- 同時我們還有一個結果:我們
onClick
( 包括我們的onLongClick
) 是來自於我們onTouchEvent
這個方法的處理。那麼下面我們就來看看View
中是怎麼處理onTouchEvent
的:
public boolean onTouchEvent(MotionEvent event) {
。。。
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
。。。
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
。。。
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}
二、onClick 和 OnLongClick
- 因為我們是拿
ACTION_DOWN
作為舉例的。那麼我們先來分析一下case MotionEvent.ACTION_DOWN
: 中onTouchEvent
是怎麼執行的,以及onClick
和OnLongClick
是如何產生的:
2.1 onClick 和 OnLongClick 的產生
首先,當我們手指按下時,有一個
mHasPerformedLongPress
標識會先被設為false
。再往下會執行一行postDelayed(mPendingCheckForTap
和ViewConfiguration.getTapTimeout())
; 我們來看看這一行的作用:首先,從名字我們就可以猜測,這是個延時執行的方法。我們進一步閱讀發現
mPendingCheckForTap
是一個Runnable
動作;ViewConfiguration.getTapTimeout()
是一個100mm
的延時。也就是說延時100mm
後去執行mPendingCheckForTap
中的動作。那麼我們就來看看mPendingCheckForTap
中做了什麼:
private final class CheckForTap implements Runnable {
public float x;
public float y;
@Override
public void run() {
mPrivateFlags &= ~PFLAG_PREPRESSED;
setPressed(true, x, y);
checkForLongClick(ViewConfiguration.getTapTimeout(), x, y);
}
}
- 也就是說,停一百秒後就開始檢查,使用者的手指是否離開了螢幕。( 就是當前
ACTION_DOWN
之後,有沒有觸發了ACTION_UP
這個環節 ),但是ACTION_DOWN
後,我們還有一個ACTION_MOVE
過程。在這個ACTION_MOVE
中,如果100mm
內離開了螢幕、或者離開了這個控制元件就會觸發ACTION_UP
,那麼就認為這是一個點選事件onClick
。如果沒有觸發ACTION_UP
的話,就會再延時400mm
。
2.2 ACTION_DOWN 之後流程
- 在
ACTION_DOWN
之後,會先等100mm
- 如果沒有離開螢幕或者離開控制元件,就是沒有觸發
ACTION_UP
的話,就會再延時 400mm。 - 這
500mm
後就會觸發onLongClick
事件。
2.3 那麼我們現在來驗證一下 onLongClick :
- 首先再
MainActivity
中加上:
mMyButton.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return true;
}
});
- 接著,我們發現
OnLongClick
是有返回值的,如果返回值是false
還會接著去觸發onClick
事件,如果返回true
的話,那麼這個長按事件就直接被消費掉了( 也就是這個點選事件就不會完後傳遞到OnClickListener
中去了 )。
2.4 總結
100mm
時為點選,500mm
時為長按,接著觸發長按事件。- 再看長按事件的返回值,如果時
true
就結束。 - 如果時
false
那麼OnClickListener
就同樣也被執行。 - 這就是由
obTouchEvent
產生出來的onClick/onLongClick
的來龍去脈。
總結
- 我們
View
的事件方法,基本上就是這麼一個思路,從dispatchTouchEvent
到OnTouchListener
監聽器,再到onTouchEvent
,接著onTouchEvent
由產生了onClick/onLongClick
。 - 如果大家感興趣的話可以更深入的去閱讀原始碼。
重點
:學Android
有一段時間了,我打算好好的梳理一下所學知識,包括Activity
、Service
、BroadcastRecevier
事件分發、滑動衝突、新能優化等所有重要模組,歡迎大家關注 _yuanhao 的 部落格園 ,方便及時接收更新- 如果有可以補充的知識點,歡迎大家在評論區指出。
碼字不易,你的點贊是我總結的最大動力!
由於我在「稀土掘金」「簡書」「
CSDN
」「部落格園」等站點,都有新內容釋出。所以大家可以直接關注我的GitHub
倉庫,以免錯過精彩內容!倉庫地址:
超級乾貨!精心歸納Android
、JVM
、演算法等,各位老鐵支援一下!給個 Star !一萬多字長文,加上精美思維導圖,記得點贊哦,歡迎關注 _yuanhao 的 部落格園 ,我們下篇文章見!
相關推薦
結合原始碼,重溫 Android View 的事件處理知多少 ?
前言 Android View 的 事件處理在我們的程式設計中,可謂是無處不在了。但對於大多數人而言,一直都是簡單的使用,對其原理缺乏深入地認識。 學 Android 有一段時間了,最近發現,很多基礎知識開始有些遺忘了,所以從新複習了 View 的事件分發。特地整理成了這篇文章分享給大家。 本文不難,可以作
Android-View事件處理機制
又遇到過監聽事件無效的情況,然後找了一些資料。在這裡做點筆記方便以後忘了複習! 一,先看一段程式碼:我自定義一個MyButton繼承Button,並重寫了dispatchT
Activity啟動流程,介面繪製到事件處理的整個流程(基於Android6.0原始碼)(2)
void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) { synchronized (this) { ...... mWindowAttributes
Android View 事件分發機制原始碼詳解(ViewGroup篇)
前言 我們在學習View的時候,不可避免會遇到事件的分發,而往往遇到的很多滑動衝突的問題都是由於處理事件分發時不恰當所造成的。因此,深入瞭解View事件分發機制的原理,對於我們來說是很有必要的。由於View事件分發機制是一個比較複雜的機制,因此筆者將寫成兩篇文
Android View 事件分發機制 原始碼解析(ViewGroup篇)
1. 前言 android點選 事件一直以來都是很多安卓程式設計師的心病,之前通過demo模擬總結出一些經驗,但是不看原始碼的程式設計師不是好程式設計師,這段時間,系統的梳理了下整個事件傳遞的原始碼,希望可以幫助大家徹底理解andriod的點選
Android按鍵事件處理流程 -- 從事件被派發到View層次結構的根節點DecorView開始分析
http://www.2cto.com/kf/201406/311432.html http://www.cxyclub.cn/n/48237/ 剛接觸Android開發的時候,對touch、key事件的處理總是一知半解,一會是Activity裡的方法,一會
談談我對Android View事件分發的理解
event 調用 ack 處理 group ans import ras 運行 寫這篇博客的緣由。近期因為項目中用到相似一個LinearLayout中水平布局中,有一個TextView和Button,然後對該LinearLayout布局設置點擊事件。點擊
Android View事件分發機制
作為程式猿,最不想 看的但是也不得不去看的就是原始碼!所謂知其然也要知其所以然,神祕的大佬曾經說過進階的方法就是READ THE FUCKING CODE! 認識MotionEvent 負責集中處理所有型別裝置的輸入事件.我們對螢幕的點選,滑動,擡起等一系的
Android View事件傳遞機制
view事件傳遞機制,在很多面試中會問道,我曾經也被問道,卻沒有回答上來。 今天我在這裡寫了一個demo去理解這個view的事件傳遞機制。 首先這個view包括兩種,viewGroup和普通view。viewGroup就是裡面還可以包含子控制元件的那種,如Linear
Android View事件體系總結一
什麼是View? View是Android中所有控制元件的基類,是一種介面層的控制元件的一種抽象,它代表了一個控制元件。 View的位置引數 View的位置主要由它的四個頂點決定,分別對應View的四個屬性:top,left,right,bottom。其中top是左上角
Fragment中RecyclerView的使用解析,以及監聽事件處理
RecyclerView是可以代替listview使用的新元件,個人感覺其主要特色:其介面卡adapter中,重寫的東西少了,頁面展示的效果跟加多了,比如可以在RecyclerView設定listview的顯示效果,也可以設定gridview的顯示效果,也可以設定瀑布流的顯示效果!下面程式碼主要
Android UI事件處理——實現事件監聽介面的4種方法
前段時間看到一個同學的android課程有這樣一個作業要求:....... 非內部類實現onClickListener監聽介面的方式監聽按鈕單擊事件 ....... 感覺蠻奇怪,一般對於UI事件的處理
【js事件詳解】js事件封裝函式,js跨瀏覽器事件處理機制
一、事件流 事件流描述的是從頁面中接受事件的順序。 IE的事件流是事件冒泡流,而Netscape的事件流是事件捕獲流 1、事件冒泡 事件冒泡,即事件最開始由最具體的元素(文件中巢狀層次最深的那個節點)接收,然後逐級向上轉播至最不具體的節點(文件)。 2、事件捕獲 事件捕獲的
android view事件OnTouch重複觸發
程式碼: img.setOnTouchListener( object : View.OnTouchListener{ &nb
Android按鍵事件處理流程
剛接觸Android開發的時候,對touch、key事件的處理總是一知半解,一會是Activity裡的方法,一會是各種View 中的,自己始終不清楚到底哪個在先哪個在後,總之對整個處理流程沒能很好的把握。每次寫這部分程式碼的時候都有些心虛, 因為我不是很清楚什麼時候、
一文讀懂Android View事件分發機制
Android View 雖然不是四大元件,但其並不比四大元件的地位低。而View的核心知識點事件分發機制則是不少剛入門同學的攔路虎。ScrollView巢狀RecyclerView(或者ListView)的滑動衝突這種老大難的問題的理論基礎就是事件分發機制。 事件
jQuery中的屬性操作,jQuery中的事件處理、jQuery 中的動畫簡單介紹
jQuery中的屬性操作,jQuery中的事件處理、jQuery 中的動畫簡單介紹 getAttribute(‘name’) setAttribute(‘name’, ‘Tom’) attr(): 獲取屬性和設定屬性 當為該方法傳遞一個引數時, 即為某元素的獲取指定屬性 當為該方法傳遞兩個引數時, 即為
【Android View事件(四)】View滑動與實現滑動的幾種方法
1 前言 在前面的幾篇文章,我向大家介紹的都是單一View事件,而在這篇文章中,我將向大家介紹連續的事件 —— 滑動。 在安卓裝置上滑動幾乎是應用的標配,由於安卓手機螢幕較小,為了給使用者呈現更多的內容,就需要使用滑動來隱藏和顯示一些內容。
Android-view事件傳遞機制
Android中dispatchTouchEvent,onInterceptTouchEvent, onTouchEvent的理解 Android中的事件型別分為按鍵事件和螢幕觸控事件,Touch事件是螢幕觸控事件的基礎事件,有必要對它進行深入的瞭解。
android 開發 View _14 MotionEvent和事件處理詳解,與實踐自定義滑動條View
MotionEvent MotionEvent物件是與使用者觸控相關的時間序列,該序列從使用者首次觸控式螢幕幕開始,經歷手指在螢幕表面的任何移動,直到手指離開螢幕時結束。手指的初次觸控(ACTION_DOWN操作),滑動(ACTION_MOVE操作)和擡起(ACTION