• Android View 事件分发机制 源码解析 (上)


    一直想写事件分发机制的文章,不管咋样,也得自己研究下事件分发的源码,写出心得~

    首先我们先写个简单的例子来测试View的事件转发的流程~

    1、案例

    为了更好的研究View的事件转发,我们自定以一个MyButton继承Button,然后把跟事件传播有关的方法进行复写,然后添加上日志~

    MyButton

    1. package com.example.zhy_event03;  
    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 final String TAG = MyButton.class.getSimpleName();  
    12.   
    13.     public MyButton(Context context, AttributeSet attrs)  
    14.     {  
    15.         super(context, attrs);  
    16.     }  
    17.   
    18.     @Override  
    19.     public boolean onTouchEvent(MotionEvent event)  
    20.     {  
    21.         int action = event.getAction();  
    22.   
    23.         switch (action)  
    24.         {  
    25.         case MotionEvent.ACTION_DOWN:  
    26.             Log.e(TAG, "onTouchEvent ACTION_DOWN");  
    27.             break;  
    28.         case MotionEvent.ACTION_MOVE:  
    29.             Log.e(TAG, "onTouchEvent ACTION_MOVE");  
    30.             break;  
    31.         case MotionEvent.ACTION_UP:  
    32.             Log.e(TAG, "onTouchEvent ACTION_UP");  
    33.             break;  
    34.         default:  
    35.             break;  
    36.         }  
    37.         return super.onTouchEvent(event);  
    38.     }  
    39.       
    40.     @Override  
    41.     public boolean dispatchTouchEvent(MotionEvent event)  
    42.     {  
    43.         int action = event.getAction();  
    44.   
    45.         switch (action)  
    46.         {  
    47.         case MotionEvent.ACTION_DOWN:  
    48.             Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");  
    49.             break;  
    50.         case MotionEvent.ACTION_MOVE:  
    51.             Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");  
    52.             break;  
    53.         case MotionEvent.ACTION_UP:  
    54.             Log.e(TAG, "dispatchTouchEvent ACTION_UP");  
    55.             break;  
    56.   
    57.         default:  
    58.             break;  
    59.         }  
    60.         return super.dispatchTouchEvent(event);  
    61.     }  
    62.   
    63.       
    64. }  


    在onTouchEvent和dispatchTouchEvent中打印了日志~

    然后把我们自定义的按钮加到主布局文件中;

    布局文件:

    1. <LinearLayout 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.     tools:context=".MainActivity" >  
    6.   
    7.     <com.example.zhy_event03.MyButton  
    8.         android:id="@+id/id_btn"  
    9.         android:layout_width="wrap_content"  
    10.         android:layout_height="wrap_content"  
    11.         android:text="click me" />  
    12.   
    13. </LinearLayout>  


    最后看一眼MainActivity的代码

    1. package com.example.zhy_event03;  
    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.view.View.OnTouchListener;  
    9. import android.widget.Button;  
    10.   
    11. public class MainActivity extends Activity  
    12. {  
    13.     protected static final String TAG = "MyButton";  
    14.     private Button mButton ;  
    15.     @Override  
    16.     protected void onCreate(Bundle savedInstanceState)  
    17.     {  
    18.         super.onCreate(savedInstanceState);  
    19.         setContentView(R.layout.activity_main);  
    20.           
    21.         mButton = (Button) findViewById(R.id.id_btn);  
    22.         mButton.setOnTouchListener(new OnTouchListener()  
    23.         {  
    24.             @Override  
    25.             public boolean onTouch(View v, MotionEvent event)  
    26.             {  
    27.                 int action = event.getAction();  
    28.   
    29.                 switch (action)  
    30.                 {  
    31.                 case MotionEvent.ACTION_DOWN:  
    32.                     Log.e(TAG, "onTouch ACTION_DOWN");  
    33.                     break;  
    34.                 case MotionEvent.ACTION_MOVE:  
    35.                     Log.e(TAG, "onTouch ACTION_MOVE");  
    36.                     break;  
    37.                 case MotionEvent.ACTION_UP:  
    38.                     Log.e(TAG, "onTouch ACTION_UP");  
    39.                     break;  
    40.                 default:  
    41.                     break;  
    42.                 }  
    43.                   
    44.                 return false;  
    45.             }  
    46.         });  
    47.     }  
    48.   
    49.       
    50. }  


    在MainActivity中,我们还给MyButton设置了OnTouchListener这个监听~

    好了,跟View事件相关一般就这三个地方了,一个onTouchEvent,一个dispatchTouchEvent,一个setOnTouchListener;

    下面我们运行,然后点击按钮,查看日志输出:

    1. 08-31 06:09:39.030: E/MyButton(879): dispatchTouchEvent ACTION_DOWN  
    2. 08-31 06:09:39.030: E/MyButton(879): onTouch ACTION_DOWN  
    3. 08-31 06:09:39.049: E/MyButton(879): onTouchEvent ACTION_DOWN  
    4. 08-31 06:09:39.138: E/MyButton(879): dispatchTouchEvent ACTION_MOVE  
    5. 08-31 06:09:39.138: E/MyButton(879): onTouch ACTION_MOVE  
    6. 08-31 06:09:39.147: E/MyButton(879): onTouchEvent ACTION_MOVE  
    7. 08-31 06:09:39.232: E/MyButton(879): dispatchTouchEvent ACTION_UP  
    8. 08-31 06:09:39.248: E/MyButton(879): onTouch ACTION_UP  
    9. 08-31 06:09:39.248: E/MyButton(879): onTouchEvent ACTION_UP  


    我有意点击的时候蹭了一下,不然不会触发MOVE,手抖可能会打印一堆MOVE的日志~~~

    好了,可以看到,不管是DOWN,MOVE,UP都会按照下面的顺序执行:

    1、dispatchTouchEvent

    2、 setOnTouchListener的onTouch

    3、onTouchEvent

    下面就跟随日志的脚步开始源码的探索~

    2、dispatchTouchEvent

    首先进入View的dispatchTouchEvent

    1. /** 
    2.      * Pass the touch screen motion event down to the target view, or this 
    3.      * view if it is the target. 
    4.      * 
    5.      * @param event The motion event to be dispatched. 
    6.      * @return True if the event was handled by the view, false otherwise. 
    7.      */  
    8.     public boolean dispatchTouchEvent(MotionEvent event) {  
    9.         if (!onFilterTouchEventForSecurity(event)) {  
    10.             return false;  
    11.         }  
    12.   
    13.         if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
    14.                 mOnTouchListener.onTouch(this, event)) {  
    15.             return true;  
    16.         }  
    17.         return onTouchEvent(event);  
    18.     }  


    直接看13行:首先判断mOnTouchListener不为null,并且view是enable的状态,然后 mOnTouchListener.onTouch(this, event)返回true,这三个条件如果都满足,直接return true ; 也就是下面的onTouchEvent(event)不会被执行了;

    那么mOnTouchListener是和方神圣,我们来看看:

    1. /** 
    2.   * Register a callback to be invoked when a touch event is sent to this view. 
    3.   * @param l the touch listener to attach to this view 
    4.   */  
    5.  public void setOnTouchListener(OnTouchListener l) {  
    6.      mOnTouchListener = l;  
    7.  }  

    其实就是我们在Activity中设置的setOnTouchListener。

    也就是说:如果我们设置了setOnTouchListener,并且return true,那么View自己的onTouchEvent就不会被执行了,当然了,本例我们return false,我们还得往下探索 ;

    已经解决一个常见的问题:View的onTouchListener和onTouchEvent的调用关系,相信大家应该已经明白了~let's go;继续往下。

     3、View的onTouchEvent:

    接下来是View的onTouchEvent:

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

    代码还是比较长的,

    10-15行,如果当前View是Disabled状态且是可点击则会消费掉事件(return true);可以忽略,不是我们的重点;

    17-21行,如果设置了mTouchDelegate,则会将事件交给代理者处理,直接return true,如果大家希望自己的View增加它的touch范围,可以尝试使用TouchDelegate,这里也不是重点,可以忽略;

    接下来到我们的重点了:

    23行的判断:如果我们的View可以点击或者可以长按,则,注意IF的范围,最终一定return true ;

     if (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
               //...
                return true;
            }

    接下来就是   switch (event.getAction())了,判断事件类型,DOWN,MOVE,UP等;

    我们按照例子执行的顺序,先看  case MotionEvent.ACTION_DOWN (71-78行):

    1、MotionEvent.ACTION_DOWN

    75行:给mPrivateFlags设置一个PREPRESSED的标识

    76行:设置mHasPerformedLongPress=false;表示长按事件还未触发;

    77行:发送一个延迟为ViewConfiguration.getTapTimeout()的延迟消息,到达延时时间后会执行CheckForTap()里面的run方法:

    1、ViewConfiguration.getTapTimeout()为115毫秒;

    2、CheckForTap

    1. private final class CheckForTap implements Runnable {  
    2.       public void run() {  
    3.           mPrivateFlags &= ~PREPRESSED;  
    4.           mPrivateFlags |= PRESSED;  
    5.           refreshDrawableState();  
    6.           if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {  
    7.               postCheckForLongClick(ViewConfiguration.getTapTimeout());  
    8.           }  
    9.       }  
    10.   }  

    在run方法里面取消mPrivateFlags的PREPRESSED,然后设置PRESSED标识,刷新背景,如果View支持长按事件,则再发一个延时消息,检测长按;

    1. private void postCheckForLongClick(int delayOffset) {  
    2.        mHasPerformedLongPress = false;  
    3.   
    4.        if (mPendingCheckForLongPress == null) {  
    5.            mPendingCheckForLongPress = new CheckForLongPress();  
    6.        }  
    7.        mPendingCheckForLongPress.rememberWindowAttachCount();  
    8.        postDelayed(mPendingCheckForLongPress,  
    9.                ViewConfiguration.getLongPressTimeout() - delayOffset);  
    10.    }  
    1. class CheckForLongPress implements Runnable {  
    2.   
    3.         private int mOriginalWindowAttachCount;  
    4.   
    5.         public void run() {  
    6.             if (isPressed() && (mParent != null)  
    7.                     && mOriginalWindowAttachCount == mWindowAttachCount) {  
    8.                 if (performLongClick()) {  
    9.                     mHasPerformedLongPress = true;  
    10.                 }  
    11.             }  
    12.         }  


    可以看到,当用户按下,首先会设置标识为PREPRESSED

    如果115后,没有抬起,会将View的标识设置为PRESSED且去掉PREPRESSED标识,然后发出一个检测长按的延迟任务,延时为:ViewConfiguration.getLongPressTimeout() - delayOffset(500ms -115ms),这个115ms刚好时检测额PREPRESSED时间;也就是用户从DOWN触发开始算起,如果500ms内没有抬起则认为触发了长按事件:

    1、如果此时设置了长按的回调,则执行长按时的回调,且如果长按的回调返回true;才把mHasPerformedLongPress置为ture;

    2、否则,如果没有设置长按回调或者长按回调返回的是false;则mHasPerformedLongPress依然是false;

    好了DOWN就分析完成了;大家回个神,下面回到VIEW的onTouchEvent中的ACTION_MOVE:

    2、MotionEvent.ACTION_MOVE

    86到105行:

    87-88行:拿到当前触摸的x,y坐标;

    91行判断当然触摸点有没有移出我们的View,如果移出了:

    1、执行removeTapCallback(); 

    2、然后判断是否包含PRESSED标识,如果包含,移除长按的检查:removeLongPressCallback();

    3、最后把mPrivateFlags中PRESSED标识去除,刷新背景;

    1. private void removeTapCallback() {  
    2.        if (mPendingCheckForTap != null) {  
    3.            mPrivateFlags &= ~PREPRESSED;  
    4.            removeCallbacks(mPendingCheckForTap);  
    5.        }  
    6.    }  

    这个是移除,DOWN触发时设置的PREPRESSED的检测;即当前触发时机在DOWN触发不到115ms时,你就已经移出控件外了;

    如果115ms后,你才移出控件外,则你的当前mPrivateFlags一定为PRESSED且发送了长按的检测;

    就会走上面的2和3;首先移除removeLongPressCallback()
     private void removeLongPressCallback() {
            if (mPendingCheckForLongPress != null) {
              removeCallbacks(mPendingCheckForLongPress);
            }
        }

    然后mPrivateFlags中PRESSED标识去除,刷新背景;

    好了,MOVE我们也分析完成了,总结一下:只要用户移出了我们的控件:则将mPrivateFlags取出PRESSED标识,且移除所有在DOWN中设置的检测,长按等;

    下面再回个神,回到View的onTouchEvent的ACTION_UP:

    3、MotionEvent.ACTION_UP

    26到69行:

    27行:判断mPrivateFlags是否包含PREPRESSED

    28行:如果包含PRESSED或者PREPRESSED则进入执行体,也就是无论是115ms内或者之后抬起都会进入执行体。

    36行:如果mHasPerformedLongPress没有被执行,进入IF

    38行:removeLongPressCallback();移除长按的检测

    45-50行:如果mPerformClick如果mPerformClick为null,初始化一个实例,然后立即通过handler添加到消息队列尾部,如果添加失败则直接执行 performClick();添加成功,在mPerformClick的run方法中就是执行performClick();

    终于执行了我们的click事件了,下面看一下performClick()方法:

    1. public boolean performClick() {  
    2.        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
    3.   
    4.        if (mOnClickListener != null) {  
    5.            playSoundEffect(SoundEffectConstants.CLICK);  
    6.            mOnClickListener.onClick(this);  
    7.            return true;  
    8.        }  
    9.   
    10.        return false;  
    11.    }  


    if (mOnClickListener != null) {    
                mOnClickListener.onClick(this);
                return true;
            }

    久违了~我们的mOnClickListener ;

    别激动,还没结束,回到ACTION_UP,

    58行:如果prepressed为true,进入IF体:

    为mPrivateFlags设置表示为PRESSED,刷新背景,125毫秒后执行mUnsetPressedState

    否则:mUnsetPressedState.run();立即执行;也就是不管咋样,最后mUnsetPressedState.run()都会执行;

    看看这个UnsetPressedState主要干什么:

      private final class UnsetPressedState implements Runnable {
            public void run() {
                setPressed(false);
            }
        }

     public void setPressed(boolean pressed) {
            if (pressed) {
                mPrivateFlags |= PRESSED;
            } else {
                mPrivateFlags &= ~PRESSED;
            }
            refreshDrawableState();
            dispatchSetPressed(pressed);
        }

    把我们的mPrivateFlags中的PRESSED取消,然后刷新背景,把setPress转发下去。

    ACTION_UP的最后一行:removeTapCallback(),如果mPendingCheckForTap不为null,移除;

    4、总结

    好了,代码跨度还是相当大的,下面需要总结下:

    1、整个View的事件转发流程是:

    View.dispatchEvent->View.setOnTouchListener->View.onTouchEvent

    在dispatchTouchEvent中会进行OnTouchListener的判断,如果OnTouchListener不为null且返回true,则表示事件被消费,onTouchEvent不会被执行;否则执行onTouchEvent。

    2、onTouchEvent中的DOWN,MOVE,UP

    DOWN时:

    a、首先设置标志为PREPRESSED,设置mHasPerformedLongPress=false ;然后发出一个115ms后的mPendingCheckForTap;

    b、如果115ms内没有触发UP,则将标志置为PRESSED,清除PREPRESSED标志,同时发出一个延时为500-115ms的,检测长按任务消息;

    c、如果500ms内(从DOWN触发开始算),则会触发LongClickListener:

    此时如果LongClickListener不为null,则会执行回调,同时如果LongClickListener.onClick返回true,才把mHasPerformedLongPress设置为true;否则mHasPerformedLongPress依然为false;

    MOVE时:

    主要就是检测用户是否划出控件,如果划出了:

    115ms内,直接移除mPendingCheckForTap;

    115ms后,则将标志中的PRESSED去除,同时移除长按的检查:removeLongPressCallback();

    UP时:

    a、如果115ms内,触发UP,此时标志为PREPRESSED,则执行UnsetPressedState,setPressed(false);会把setPress转发下去,可以在View中复写dispatchSetPressed方法接收;

    b、如果是115ms-500ms间,即长按还未发生,则首先移除长按检测,执行onClick回调;

    c、如果是500ms以后,那么有两种情况:

    i.设置了onLongClickListener,且onLongClickListener.onClick返回true,则点击事件OnClick事件无法触发;

    ii.没有设置onLongClickListener或者onLongClickListener.onClick返回false,则点击事件OnClick事件依然可以触发;

    d、最后执行mUnsetPressedState.run(),将setPressed传递下去,然后将PRESSED标识去除;

    最后问个问题,然后再运行个例子结束:

    1、setOnLongClickListener和setOnClickListener是否只能执行一个

    不是的,只要setOnLongClickListener中的onClick返回false,则两个都会执行;返回true则会屏幕setOnClickListener

    最后我们给MyButton同时设置setOnClickListener和setOnLongClickListener,运行看看:

    1. package com.example.zhy_event03;  
    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.view.View.OnClickListener;  
    9. import android.view.View.OnLongClickListener;  
    10. import android.view.View.OnTouchListener;  
    11. import android.widget.Button;  
    12. import android.widget.Toast;  
    13.   
    14. public class MainActivity extends Activity  
    15. {  
    16.     protected static final String TAG = "MyButton";  
    17.     private Button mButton ;  
    18.     @Override  
    19.     protected void onCreate(Bundle savedInstanceState)  
    20.     {  
    21.         super.onCreate(savedInstanceState);  
    22.         setContentView(R.layout.activity_main);  
    23.           
    24.         mButton = (Button) findViewById(R.id.id_btn);  
    25.         mButton.setOnTouchListener(new OnTouchListener()  
    26.         {  
    27.             @Override  
    28.             public boolean onTouch(View v, MotionEvent event)  
    29.             {  
    30.                 int action = event.getAction();  
    31.   
    32.                 switch (action)  
    33.                 {  
    34.                 case MotionEvent.ACTION_DOWN:  
    35.                     Log.e(TAG, "onTouch ACTION_DOWN");  
    36.                     break;  
    37.                 case MotionEvent.ACTION_MOVE:  
    38.                     Log.e(TAG, "onTouch ACTION_MOVE");  
    39.                     break;  
    40.                 case MotionEvent.ACTION_UP:  
    41.                     Log.e(TAG, "onTouch ACTION_UP");  
    42.                     break;  
    43.                 default:  
    44.                     break;  
    45.                 }  
    46.                   
    47.                 return false;  
    48.             }  
    49.         });  
    50.         mButton.setOnClickListener(new OnClickListener()  
    51.         {  
    52.             @Override  
    53.             public void onClick(View v)  
    54.             {  
    55.                 Toast.makeText(getApplicationContext(), "onclick",Toast.LENGTH_SHORT).show();  
    56.             }  
    57.         });  
    58.           
    59.         mButton.setOnLongClickListener(new OnLongClickListener()  
    60.         {  
    61.             @Override  
    62.             public boolean onLongClick(View v)  
    63.             {  
    64.                 Toast.makeText(getApplicationContext(), "setOnLongClickListener",Toast.LENGTH_SHORT).show();  
    65.                 return false;  
    66.             }  
    67.         });  
    68.     }  
    69.   
    70.       
    71. }  

    效果图:

    可以看到LongClickListener已经ClickListener都触发了~

    最后,本篇博文完成了对View的事件分发机制的整个流程的说明,并且对源码进行了分析;

    当然了,View结束,肯定到我们的ViewGroup了,请点击:Android ViewGroup事件分发机制

  • 相关阅读:
    Python学习笔记(6)-异常
    Python学习笔记(1)-基本概念
    自动化测试工具Sikuli的安装以及应用
    SoapUI添加断言
    SoapUI接口之间的数据传递
    Fitnesse初体验
    SoapUI5.1.2命令行执行https类型接口集成
    SoapUI命令行执行测试用例
    Jenkins集成AirTest不启动浏览器
    AirTest执行时不能输入汉字
  • 原文地址:https://www.cnblogs.com/ldq2016/p/6877978.html
Copyright © 2020-2023  润新知