Android笔记:触摸事件的分析与总结----TouchEvent处理机制

   其余相关博文:java

   Android笔记:触摸事件的分析与总结----MotionEvent对象android

   Android笔记:触摸事件的分析与总结----TouchEvent处理机制app

   Android笔记:触摸事件的分析与总结----多点触控ide

    


    Android中的事件类型分为按键事件和屏幕触摸事件。TouchEvent是屏幕触摸事件的基础事件,要深刻了解屏幕触摸事件的处理机制,就必须掌握TouchEvent在整个触摸事件中的转移和处理过程。此处将对TouchEvent处理机制的学习作个小小的总结和备记。源码分析

    当屏幕中包含一个ViewGroup,而这个ViewGroup又包含一个子view,这个时候android系统如何处理Touch事件呢?究竟是 ViewGroup来处理Touch事件,仍是子view来处理Touch事件呢?布局

    这问题涉及到与每一个View或者ViewGroup的子类都具备的三个和TouchEvent处理密切相关的方法:学习

1)dispatchTouchEvent(MotionEvent ev)     这个方法用来分发TouchEvent测试

2)onInterceptTouchEvent(MotionEvent ev)  这个方法用来拦截TouchEventui

3)onTouchEvent(MotionEvent ev)           这个方法用来处理TouchEventthis


    其中view类和Activity中都有dispatchTouchEvent()和onTouchEvent()两个方法。ViewGroup继承自View,并且还新添了一个onInterceptTouchEvent()方法。

    这三个方法的返回值都是boolean值,对于返回结果,若是return true,那么表示该方法消费了这次事件,若是return false,那么表示该方法并未处理彻底,该事件仍然须要以某种方式传递下去继续等待处理。


1、dispatchTouchEvent

    dispatchTouchEvent(MotionEventev) 这个方法用来分发TouchEvent,默认返回false。

    先看下Activity中的注释和方法:

    /**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     * 
     * @param ev The touch screen event.
     * 
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev)
    {
        if (ev.getAction() == MotionEvent.ACTION_DOWN)
        {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev))
        {
            return true;
        }
        return onTouchEvent(ev);
    }


    注释说明:它会被调用处理触摸屏事件,能够重写覆盖此方法来拦截全部触摸屏事件在这些事件分发到窗口以前。一般应该处理触摸屏事件,必定要调用这个实现。当返回值为true时,表示这个事件已经被消费了。

    源码简要说明下,onUserInteraction()是个空方法可忽略其影响。getWindow()返回当前Activity的顶层窗口Window对象。Window类的惟一子类是PhoneWindow,查看PhoneWindow的superDispatchTouchEvent()方法,里面又直接调用DecorView类的superDispatchTouchEvent()方法。DecorView是PhoneWindow的一个final的内部类而且继承FrameLayout的,也是Window界面的最顶层的View对象。DecorView类的superDispatchTouchEvent()方法又是调用了父类FrameLayout的dispatchTouchEvent()方法。而FrameLayout中并无dispatchTouchEvent()方法,因此最后调用的仍是ViewGroup的dispatchTouchEvent()方法。最后再经过ViewGroup的dispatchTouchEvent()方法将TouchEvent分发到其子View上。

     ViewGroup和View中的dispatchTouchEvent()代码较多,具体的源码分析可参见:【转】Android笔记:触摸事件的分析与总结----Touch事件分发方法dispatchTouchEvent()源码分析

     其余参考资料:http://blog.csdn.net/xiaanming/article/details/21696315。  


2、onInterceptTouchEvent

    onInterceptTouchEvent()是ViewGroup的一个方法,目的是在系统向该ViewGroup及其各个childView触发onTouchEvent()以前对相关事件进行一次拦截。

    ViewGroup中onInterceptTouchEvent()方法及注释:

    /**
     * Implement this method to intercept all touch screen motion events. This
     * allows you to watch events as they are dispatched to your children, and
     * take ownership of the current gesture at any point.
     * 
     * <p>
     * Using this function takes some care, as it has a fairly complicated
     * interaction with {@link View#onTouchEvent(MotionEvent)
     * View.onTouchEvent(MotionEvent)}, and using it requires implementing that
     * method as well as this one in the correct way. Events will be received in
     * the following order:
     * 
     * <ol>
     * <li>You will receive the down event here.
     * <li>The down event will be handled either by a child of this view group,
     * or given to your own onTouchEvent() method to handle; this means you
     * should implement onTouchEvent() to return true, so you will continue to
     * see the rest of the gesture (instead of looking for a parent view to
     * handle it). Also, by returning true from onTouchEvent(), you will not
     * receive any following events in onInterceptTouchEvent() and all touch
     * processing must happen in onTouchEvent() like normal.
     * <li>For as long as you return false from this function, each following
     * event (up to and including the final up) will be delivered first here and
     * then to the target's onTouchEvent().
     * <li>If you return true from here, you will not receive any following
     * events: the target view will receive the same event but with the action
     * {@link MotionEvent#ACTION_CANCEL}, and all further events will be
     * delivered to your onTouchEvent() method and no longer appear here.
     * </ol>
     * 
     * @param ev
     *            The motion event being dispatched down the hierarchy.
     * @return Return true to steal motion events from the children and have
     *         them dispatched to this ViewGroup through onTouchEvent(). The
     *         current target will receive an ACTION_CANCEL event, and no
     *         further messages will be delivered here.
     */
    public boolean onInterceptTouchEvent(MotionEvent ev)
    {
        return false;
    }


    onInterceptTouchEvent()默认返回了false,注释的大意为重写该方法能够实现对触屏事件的拦截,使用该方法须要特别注意的是,该方法与View类的onTouchEvent(MotionEvent)或者View.onTouchEvent(MotionEvent)方法具备复杂的关联机制。结合onTouchEvent(),总结下onInterceptTouchEvent()大体的规则为:


