onTouch先執行,還是onClick執行?

来源:http://www.cnblogs.com/ganchuanpu/archive/2017/03/24/6613533.html
-Advertisement-
Play Games

有一個Button 按鈕,要想為該按鈕設置onClick事件和OnTouch事件 此時,我們現在分析一下,是onTouch先執行,還是onClick執行,接下來我從FrameWork 源碼去探尋一下整個事件的執行流程和原理: 我們知道 Button ,TextView 等基礎控制項的基類都是View, ...


有一個Button 按鈕,要想為該按鈕設置onClick事件和OnTouch事件

mTestButton.setOnClickListener(new View.OnClickListener() {  
           @Override  
           public void onClick(View view) {  
               Log.d(TAG, "onClick execute");  
           }  
});  
mTestButton.setOnTouchListener(new View.OnTouchListener() {  
           @Override  
           public boolean onTouch(View view, MotionEvent motionEvent) {  
               Log.d(TAG, "onTouch execute, action event " + motionEvent.getAction());  
               return false;  
           }  
});  

  此時,我們現在分析一下,是onTouch先執行,還是onClick執行,接下來我從FrameWork 源碼去探尋一下整個事件的執行流程和原理:

  我們知道 Button ,TextView 等基礎控制項的基類都是View,只要你觸摸到了任何一個控制項,就一定會調用該控制項的dispatchTouchEvent方法。那當我們去點擊按鈕的時候,就會去調用Button類(實際上是基類View)里的dispatchTouchEvent方法,所以接下來看View源碼中dispatchTouchEvent()方法的具體實現:

public boolean dispatchTouchEvent(MotionEvent event) {  
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
            mOnTouchListener.onTouch(this, event)) {  
        return true;  
    }  
    return onTouchEvent(event);  
}  

  分析上述代碼,第2行 如果三個條件都為真的話,就返回true,否則執行onTouchEvent,先看第一個條件mOnTouchListener!=null,這個條件就是如果設置了OnTouchListener就會為true,否則是false; 第二個條件(mViewFlags & ENABLED_MASK) == ENABLED是判斷當前點擊的控制項是否是enable的,按鈕預設都是enable的,因此這個條件恆定為true;第三個條件就比較複雜了,mOnTouchListener.onTouch(this, event),這個其實就是去回調控制項註冊touch事件時的onTouch方法。也就是說如果我們在onTouch方法里返回true,就會讓這三個條件全部成立,從而整個方法直接返回true。如果我們在onTouch方法里返回false,就會再去執行onTouchEvent(event)方法。onTouchEvent(MotionEvent event)方法同樣也是在view中定義的一個方法,主要是處理傳遞到view 的手勢事件,包括ACTION_DOWN,ACTION_MOVE,ACTION_UP,ACTION_CANCEL四種事件。

   接下來我們結合上面的具體例子,來分析一下這個過程,首先會執行dispatchTouchEvent(MotionEvent event) ,所以onTouch方法肯定是早於onClick方法的,如果在onTouch里返回false,就會出現下麵的現象:

10-20 18:57:49.670: DEBUG/MainActivity(20153): onTouch execute, action event 0
10-20 18:57:49.715: DEBUG/MainActivity(20153): onTouch execute, action event 1
10-20 18:57:49.715: DEBUG/MainActivity(20153): onClick execute

    即先執行了onTouch,再執行了onClick事件,而且onTouch執行了兩次,一個是action_down,一個是action_up事件;

如果onTouch里返回true,則出現下麵的現象:

