• 以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/

  • 相关阅读:
    android 单位详解
    ViewFlipper的使用
    today is history,today is tomorrow
    Android2.1 和之后的版本 中的 drawable(hdpi,ldpi,mdpi) 的区别
    auto_ptr
    android编写Service入门
    Android程序完全退出的三种方法
    Android中Toast的用法简介
    安装android开发环境
    error C2850: 'PCH header file'
  • 原文地址:https://www.cnblogs.com/HighFun/p/2979901.html
Copyright © 2020-2023  润新知