1. down事件首先会传递到onInterceptTouchEvent()方法。

2. 若是该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成以后return false,那么后续的move, up等事件将继续会先传递给该ViewGroup,以后才和down事件同样传递给最终的目标View的onTouchEvent()处理。

3. 若是该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成以后return true,那么后续的move, up等事件将再也不传递给onInterceptTouchEvent(),而是和down事件同样传递给该ViewGroup的onTouchEvent()处理,注意,目标view将接收不到任何事件。

4. 若是最终须要处理事件的view的onTouchEvent()返回了false,那么该事件将被传递至其上一层次的view的onTouchEvent()处理。

5. 若是最终须要处理事件的view 的onTouchEvent()返回了true,那么后续事件将能够继续传递给该view的onTouchEvent()处理。

    


3、onTouchEvent

    onTouchEvent()的处理机制详见此文:

    Android笔记:触摸事件的分析与总结----MotionEvent对象



4、TouchEvent处理范例

    此处建立一个包含自定义LinearLayout(ViewGroup类)和自定义TextView(View类)的Activity来分析触摸屏幕时TouchEvent的处理机制。

    效果图以下:

wKiom1Qj1aTiY14EAACGBwMjfvc945.jpg


    activity_main.xml代码以下:

<?xml version="1.0" encoding="utf-8"?> 
<com.example.d_touchevent.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:orientation="vertical" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" 
    android:gravity="center" > 
       <com.example.d_touchevent.MyTextView 
            android:layout_width="100dp" 
            android:layout_height="100dp" 
            android:id="@+id/tv" 
            android:text="测试" 
            android:textSize="40sp" 
            android:textStyle="bold" 
            android:background="#F0F00F" 
            android:textColor="#0000FF"/> 
</com.example.d_touchevent.MyLinearLayout>


    MainActivity.java代码以下:

package com.example.d_touchevent;

import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.MotionEvent;

/**
 * 参考资料:http://glblong.blog.51cto.com/3058613/1559320
 * @author zeng
 *
 */
public class MainActivity extends Activity
{
    private String TAG = "Activity ---  ";

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        boolean b = super.onTouchEvent(event);
        
        
        int action = event.getAction();
        
        switch (action)
        {
        
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG , "ACTION_DOWN   --- " + TAG + "onTouchEvent          处理 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_MOVE:
                
                Log.e(TAG, "ACTION_MOVE    --- " + TAG + "onTouchEvent          处理 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_UP:
                
                Log.e(TAG, "ACTION_UP     --- " + TAG + "onTouchEvent          处理 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_CANCEL:
                
                Log.e(TAG, "ACTION_CANCEL --- " + TAG + "onTouchEvent          处理 --- " + b);
                
                break;
        
        }
        
        
        return b;
    }
    
    
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev)
    {
        int action = ev.getAction();
        
        switch (action)
        {
        
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "ACTION_DOWN   --- " + TAG + "dispatchTouchEvent    分发 --- ");
                
                break;
            
            case MotionEvent.ACTION_MOVE:
                
                Log.e(TAG, "ACTION_MOVE   --- " + TAG + "dispatchTouchEvent    分发 --- ");
                
                break;
            
            case MotionEvent.ACTION_UP:
                
                Log.e(TAG, "ACTION_UP     --- " + TAG + "dispatchTouchEvent    分发 --- ");
                
                break;
            
            case MotionEvent.ACTION_CANCEL:
                
                Log.e(TAG, "ACTION_CANCEL --- " + TAG + "dispatchTouchEvent    分发 --- ");
                
                break;
        
        }
        return super.dispatchTouchEvent(ev);
    }
    
}


    MyLinearLayout.java代码以下:

package com.example.d_touchevent;

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

