以ontouch为例说明android事件发送机制

android里面和touch相关的方法最常见的有四个:onTouch,dispatchTouchEvent,onTouchEvent,如果是一个GroupView的话还有一个onInterceptTouchEvent。

这四个方法有什么关系?很多新手搞不明白,我在网上找了很多的资料,发现自己的研究结果与资料上的有出入。所以将自己的结论写出来,Android的事件传递机制到底是怎么样的,也可以由此一探究竟。

我这例子实在网上的例子改造的,原来文章的链接:http://www.blogjava.net/lzqdiy/archive/2011/05/08/349794.html

先贴出我的代码,在做仔细的分析

<?xml version="1.0" encoding="utf-8"?>

<view android:layout_width="fill_parent"
      android:layout_height="fill_parent"
      class="com.example.AndroidTouchTest.MyLinearLayout"
      xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/view">
    <com.example.AndroidTouchTest.MyTextView
            android:layout_width="200px"
            android:layout_height="200px"
            android:id="@+id/tv"
            android:text="lzqdiy"
            android:textSize="40sp"
            android:textStyle="bold"
            android:background="#FFFFFF"
            android:textColor="#0000FF"/>
</view>
package com.example.AndroidTouchTest;

import android.app.Activity;
import android.os.Bundle;

public class MyActivity extends Activity {
    /**
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}
View Code
package com.example.AndroidTouchTest;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;

/**
 * Created with IntelliJ IDEA.
 * User: dothegod
 * Date: 3/24/13
 * Time: 10:01 PM
 * To change this template use File | Settings | File Templates.
 */
public class MyLinearLayout extends LinearLayout {
    private final String TAG = "MyLinearLayout";

    public MyLinearLayout(Context context, AttributeSet attrs) {

        super(context, attrs);

        Log.d(TAG, TAG);

        setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                int action = event.getAction();

                switch (action) {

                    case MotionEvent.ACTION_DOWN:

                        Log.d(TAG, "onTouch action:ACTION_DOWN");

                        break;

                    case MotionEvent.ACTION_MOVE:

                        Log.d(TAG, "onTouch action:ACTION_MOVE");

                        break;

                    case MotionEvent.ACTION_UP:

                        Log.d(TAG, "onTouch action:ACTION_UP");

                        break;

                    case MotionEvent.ACTION_CANCEL:

                        Log.d(TAG, "onTouch action:ACTION_CANCEL");

                        break;

                }
                boolean flag = false;
                Log.d(TAG, "onTouch action: FORCE SET flag " + (flag == true? "true":"false"));
                return flag;
            }
        });

    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int action = ev.getAction();

        switch (action) {

            case MotionEvent.ACTION_DOWN:

                Log.d(TAG, "dispatchTouchEvent action:ACTION_DOWN");

                break;

            case MotionEvent.ACTION_MOVE:

                Log.d(TAG, "dispatchTouchEvent action:ACTION_MOVE");

                break;

            case MotionEvent.ACTION_UP:

                Log.d(TAG, "dispatchTouchEvent action:ACTION_UP");

                break;

            case MotionEvent.ACTION_CANCEL:

                Log.d(TAG, "dispatchTouchEvent action:ACTION_CANCEL");

                break;

        }
        boolean flag = super.dispatchTouchEvent(ev);
        Log.d(TAG, "dispatchTouchEvent action: flag " + (flag == true? "true":"false"));
//        flag = true;
//        Log.d(TAG, "dispatchTouchEvent action: FORCE SET flag " + (flag == true? "true":"false"));
        return flag;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        int action = ev.getAction();

        switch (action) {

            case MotionEvent.ACTION_DOWN:

                Log.d(TAG, "onInterceptTouchEvent action:ACTION_DOWN");

                break;

            case MotionEvent.ACTION_MOVE:

                Log.d(TAG, "onInterceptTouchEvent action:ACTION_MOVE");

                break;

            case MotionEvent.ACTION_UP:

                Log.d(TAG, "onInterceptTouchEvent action:ACTION_UP");

                break;

            case MotionEvent.ACTION_CANCEL:

                Log.d(TAG, "onInterceptTouchEvent action:ACTION_CANCEL");

                break;

        }

        boolean flag = super.onInterceptTouchEvent(ev);
        Log.d(TAG, "onInterceptTouchEvent action: flag " + (flag == true? "true":"false"));
