zoukankan      html  css  js  c++  java
  • 自定义控件(视图)2期笔记14:自定义视图之View事件分发 dispatchTouchEvent,onTouch,onTouchEvent,onClick逻辑顺序过程

    1. 这里我们先从案例角度说明dispatchTouchEvent,onTouch,onTouchEvent,onClick逻辑顺序过程

    (1)首先我们重写一个MyButton 继承自 Button,代码如下:

     1 package com.himi.eventdemo;
     2 
     3 import android.content.Context;
     4 import android.util.AttributeSet;
     5 import android.util.Log;
     6 import android.view.MotionEvent;
     7 import android.widget.Button;
     8 
     9 public class MyButton extends Button {
    10     
    11     private static String TAG = "MyButton";
    12     public MyButton(Context context) {
    13         super(context);
    14     }
    15 
    16     public MyButton(Context context, AttributeSet attrs) {
    17         super(context, attrs);
    18     }
    19 
    20     public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {
    21         super(context, attrs, defStyleAttr);
    22     }
    23 
    24 
    25     @Override
    26     public boolean dispatchTouchEvent(MotionEvent event) {
    27 
    28         switch (event.getAction()) {
    29             case MotionEvent.ACTION_DOWN:
    30                 Log.e(TAG,"dispatchTouchEvent====MyButton=====ACTION_DOWN");
    31                 break;
    32             case MotionEvent.ACTION_MOVE:
    33                  Log.e(TAG,"dispatchTouchEvent====MyButton=====ACTION_MOVE");
    34                 break;
    35             case MotionEvent.ACTION_UP:
    36                  Log.e(TAG,"dispatchTouchEvent====MyButton=====ACTION_UP");
    37                 break;
    38         }
    39 
    40         return super.dispatchTouchEvent(event);
    41     }
    42 
    43     @Override
    44     public boolean onTouchEvent(MotionEvent event) {
    45 
    46         switch (event.getAction()) {
    47             case MotionEvent.ACTION_DOWN:
    48                  Log.e(TAG,"onTouchEvent====MyButton=====ACTION_DOWN");
    49                 break;
    50             case MotionEvent.ACTION_MOVE:
    51                  Log.e(TAG,"onTouchEvent====MyButton=====ACTION_MOVE");
    52                 break;
    53             case MotionEvent.ACTION_UP:
    54                  Log.e(TAG,"onTouchEvent====MyButton=====ACTION_UP");
    55                 break;
    56         }
    57 
    58         return super.onTouchEvent(event);
    59     }
    60 }

    (2)来到主布局文件activity_main.xml,如下:

     1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     2     xmlns:tools="http://schemas.android.com/tools"
     3     android:layout_width="match_parent"
     4     android:layout_height="match_parent"
     5     android:gravity="center"
     6     tools:context="com.himi.eventdemo.MainActivity" >
     7     
     8     <com.himi.eventdemo.MyButton
     9         android:id="@+id/myButton"
    10         android:layout_width="wrap_content"
    11         android:layout_height="wrap_content"
    12         android:text="测试"
    13         />
    14         
    15 </RelativeLayout>

    (3)测试MainActivity,如下:

     1 package com.himi.eventdemo;
     2 
     3 import android.app.Activity;
     4 import android.os.Bundle;
     5 import android.util.Log;
     6 import android.view.MotionEvent;
     7 import android.view.View;
     8 import android.widget.Button;
     9 
    10 public class MainActivity extends Activity {
    11     
    12     private static String TAG ="MainActivity";
    13 
    14 
    15     private Button myButton;
    16 
    17 
    18     @Override
    19     protected void onCreate(Bundle savedInstanceState) {
    20         super.onCreate(savedInstanceState);
    21         setContentView(R.layout.activity_main);
    22 
    23         myButton = (Button) findViewById(R.id.myButton);
    24 
    25         myButton.setOnTouchListener(new View.OnTouchListener() {
    26             @Override
    27             public boolean onTouch(View v, MotionEvent event) {
    28                 switch (event.getAction()) {
    29                     case MotionEvent.ACTION_DOWN:
    30                          Log.e(TAG,"onTouch====MyButton=====ACTION_DOWN");
    31                         break;
    32                     case MotionEvent.ACTION_MOVE:
    33                          Log.e(TAG,"onTouch====MyButton=====ACTION_MOVE");
    34                         break;
    35                     case MotionEvent.ACTION_UP:
    36                          Log.e(TAG,"onTouch====MyButton=====ACTION_UP");
    37                         break;
    38                 }
    39                 return false;
    40             }
    41         });
    42 
    43         myButton.setOnClickListener(new View.OnClickListener() {
    44             @Override
    45             public void onClick(View v) {
    46                 Log.e(TAG,"onClick====MyButton=====onClick");
    47             }
    48         });
    49 
    50 
    51     }
    52 
    53 
    54 }

    (4)部署程序到手机上,如下:

    点击测试按钮,打印结果如下:

    从上面打印的结果分析:

    点击Button按钮事件分发过程如下:

    dispatchTouchEvent --> onTouch --> onTouchEvent --> onClick

    相信细心的你肯定发现了,都是在ACTION_UP事件之后才触发onClick点击事件

    2. 下面我们从源码的角度分析dispatchTouchEvent,onTouch,onTouchEvent,onClick逻辑顺序过程

    (1)事件分发都是从dispatchTouchEvent方法开始的,那么我们这里是重写了dispatchTouchEvent方法,并且最后也调用了父类的super.dispatchTouchEvent(event)方法。那么我们看看父类中的方法到底做了什么??点击进入父类的dispatchTouchEvent方法,发现此方法在View类中找到,其实也不奇怪,所有控件的父类都是View。这里我贴出最新源码如下:

     1 public boolean dispatchTouchEvent(MotionEvent event) {
     2         boolean result = false;
     3 
     4         if (mInputEventConsistencyVerifier != null) {
     5             mInputEventConsistencyVerifier.onTouchEvent(event, 0);
     6         }
     7 
     8         final int actionMasked = event.getActionMasked();
     9         if (actionMasked == MotionEvent.ACTION_DOWN) {
    10             // Defensive cleanup for new gesture
    11             stopNestedScroll();
    12         }
    13 
    14         if (onFilterTouchEventForSecurity(event)) {
    15             //noinspection SimplifiableIfStatement
    16             ListenerInfo li = mListenerInfo;
    17             if (li != null && li.mOnTouchListener != null
    18                     && (mViewFlags & ENABLED_MASK) == ENABLED
    19                     && li.mOnTouchListener.onTouch(this, event)) {
    20                 result = true;
    21             }
    22 
    23             if (!result && onTouchEvent(event)) {
    24                 result = true;
    25             }
    26         }
    27 
    28         if (!result && mInputEventConsistencyVerifier != null) {
    29             mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    30         }
    31 
    32         // Clean up after nested scrolls if this is the end of a gesture;
    33         // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
    34         // of the gesture.
    35         if (actionMasked == MotionEvent.ACTION_UP ||
    36                 actionMasked == MotionEvent.ACTION_CANCEL ||
    37                 (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
    38             stopNestedScroll();
    39         }
    40 
    41         return result;
    42     }

    忽略其他无关代码,我们直接看17--25行。

    第17行的 if 判断关键在于li.mOnTouchListener.onTouch(this, event) 的返回值

    这个接口回调就是我们外面写的myButton.setOnTouchListener事件(Button 的onTouch事件),

    在MainActivity代码里,我们setOnTouchListener返回的值是false,所以在源码中我们可以看到 17行的条件不成立,那么条件不成立,result=false;

    因此,源码的第23行 if 判断第一个条件成立,继续执行第二个条件,也就是onTouchEvent。我们跳到这个方法里看看里面干啥了?看如下代码:

     1 public boolean onTouchEvent(MotionEvent event) {
     2 
     3         if (((viewFlags & CLICKABLE) == CLICKABLE ||
     4                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
     5             switch (event.getAction()) {
     6                 case MotionEvent.ACTION_UP:
     7                     boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
     8                     if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
     9                         // take focus if we don't have it already and we should in
    10                         // touch mode.
    11                         boolean focusTaken = false;
    12                         if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
    13                             focusTaken = requestFocus();
    14                         }
    15 
    16                         if (prepressed) {
    17                             // The button is being released before we actually
    18                             // showed it as pressed.  Make it show the pressed
    19                             // state now (before scheduling the click) to ensure
    20                             // the user sees it.
    21                             setPressed(true, x, y);
    22                        }
    23 
    24                         if (!mHasPerformedLongPress) {
    25                             // This is a tap, so remove the longpress check
    26                             removeLongPressCallback();
    27 
    28                             // Only perform take click actions if we were in the pressed state
    29                             if (!focusTaken) {
    30                                 // Use a Runnable and post this rather than calling
    31                                 // performClick directly. This lets other visual state
    32                                 // of the view update before click actions start.
    33                                 if (mPerformClick == null) {
    34                                     mPerformClick = new PerformClick();
    35                                 }
    36                                 if (!post(mPerformClick)) {
    37                      performClick();
    38                                 }
    39                             }
    40                         }
    41 
    42                         if (mUnsetPressedState == null) {
    43                             mUnsetPressedState = new UnsetPressedState();
    44                         }
    45 
    46                         if (prepressed) {
    47                             postDelayed(mUnsetPressedState,
    48                                     ViewConfiguration.getPressedStateDuration());
    49                         } else if (!post(mUnsetPressedState)) {
    50                             // If the post failed, unpress right now
    51                             mUnsetPressedState.run();
    52                         }
    53 
    54                         removeTapCallback();
    55                     }
    56                     break;
    57             return true;
    58         }
    59 
    60         return false;
    61     }

    我们看看这里边都做了些什么,忽略其他,我们直接看37行的 performClick(); 方法,跳进去继续看:

    (注意:这里的performClick方法是在ACTION_UP手势里边执行的哦!!!

     1 public boolean performClick() {
     2         final boolean result;
     3         final ListenerInfo li = mListenerInfo;
     4         if (li != null && li.mOnClickListener != null) {
     5             playSoundEffect(SoundEffectConstants.CLICK);
     6             li.mOnClickListener.onClick(this);
     7             result = true;
     8         } else {
     9             result = false;
    10         }
    11 
    12         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    13         return result;
    14     }

    看见没??

    第6行 li.mOnClickListener.onClick(this);

    这个接口回调就是我们Button的 onClick事件。到此为止,我们从源码分析了Button事件分发过程。


    结论:

    dispatchTouchEvent---->onTouch---->onTouchEvent----->onClick。并且如果仔细的你会发现,在onTouchEvent方法内部判断执行onClick方法,但是,在所有ACTION_UP事件之后才触发onClick点击事件

    3. 现在我们来看看其他情况:当onTouch返回为true,打印结果如下:

     1 package com.himi.eventdemo;
     2 
     3 import android.app.Activity;
     4 import android.os.Bundle;
     5 import android.util.Log;
     6 import android.view.MotionEvent;
     7 import android.view.View;
     8 import android.widget.Button;
     9 
    10 public class MainActivity extends Activity {
    11     
    12     private static String TAG ="MainActivity";
    13 
    14 
    15     private Button myButton;
    16 
    17 
    18     @Override
    19     protected void onCreate(Bundle savedInstanceState) {
    20         super.onCreate(savedInstanceState);
    21         setContentView(R.layout.activity_main);
    22 
    23         myButton = (Button) findViewById(R.id.myButton);
    24 
    25         myButton.setOnTouchListener(new View.OnTouchListener() {
    26             @Override
    27             public boolean onTouch(View v, MotionEvent event) {
    28                 switch (event.getAction()) {
    29                     case MotionEvent.ACTION_DOWN:
    30                          Log.e(TAG,"onTouch====MyButton=====ACTION_DOWN");
    31                         break;
    32                     case MotionEvent.ACTION_MOVE:
    33                          Log.e(TAG,"onTouch====MyButton=====ACTION_MOVE");
    34                         break;
    35                     case MotionEvent.ACTION_UP:
    36                          Log.e(TAG,"onTouch====MyButton=====ACTION_UP");
    37                         break;
    38                 }
    39                 return true;
    40             }
    41         });
    42 
    43         myButton.setOnClickListener(new View.OnClickListener() {
    44             @Override
    45             public void onClick(View v) {
    46                 Log.e(TAG,"onClick====MyButton=====onClick");
    47             }
    48         });
    49 
    50 
    51     }
    52 
    53 
    54 }

    打印结果如下:

     结论:

    dispatchTouchEvent---->onTouch

    惊奇的发现,竟然没有执行onClick事件是吧????如果你仔细阅读上面的文章,估计你知道为什么了吧?还是跟大家一起分析一下吧:源码如下:

     1 public boolean dispatchTouchEvent(MotionEvent event) {
     2         boolean result = false;
     3 
     4         if (mInputEventConsistencyVerifier != null) {
     5             mInputEventConsistencyVerifier.onTouchEvent(event, 0);
     6         }
     7 
     8         final int actionMasked = event.getActionMasked();
     9         if (actionMasked == MotionEvent.ACTION_DOWN) {
    10             // Defensive cleanup for new gesture
    11             stopNestedScroll();
    12         }
    13 
    14         if (onFilterTouchEventForSecurity(event)) {
    15             //noinspection SimplifiableIfStatement
    16             ListenerInfo li = mListenerInfo;
    17             if (li != null && li.mOnTouchListener != null
    18                     && (mViewFlags & ENABLED_MASK) == ENABLED
    19                     && li.mOnTouchListener.onTouch(this, event)) {
    20                 result = true;
    21             }
    22 
    23             if (!result && onTouchEvent(event)) {
    24                 result = true;
    25             }
    26         }
    27 
    28         if (!result && mInputEventConsistencyVerifier != null) {
    29             mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    30         }
    31 
    32         // Clean up after nested scrolls if this is the end of a gesture;
    33         // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
    34         // of the gesture.
    35         if (actionMasked == MotionEvent.ACTION_UP ||
    36                 actionMasked == MotionEvent.ACTION_CANCEL ||
    37                 (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
    38             stopNestedScroll();
    39         }
    40 
    41         return result;
    42     }

    从第 17 行可以看出,条件成立,result=true;

    那么第 23 行 if 条件根本不会执行第二个判断,那么就不会执行onTouchEvent方法,也就不会调用 onClick的接口,因此Button 不会执行setOnClickListener中的onClick事件。

    4. 总结:

     

    那么我们继续回归dispatchTouchEvent中不是ViewGroup的情形
    接下来,系统会自动判断我们是否实现了onTouchListener 这里就开始有分支了
    --1>. 当我们实现了onTouchListener,那么下一步我们的事件叫交给了onTouchListener.onTouch来处理,这里就又开始了分支
    (1)如果我们在onTouch中返回了true,那么就表明我们的onTouchListener 已经消化掉了本次的事件,本次事件完结。这就是为什么我们在onTouch中返回去就永运不会执行onClick,onLongClick了
    (2)如果我们在onTouch中返回了false,那么很明显了我们的事件就会被onTouchEvent处理
    --2>. 同理,当我们没有实现了onTouchListener,很明显了我们的事件就会被onTouchEvent处理。
    殊途同归,最终如果我们的事件没有被干掉,最终都交给了onTouchEvent。那么接下来我们继续来看onTouchEvent,那么我们的onTouchEvent又是用来干什么的呢(这里既然已经有onTouchListener了,他们似乎一模一样啊)?
    其实不然,说白了我们的onTouchEvent最终会用来分发onClickonLongClick事件
     
  • 相关阅读:
    eclipse 关闭web项目无用校验
    Java7的那些新特性
    Linux内核源码情景分析-wait()、schedule()
    android canvas 画图笔记
    android启动第一个界面时即闪屏的核心代码(两种方式)
    leetCode(24):Binary Search Tree Iterator
    12:打印 1 到最大的 n 位数
    Android仿QQ ios dialog,仿QQ退出向上菜单
    iOS UI16_数据持久化
    Android自己定义百度地图缩放图标
  • 原文地址:https://www.cnblogs.com/hebao0514/p/5718840.html
Copyright © 2011-2022 走看看