/**
 * 参考资料:http://glblong.blog.51cto.com/3058613/1559320
 * @author zeng
 *
 */
public class MyLinearLayout extends LinearLayout
{
    private final String TAG = "L布局    ---  ";
    
    public MyLinearLayout(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        
        Log.e(TAG, TAG);
    }
    
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev)
    {
        int action = ev.getAction();
        
        switch (action)
        {
        
            case MotionEvent.ACTION_DOWN:
                Log.e("", " ");
                Log.e("", " ");
                Log.e(TAG, "ACTION_DOWN   --- " + TAG + "dispatchTouchEvent    分发 --- ");
                
                break;
            
            case MotionEvent.ACTION_MOVE:
                
                Log.e(TAG, "ACTION_MOVE   --- " + TAG + "dispatchTouchEvent    分发 --- ");
                
                break;
            
            case MotionEvent.ACTION_UP:
                
                Log.e(TAG, "ACTION_UP     --- " + TAG + "dispatchTouchEvent    分发 --- ");
                
                break;
            
            case MotionEvent.ACTION_CANCEL:
                
                Log.e(TAG, "ACTION_CANCEL --- " + TAG + "dispatchTouchEvent    分发 --- ");
                
                break;
        
        }
        return super.dispatchTouchEvent(ev);
    }
    
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev)
    {
        boolean b = false;
        
        int action = ev.getAction();
        switch (action)
        {
        
            case MotionEvent.ACTION_DOWN:
                
                Log.e(TAG, "ACTION_DOWN   --- " + TAG + "onInterceptTouchEvent 拦截 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_MOVE:
                
                Log.e(TAG, "ACTION_MOVE   --- " + TAG + "onInterceptTouchEvent 拦截 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_UP:
                
                Log.e(TAG, "ACTION_UP     --- " + TAG + "onInterceptTouchEvent 拦截 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_CANCEL:
                
                Log.e(TAG, "ACTION_CANCEL --- " + TAG + "onInterceptTouchEvent 拦截 --- " + b);
                
                break;
        
        }
        
        return b;
        
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent ev)
    {
        boolean b = true;
        
        int action = ev.getAction();
        switch (action)
        {
        
            case MotionEvent.ACTION_DOWN:
                
                Log.e(TAG, "ACTION_DOWN   --- " + TAG + "onTouchEvent          处理 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_MOVE:
                
                Log.e(TAG, "ACTION_MOVE   --- " + TAG + "onTouchEvent          处理 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_UP:
                
                Log.e(TAG, "ACTION_UP     --- " + TAG + "onTouchEvent          处理 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_CANCEL:
                
                Log.e(TAG, "ACTION_CANCEL --- " + TAG + "onTouchEvent          处理 --- " + b);
                
                break;
        
        }
        
        return b;
    }
}


    MyTextView.java代码以下:

package com.example.d_touchevent;

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

/**
 * 参考资料:http://glblong.blog.51cto.com/3058613/1559320
 * @author zeng
 *
 */
public class MyTextView extends TextView
{
    private final String TAG = "TextView ---  ";
    
    public MyTextView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }
    
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev)
    {
        int action = ev.getAction();
        
        switch (action)
        {
        
            case MotionEvent.ACTION_DOWN:
                
                Log.e(TAG, "ACTION_DOWN   --- " + TAG + "dispatchTouchEvent    分发 --- ");
                
                break;
            
            case MotionEvent.ACTION_MOVE:
                
                Log.e(TAG, "ACTION_MOVE   --- " + TAG + "dispatchTouchEvent    分发 --- ");
                
                break;
            
            case MotionEvent.ACTION_UP:
                
                Log.e(TAG, "ACTION_UP     --- " + TAG + "dispatchTouchEvent    分发 --- ");
                
                break;
            
            case MotionEvent.ACTION_CANCEL:
                
                Log.e(TAG, "ACTION_CANCEL --- " + TAG + "dispatchTouchEvent    分发 --- ");
                
                break;
        
        }
        return super.dispatchTouchEvent(ev);
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent ev)
    {
        boolean b = false;
        
        int action = ev.getAction();
        switch (action)
        {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "ACTION_DOWN   --- " + TAG + "onTouchEvent          处理 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_MOVE:
                
                Log.e(TAG, "ACTION_MOVE   --- " + TAG + "onTouchEvent          处理 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_UP:
                
                Log.e(TAG, "ACTION_UP     --- " + TAG + "onTouchEvent          处理 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_CANCEL:
                
                Log.e(TAG, "ACTION_CANCEL --- " + TAG + "onTouchEvent          处理 --- " + b);
                
                break;
        
        }
        
        return b;
        
    }
    

}


5、范例运行分析


    注:a.如下Logcat中,若没有特别说明,dispatchTouchEvent()都按默认方法返回false。    

       b.为方便,L布局简写为L,TextView简写为T,Activity简写为A,下同。


    1)点击范例中的【测试】按钮,运行日志以下:

wKioL1Qj1u6AUjd2AALSLhLP6vA025.jpg

    结论:

    当ACTION_DOWN事件产生时,首先触发了Activity的dispatchTouchEvent()方法;接着传递到ViewGroup上,触发L布局的dispatchTouchEvent()方法继续分发TouchEvent;L布局的onInterceptTouchEvent()方法为false,即不会拦截TouchEvent的传递,于是继续传递到ViewGroup里的View对象TextView中,此时仍然先是调用了TextView的dispatchTouchEvent()方法来处理TouchEvent的分发。从上到下依次传递:Activity -> L布局 -> TextView。

    同理,当ACTION_UP事件产生时,首先也是Activity的dispatchTouchEvent()方法,接着再到L布局的dispatchTouchEvent()方法。

     

    2)L.dispatchTouchEvent() = true ,运行日志以下:

wKioL1Qj1y3gNZAUAAGG5oMOlwY678.jpg

   结论:

   此时,每一个触摸事件产生时,都只执行到L布局的dispatchTouchEvent()方法,而不会继续再传递并触发其余方法。


    

    3)A.dispatchTouchEvent() = false  &&  L.dispatchTouchEvent() = false  &&  T.dispatchTouchEvent() = true,运行日志以下:

wKiom1Qj1yqAJ1fMAAKa1HHWNC4703.jpg

    结论:

    由上可见,当TouchEvent由Activity传递到TextView时,执行到dispatchTouchEvent()后便结束了。也就是到TextView时,Android系统认为ACTION_DOWN和ACITON_UP都已经被消费了,而没有继续分发下去。


    4)L.onInterceptTouchEvent = true   &&   L.onTouchEvent = true ,运行日志以下:

wKiom1Qj10Tyr1MpAAObyesw05M744.jpg

    结论:

    这种状况下,L布局处理了全部的TouchEvent。


    5)L.onInterceptTouchEvent = true   &&   L.onTouchEvent = false , 运行日志以下:

