zoukankan      html  css  js  c++  java
  • Android学习之事件分发机制

    博文出处:http://blog.csdn.net/sinyu890807/article/details/9097463

    当前有一个非常简单的项目,只有一个Activity,并且Activity中只有一个按钮。你可能已经知道,如果想要给这个按钮注册一个点击事件,只需要调用:

        button.setOnClickListener(new OnClickListener() {  
            @Override  
            public void onClick(View v) {  
                Log.d("TAG", "onClick execute");  
            }  
        });  

    这样在onClick方法里面写实现,就可以在按钮被点击的时候执行。你可能也已经知道,如果想给这个按钮再添加一个touch事件,只需要调用:

        button.setOnTouchListener(new OnTouchListener() {  
            @Override  
            public boolean onTouch(View v, MotionEvent event) {  
                Log.d("TAG", "onTouch execute, action " + event.getAction()
    );
    return false; } });
    onTouch方法里能做的事情比onClick要多一些,比如判断手指按下、抬起、移动等事件。那么如果我两个事件都注册了,哪一个会先执行呢?我们来试一下就知道了,运行程序点击按钮,打印结果如下:
    可以看到,onTouch是优先于onClick执行的,并且onTouch执行了两次,一次是ACTION_DOWN,一次是ACTION_UP(还可能 会有多次ACTION_MOVE的执行,如果你手抖了一下)。因此事件传递的顺序是先经过onTouch,再传递到onClick。 onTouch方法是有返回值的,这里我们返回的是false,如果我们尝试把onTouch方法里的返回值改成true,onclick方法就不再执行了。我们可以将这种情况看做触摸事件被onTouch方法拦截了,没有继续向onclick传递。 那么view的点击事件是怎么传递的呢?示意图:              
    dispatchTouchEvent的源码如下:
        public boolean dispatchTouchEvent(MotionEvent event) {  
            if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && 
                    mOnTouchListener.onTouch(this, event)) {  
                return true;  
            }  
            return onTouchEvent(event);  
        }  
     我们可以 看到,在这个方法内,首先是进行了一个判断,如果mOnTouchListener != null,(mViewFlags & ENABLED_MASK) == ENABLED和mOnTouchListener.onTouch(this, event)这三个条件都为真,就返回true,否则就去执行onTouchEvent(event)方法并返回。先看一下第一个条件,mOnTouchListener这个变量是在哪里赋值的呢?我们寻找之后在View里发现了如下方法:
     
        public void setOnTouchListener(OnTouchListener l) {  
            mOnTouchListener = l;  
        }  
     
    Bingo!找到了,mOnTouchListener正是在setOnTouchListener方法里赋值的,也就是说只要我们给控件注册了touch事件,mOnTouchListener就一定被赋值了。第二个条件(mViewFlags & ENABLED_MASK) == ENABLED是判断当前点击的控件是否是enable的,按钮默认都是enable的,因此这个条件恒定为true。第三个条件就比较关键了,mOnTouchListener.onTouch(this, event),其实也就是去回调控件注册touch事件时的onTouch方法。也就是说如果我们在onTouch方法里返回true,就会让这三个条件全部成立,从而整个方法直接返回true。如果我们在onTouch方法里返回false,就会再去执行onTouchEvent(event)方法。
    那么来分析一下: 首先在dispatchTouchEvent中最先执行的就是onTouch方法,因此onTouch肯定是要优先于onClick执行的, 也是印证了刚刚的打印结果。而如果在onTouch方法里返回了true,就会让dispatchTouchEvent方法直接返回true,不会再继续 往下执行。而打印结果也证实了如果onTouch返回true,onClick就不会再执行了。
    同时我们可以发现:onClick的调用肯定是在onTouchEvent(event)方法中的;onTouchEvent的源码
     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     }  

    相较于刚才的dispatchTouchEvent方法,onTouchEvent方法复杂了很多,不过没关系,我们只挑重点看就可以了。首先在第14行我们可以看出,如果该控件是可以点击的就会进入到第16行的switch判断中去,而如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。在经过种种判断之后,会执行到第38行的performClick()方法,那我们进入到这个方法里瞧一瞧: 
    1     public boolean performClick() {  
    2         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
    3         if (mOnClickListener != null) {  
    4             playSoundEffect(SoundEffectConstants.CLICK);  
    5             mOnClickListener.onClick(this);  
    6             return true;  
    7         }  
    8         return false;  
    9     }  
    可以看到,只要mOnClickListener不是null,就会去调用它的onClick方法,那mOnClickListener又是在哪里赋值的呢?经过寻找后找到如下方法:
        public void setOnClickListener(OnClickListener l) {  
            if (!isClickable()) {  
                setClickable(true);  
            }  
            mOnClickListener = l;  
        }  
    
      
     
  • 相关阅读:
    简练软考知识点整理-项目采购管理简介
    简练软考知识点整理-项目风险管理简介
    SQL(replace)替换字段中指定的字符
    sh 脚本名字和./脚本名字有什么区别
    linux下tar命令解压到指定的目录
    查看文件MD5值
    Topo check failed. Mapred tasks exceed 1000000000
    cron表达式详解
    Linux下#!/usr/bin/env bash和#!/usr/bin/bash、#!/bin/bash的比较
    linux中seq命令用法
  • 原文地址:https://www.cnblogs.com/mafangfang/p/5435313.html
Copyright © 2011-2022 走看看