//        flag = true;
//        Log.d(TAG, "onInterceptTouchEvent action: FORCE SET flag " + (flag == true? "true":"false"));
        return flag;

    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        int action = ev.getAction();

        switch (action) {

            case MotionEvent.ACTION_DOWN:

                Log.d(TAG, "---onTouchEvent action:ACTION_DOWN");

                break;

            case MotionEvent.ACTION_MOVE:

                Log.d(TAG, "---onTouchEvent action:ACTION_MOVE");

                break;

            case MotionEvent.ACTION_UP:

                Log.d(TAG, "---onTouchEvent action:ACTION_UP");

                break;

            case MotionEvent.ACTION_CANCEL:

                Log.d(TAG, "---onTouchEvent action:ACTION_CANCEL");

                break;

        }

        boolean flag = super.onTouchEvent(ev);
        Log.d(TAG, "---onTouchEvent action: flag " + (flag == true? "true":"false"));
//        flag = true;
//        Log.d(TAG, "---onTouchEvent action: FORCE SET flag " + (flag == true? "true":"false"));
        return flag;
    }

}
View Code
package com.example.AndroidTouchTest;

/**
 * Created with IntelliJ IDEA.
 * User: dothegod
 * Date: 3/24/13
 * Time: 10:02 PM
 * To change this template use File | Settings | File Templates.
 */
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;

public class MyTextView extends TextView {

    private final String TAG = "MyTextView";

    public MyTextView(Context context, AttributeSet attrs) {

        super(context, attrs);
        setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                int action = event.getAction();

                switch (action) {

                    case MotionEvent.ACTION_DOWN:

                        Log.d(TAG, "onTouch action:ACTION_DOWN");

                        break;

                    case MotionEvent.ACTION_MOVE:

                        Log.d(TAG, "onTouch action:ACTION_MOVE");

                        break;

                    case MotionEvent.ACTION_UP:

                        Log.d(TAG, "onTouch action:ACTION_UP");

                        break;

                    case MotionEvent.ACTION_CANCEL:

                        Log.d(TAG, "onTouch action:ACTION_CANCEL");

                        break;

                }
                boolean flag = false;
                Log.d(TAG, "onTouch action: FORCE SET flag " + (flag == true? "true":"false"));
                return flag;
            }
        });

    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int action = ev.getAction();

        switch (action) {

            case MotionEvent.ACTION_DOWN:

                Log.d(TAG, "dispatchTouchEvent action:ACTION_DOWN");

                break;

            case MotionEvent.ACTION_MOVE:

                Log.d(TAG, "dispatchTouchEvent action:ACTION_MOVE");

                break;

            case MotionEvent.ACTION_UP:

                Log.d(TAG, "dispatchTouchEvent action:ACTION_UP");

                break;

            case MotionEvent.ACTION_CANCEL:

                Log.d(TAG, "dispatchTouchEvent action:ACTION_CANCEL");

                break;

        }
        boolean flag = super.dispatchTouchEvent(ev);
        Log.d(TAG, "dispatchTouchEvent action: flag " + (flag == true? "true":"false"));
//        flag = true;
//        Log.d(TAG, "onTouchEvent action: FORCE SET flag " + (flag == true? "true":"false"));
        return flag;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        int action = ev.getAction();

        switch (action) {

            case MotionEvent.ACTION_DOWN:

                Log.d(TAG, "---onTouchEvent action:ACTION_DOWN");

                break;

            case MotionEvent.ACTION_MOVE:

                Log.d(TAG, "---onTouchEvent action:ACTION_MOVE");

                break;

            case MotionEvent.ACTION_UP:

                Log.d(TAG, "---onTouchEvent action:ACTION_UP");

                break;

            case MotionEvent.ACTION_CANCEL:

                Log.d(TAG, "---onTouchEvent action:ACTION_CANCEL");

                break;

        }

        boolean flag = super.onTouchEvent(ev);
        Log.d(TAG, "---onTouchEvent action: flag " + (flag == true? "true":"false"));