wKioL1Qj1_PCsV5oAAO3Omd7D88812.jpg

   结论:

    L布局只处理了ACTION_DOWN事件,而L布局最外层的ctivity处理了TouchEvent。


    6)L.onInterceptTouchEvent=false  &&  L.onTouchEvent=true  &&  T.onTouchEvent=true , 运行日志以下:

wKiom1Qj2DnTyBMeAASIoe2a38s538.jpg

    结论:

    TouchEvent彻底由TextView处理。


    7)L.onInterceptTouchEvent=false  &&  L.onTouchEvent=true  &&  T.onTouchEvent=false , 运行日志以下:

wKioL1Qj2IewoeuaAATViItVS0Y803.jpg

     结论:

     TextView只处理了ACTION_DOWN事件,LinearLayout处理了全部的TouchEvent。



6、分析总结

1.三个主要相关的方法的默认值

全部dispatchTouchEvent方法的默认值都是false。

ViewGroup里的onInterceptTouchEvent默认值是false这样才能把事件传给View里的onTouchEvent.

Activity和ViewGroup里的onTouchEvent默认值都是false。

View里的onTouchEvent返回默认值是true.这样才能执行屡次touch事件。



2.TouchEvent的处理流程

当TouchEvent发生时,首先Activity将TouchEvent传递给最顶层的View, TouchEvent最早到达最顶层 view 的 dispatchTouchEvent ,而后由  dispatchTouchEvent 方法进行分发,若是dispatchTouchEvent返回true ,则表示该触摸事件已经被消费了,若是dispatchTouchEvent返回 false ,则交给这个 view 的 interceptTouchEvent 方法来决定是否要拦截这个事件,若是 interceptTouchEvent 返回 true ,也就是拦截掉了,则交给它的 onTouchEvent 来处理,若是 interceptTouchEvent 返回 false ,那么就传递给子 view ,由子 view 的 dispatchTouchEvent 再来开始这个事件的分发。若是事件传递到某一层的子 view 的 onTouchEvent 上了,这个方法返回了 false ,那么这个事件会从这个 view 往上传递,都是 onTouchEvent 来接收。而若是传递到最上面的 onTouchEvent 也返回 false 的话,这个事件就会“   消失”,并且接收不到下一次事件。



3.TouchEvent的处理流程图

本身制做了个TouchEvent处理的流程图,方便理清TouchEvent事件在各类UI对象以及对应方法中的处理机制。将流程图与上面的运行日志结合分析,发现对TouchEvent处理的机制清晰了不少。如有错误之处,欢迎指教。

wKioL1Qo01bh7FokAAKn6n5a4u0915.jpg

相关文章
相关标签/搜索