10-20 19:01:59.795: DEBUG/MainActivity(21010): onTouch execute, action event 0
10-20 19:01:59.860: DEBUG/MainActivity(21010): onTouch execute, action event 1

   結果是onClick事件沒有執行了,原因是如果onTouch返回true的話,則dispatchEvent(MotionEvent event)方法直接返回true了,相當於不往下傳遞事件了,所以onClick不會執行,相反如果onTouch返回false的話(此時會執行onClick方法),則會執行 onTouchEvent(MotionEvent event)方法,由此可以得出這樣一個結論,onClick事件的具體調用執行肯定是在onTouchEvent(MotionEvent event)方法源碼中,接下來分析一下該函數的源碼:

 1 public boolean onTouchEvent(MotionEvent event) {  
 2     final int viewFlags = mViewFlags;  
 3     if ((viewFlags & ENABLED_MASK) == DISABLED) {  
 4         // A disabled view that is clickable still consumes the touch  
 5         // events, it just doesn't respond to them.  
 6         return (((viewFlags & CLICKABLE) == CLICKABLE ||  
 7                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  
 8     }  
 9     if (mTouchDelegate != null) {  
10         if (mTouchDelegate.onTouchEvent(event)) {  
11             return true;  
12         }  
13     }  
14     if (((viewFlags & CLICKABLE) == CLICKABLE ||  
15             (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
16         switch (event.getAction()) {  
17             case MotionEvent.ACTION_UP:  
18                 boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
19                 if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
20                     // take focus if we don't have it already and we should in  
21                     // touch mode.  
22                     boolean focusTaken = false;  
23                     if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
24                         focusTaken = requestFocus();  
25                     }  
26                     if (!mHasPerformedLongPress) {  
27                         // This is a tap, so remove the longpress check  
28                         removeLongPressCallback();  
29                         // Only perform take click actions if we were in the pressed state  
30                         if (!focusTaken) {  
31                             // Use a Runnable and post this rather than calling  
32                             // performClick directly. This lets other visual state  
33                             // of the view update before click actions start.  
34                             if (mPerformClick == null) {  
35                                 mPerformClick = new PerformClick();  
36                             }  
37                             if (!post(mPerformClick)) {  
38                                 performClick();  
39                             }  
40                         }  
41                     }  
42                     if (mUnsetPressedState == null) {  
43                         mUnsetPressedState = new UnsetPressedState();  
44                     }  
45                     if (prepressed) {  
46                         mPrivateFlags |= PRESSED;  
47                         refreshDrawableState();  
48                         postDelayed(mUnsetPressedState,  
49                                 ViewConfiguration.getPressedStateDuration());  
50                     } else if (!post(mUnsetPressedState)) {  
51                         // If the post failed, unpress right now  
52                         mUnsetPressedState.run();  
53                     }  
54                     removeTapCallback();  
55                 }  
56                 break;  
57             case MotionEvent.ACTION_DOWN:  
58                 if (mPendingCheckForTap == null) {  
59                     mPendingCheckForTap = new CheckForTap();  
60                 }  
61                 mPrivateFlags |= PREPRESSED;  
62                 mHasPerformedLongPress = false;  
63                 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
64                 break;  
65             case MotionEvent.ACTION_CANCEL:  
66                 mPrivateFlags &= ~PRESSED;  
67                 refreshDrawableState();  
68                 removeTapCallback();  
69                 break;  
70             case MotionEvent.ACTION_MOVE:  
71                 final int x = (int) event.getX();  
72                 final int y = (int) event.getY();  
73                 // Be lenient about moving outside of buttons  
74                 int slop = mTouchSlop;  
75                 if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
76                         (y < 0 - slop) || (y >= getHeight() + slop)) {  
77                     // Outside button  
78                     removeTapCallback();  
79                     if ((mPrivateFlags & PRESSED) != 0) {  
80                         // Remove any future long press/tap checks  
81                         removeLongPressCallback();  
82                         // Need to switch from pressed to not pressed  
83                         mPrivateFlags &= ~PRESSED;  
84                         refreshDrawableState();  
85                     }  
86                 }  
87                 break;  
88         }  
89         return true;  
90     }  
91     return false;  
92 }
View Code

  雖然源碼有點多,但是我們只重點關註關鍵代碼,在38行我們看到了代碼:performClick();這個方法從名字表義來看就是OnClick方法的調用,我們進入到該方法中去看一探究竟,是否執行了OnClick方法呢?

public boolean performClick() {  
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
    if (mOnClickListener != null) {  
        playSoundEffect(SoundEffectConstants.CLICK);  
        mOnClickListener.onClick(this);  
        return true;  
    }  
    return false;  
} 

  從上述代碼可以看到,只要mOnClickListener不是null,就會去調用它的onClick方法,那mOnClickListener又是在哪裡賦值的呢?經過分析後找到如下方法:

public void setOnClickListener(OnClickListener l) {  
    if (!isClickable()) {  
        setClickable(true);  
    }  
    mOnClickListener = l;  
} 

  而上述這個方法就是我們在Application層經常使用的方法,即我們給button 設置點擊事件的時候就會調用該方法了,分析到這了,我們知道了OnClick方法確實是在OnTouchEvent方法中,那麼除了要設置 OnClickListener,調用onClick的條件又是什麼呢?我們從38行代碼往前推,從第14行可以分析出:

    只要該控制項是可點擊的或者是長按類型的,則會進入到MotionEvent.ACTION_UP這個分支當中 ,然後經過各種條件判斷,則會進入到38行的performClick()方法中。

     至此,一切都清晰明白了!當我們通過調用setOnClickListener方法來給控制項註冊一個點擊事件時,就會給mOnClickListener賦值。然後每當控制項被點擊時或者長按時,都會在performClick()方法里回調被點擊控制項的onClick方法。

  經驗之談:

  關於OnTouchEvent(MotionEvent事件)事件的層級傳遞。我們都知道如果給一個控制項註冊了touch事件,每次點擊它的時候都會觸發一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。這裡需要註意,如果你在執行ACTION_DOWN的時候返回了false,後面一系列其它的action就不會再得到執行了。簡單的說,就是當dispatchTouchEvent在進行事件分發的時候,只有前一個action返回true,才會觸發後一個action。

  

   那我們可以換一個控制項,將按鈕替換成ImageView,然後給它也註冊一個touch事件,並返回false。如下所示:

  

  


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.Android 有自帶的jar包可以生成二維碼core-3.0.0.jar,其中的com.google.zxing包 2.寫一個二維碼生成的工具類,網上搜的話應該一大堆。 1 package com.example.administrator.twocodedemo; 2 3 import an ...
  • 作者:Antonio Leiva 時間:Mar 21, 2017 原文鏈接:https://antonioleiva.com/operator-overload-kotlin/ 就像其他每種語言一樣,在Kotlin中,已經預定義了一些操作符執行一定的操作。 最典型的是加(+),減(-),乘(*),除 ...
  • UINavigationController 返回手勢與 leftBarButtonItem UINavigationController 自帶從屏幕左側邊緣向右滑動的返回手勢,可以通過這個手勢實現 pop,或者 pop 中途取消 pop 而停留在當前控制器(UIViewController)。如果 ...
  • 前言 因為 "實戰項目系列" 涉及到數據持久化,這邊就來補充一下。 如本文有錯或理解偏差歡迎聯繫我,會儘快改正更新! 如有什麼問題,也可直接通過郵箱 [email protected] 聯繫我。 demo鏈接: https://pan.baidu.com/s/1hsspiio 密碼: dk3h 數據持 ...
  • UICollectionView 適配 iPhone 7 Plus 需求:在屏幕上水平放置 5 張正方形圖片,每張圖片的寬度相等,無縫隙排列鋪滿一個屏幕寬度。 看似很簡單的需求。用 UICollectionView 實現的話,把 UICollectionView 的寬度設置為屏幕寬度;屏幕寬度除以 ...
  • 譯者註:由於本人水平有限,譯文中難免會出現概念模糊、晦澀難懂,如果實在沒心思看下去,請發揮你的學習能動性,到原文中自行翻譯,感謝!!!點 "這裡" ,直達英文各種長句的世界。 好了,既然你選擇繼續往下看,那就一起來學習吧!!! 譯者:Noddy 地址: "直流電路理論" 如果你喜歡本譯文,請到項目上 ...
  • 1.應用程式 Android會同一系列核心應用程式包一起發佈,該應用程式包包括email客戶端,SMS短消息程式,日曆,地圖,瀏覽器,聯繫人管理程式等。所有的應用程式都是使用JAVA語言編寫的。 2.應用程式框架 開發人員也可以完全訪問核心應用程式所使用的API框架。該應用程式的架構設計簡化了組件的 ...
  • 有這樣一個ListView,要求在屏幕底部有一個篩選排序的浮動框: 1、手指下拉隱藏,上滑顯示 ; 2、如果沒做任何操作,2S之後,要自動顯示; 3、滑動到最底部,始終顯示。 首先看其效果圖: 實現上述效果,其實現原理如下: 1、在屏幕頂部固定一個BottomView,XML佈局最好使用Relati ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...