//        flag = true;
//        Log.d(TAG, "---onTouchEvent action: FORCE SET flag " + (flag == true? "true":"false"));
        return flag;

    }

}
View Code
package com.example.AndroidTouchTest;

import android.app.Activity;
import android.os.Bundle;

public class MyActivity extends Activity {
    /**
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

 

点击textiew的时候,会看到下面的打印:

03-23 17:22:23.545: DEBUG/MyLinearLayout(2459): dispatchTouchEvent action:ACTION_DOWN
03-23 17:22:23.545: DEBUG/MyLinearLayout(2459): onInterceptTouchEvent action:ACTION_DOWN
03-23 17:22:23.545: DEBUG/MyLinearLayout(2459): onInterceptTouchEvent action: flag false
03-23 17:22:23.545: DEBUG/MyTextView(2459): dispatchTouchEvent action:ACTION_DOWN
03-23 17:22:23.545: DEBUG/MyTextView(2459): onTouch action:ACTION_DOWN
03-23 17:22:23.545: DEBUG/MyTextView(2459): onTouch action: FORCE SET flag false
03-23 17:22:23.545: DEBUG/MyTextView(2459): ---onTouchEvent action:ACTION_DOWN
03-23 17:22:23.545: DEBUG/MyTextView(2459): ---onTouchEvent action: flag false
03-23 17:22:23.545: DEBUG/MyTextView(2459): onTouchEvent action: flag false
03-23 17:22:23.545: DEBUG/MyLinearLayout(2459): onTouch action:ACTION_DOWN
03-23 17:22:23.545: DEBUG/MyLinearLayout(2459): onTouch action: FORCE SET flag false
03-23 17:22:23.545: DEBUG/MyLinearLayout(2459): ---onTouchEvent action:ACTION_DOWN
03-23 17:22:23.554: DEBUG/MyLinearLayout(2459): ---onTouchEvent action: flag false
03-23 17:22:23.554: DEBUG/MyLinearLayout(2459): dispatchTouchEvent action: flag false

调用顺序整理如下:颜色表示控件,缩进表示调用关系。

MyLinearLayout(2459): dispatchTouchEvent

  MyLinearLayout(2459): onInterceptTouchEvent

  MyTextView(2459): dispatchTouchEvent action

    MyTextView(2459): onTouch

    MyTextView(2459): ---onTouchEvent

  MyLinearLayout(2459): onTouch

  MyLinearLayout(2459): ---onTouchEvent

MyLinearLayout(2459): dispatchTouchEven

 

这个是什么意思呢?

首先我们要分析下这几个函数的作用和返回值的意义

dispatchTouchEvent是用来分发事件的,控件的事件的分发都是通过这个函数来完成的。

onInterceptTouchEvent是判断是否截取事件,为什么要截取事件呢?因为控件重要有可能包含其他的控件,比如说,本例子代码中linearlayout中包含了一个textiew。他的作用就是决定这个事件是否需要传递給子控件。不过注意仅仅是GroupView的控件才有哦,有些控件,像textview就没法办法包含子控件的,所以它就没有这个方法。

ontouch是事件监听器的方法,只要有触摸的操作就是有这个方法。

onTouchEvent是触摸事件发生以后产生的处理。

他们都有一个boolean的返回值,false表示该事件需要继续传播,true表示该事件不用继续传播了。

每点击一次屏幕会有三个事件发生:ACTION_DOWN, ACTION_MOVE, ACTION_UP。 这里可以看到如果dispatchTouchEvent返回值是false的话,则不会再有后续的事件,后续的事件被丢弃掉了。

LinearLayout的调用函数图

 

LinearLayout的 dispatchTouchEvent代码

View Code
  1     public boolean dispatchTouchEvent(MotionEvent ev) {
  2         if (mInputEventConsistencyVerifier != null) {
  3             mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
  4         }
  5 
  6         boolean handled = false;
  7         if (onFilterTouchEventForSecurity(ev)) {
  8             final int action = ev.getAction();
  9             final int actionMasked = action & MotionEvent.ACTION_MASK;
 10 
 11             // Handle an initial down.
 12             if (actionMasked == MotionEvent.ACTION_DOWN) {
 13                 // Throw away all previous state when starting a new touch gesture.
 14                 // The framework may have dropped the up or cancel event for the previous gesture
 15                 // due to an app switch, ANR, or some other state change.
 16                 cancelAndClearTouchTargets(ev);
 17                 resetTouchState();
 18             }
 19 
 20             // Check for interception.
 21             final boolean intercepted;
 22             if (actionMasked == MotionEvent.ACTION_DOWN
 23                     || mFirstTouchTarget != null) {
 24                 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
 25                 if (!disallowIntercept) {
 26                     intercepted = onInterceptTouchEvent(ev);
 27                     ev.setAction(action); // restore action in case it was changed
 28                 } else {
 29                     intercepted = false;
 30                 }
 31             } else {
 32                 // There are no touch targets and this action is not an initial down
 33                 // so this view group continues to intercept touches.
 34                 intercepted = true;
 35             }
 36 
 37             // Check for cancelation.
 38             final boolean canceled = resetCancelNextUpFlag(this)
 39                     || actionMasked == MotionEvent.ACTION_CANCEL;
 40 
 41             // Update list of touch targets for pointer down, if needed.
 42             final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
 43             TouchTarget newTouchTarget = null;
 44             boolean alreadyDispatchedToNewTouchTarget = false;
 45             if (!canceled && !intercepted) {
 46                 if (actionMasked == MotionEvent.ACTION_DOWN
 47                         || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
 48                         || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
 49                     final int actionIndex = ev.getActionIndex(); // always 0 for down
 50                     final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
 51                             : TouchTarget.ALL_POINTER_IDS;
 52 
 53                     // Clean up earlier touch targets for this pointer id in case they
 54                     // have become out of sync.
 55                     removePointersFromTouchTargets(idBitsToAssign);
 56 
 57                     final int childrenCount = mChildrenCount;
 58                     if (childrenCount != 0) {
 59                         // Find a child that can receive the event.
 60                         // Scan children from front to back.
 61                         final View[] children = mChildren;
 62                         final float x = ev.getX(actionIndex);
 63                         final float y = ev.getY(actionIndex);
 64 
 65                         final boolean customOrder = isChildrenDrawingOrderEnabled();
 66                         for (int i = childrenCount - 1; i >= 0; i--) {
 67                             final int childIndex = customOrder ?
 68                                     getChildDrawingOrder(childrenCount, i) : i;
 69                             final View child = children[childIndex];
 70                             if (!canViewReceivePointerEvents(child)
 71                                     || !isTransformedTouchPointInView(x, y, child, null)) {
 72                                 continue;
 73                             }
 74 
 75                             newTouchTarget = getTouchTarget(child);
 76                             if (newTouchTarget != null) {
 77                                 // Child is already receiving touch within its bounds.
 78                                 // Give it the new pointer in addition to the ones it is handling.
 79                                 newTouchTarget.pointerIdBits |= idBitsToAssign;
 80                                 break;
 81                             }
 82 
 83                             resetCancelNextUpFlag(child);
 84                             if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
 85                                 // Child wants to receive touch within its bounds.
 86                                 mLastTouchDownTime = ev.getDownTime();
 87                                 mLastTouchDownIndex = childIndex;
 88                                 mLastTouchDownX = ev.getX();
 89                                 mLastTouchDownY = ev.getY();
 90                                 newTouchTarget = addTouchTarget(child, idBitsToAssign);
 91                                 alreadyDispatchedToNewTouchTarget = true;
 92                                 break;
 93                             }
 94                         }
 95                     }
 96 
 97                     if (newTouchTarget == null && mFirstTouchTarget != null) {
 98                         // Did not find a child to receive the event.
 99                         // Assign the pointer to the least recently added target.
100                         newTouchTarget = mFirstTouchTarget;
101                         while (newTouchTarget.next != null) {
102                             newTouchTarget = newTouchTarget.next;
103                         }
104                         newTouchTarget.pointerIdBits |= idBitsToAssign;
105                     }
106                 }
107             }
108 
109             // Dispatch to touch targets.
110             if (mFirstTouchTarget == null) {
111                 // No touch targets so treat this as an ordinary view.
112                 handled = dispatchTransformedTouchEvent(ev, canceled, null,
113                         TouchTarget.ALL_POINTER_IDS);
114             } else {
115                 // Dispatch to touch targets, excluding the new touch target if we already
116                 // dispatched to it.  Cancel touch targets if necessary.
117                 TouchTarget predecessor = null;
118                 TouchTarget target = mFirstTouchTarget;
119                 while (target != null) {
120                     final TouchTarget next = target.next;
121                     if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
122                         handled = true;
123                     } else {
124                         final boolean cancelChild = resetCancelNextUpFlag(target.child)
125                         || intercepted;
126                         if (dispatchTransformedTouchEvent(ev, cancelChild,
127                                 target.child, target.pointerIdBits)) {
128                             handled = true;
129                         }
130                         if (cancelChild) {
131                             if (predecessor == null) {
132                                 mFirstTouchTarget = next;
133                             } else {
134                                 predecessor.next = next;
135                             }
136                             target.recycle();
137                             target = next;
138                             continue;
139                         }
140                     }
141                     predecessor = target;
142                     target = next;
143                 }
144             }
145 
146             // Update list of touch targets for pointer up or cancel, if needed.
147             if (canceled
148                     || actionMasked == MotionEvent.ACTION_UP
149                     || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
150                 resetTouchState();
151             } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
152                 final int actionIndex = ev.getActionIndex();
153                 final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
154                 removePointersFromTouchTargets(idBitsToRemove);
155             }
156         }
157 
158         if (!handled && mInputEventConsistencyVerifier != null) {
159             mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
160         }
161         return handled;
162     }

Textiew的调用流程

 1     public boolean dispatchTouchEvent(MotionEvent event) {
 2         if (mInputEventConsistencyVerifier != null) {
 3             mInputEventConsistencyVerifier.onTouchEvent(event, 0);
 4         }
 5 
 6         if (onFilterTouchEventForSecurity(event)) {
 7             //noinspection SimplifiableIfStatement
 8             ListenerInfo li = mListenerInfo;
 9             if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
10                     && li.mOnTouchListener.onTouch(this, event)) {
11                 return true;
12             }
13 
14             if (onTouchEvent(event)) {
15                 return true;
16             }
17         }
18 
19         if (mInputEventConsistencyVerifier != null) {
20             mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
21         }
22         return false;
23     }

可以看到TextView的dispatchTouchEvent先调用的Ontouch(9,10行)如果调用结果返回true则返回,如果是false则继续调用Ontouchevent。

正式基于这两种传输机制,事件得以在控件中不断传输。

那么事件是怎么通过父控件传入到子控件呢?

 1                 while (target != null) {
 2                     final TouchTarget next = target.next;
 3                     if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
 4                         handled = true;
 5                     } else {
 6                         final boolean cancelChild = resetCancelNextUpFlag(target.child)
 7                         || intercepted;
 8                         if (dispatchTransformedTouchEvent(ev, cancelChild,
 9                                 target.child, target.pointerIdBits)) {
10                             handled = true;

这是在LinearLayout中的代码段,可以看到调用了dispatchTransformedTouchEvent方法,代码比较长,只给出一部分。

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
…………

可以看到会寻找子控件,并且调用子控件的dispatchTouchEvent,如果没有就调用父类(不是父控件)的dispatchTouchEvent。而ViewGroup的父类就是View。

机制就是这样,不同的返回值会有不同的调用顺序,但是原理都是一样的。可以修改没有方法的返回值来查看代码的调用情况。

源代码地址:https://github.com/Dothegod/AndroidTouchTest/

posted @ 2013-03-26 21:35  HighFUN  阅读(1331)  评论(0编辑  收藏  举报