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

一、MotionEvent对象

当用户触摸屏幕时,将创建一个MontionEvent对象。MotionEvent包含了关于发生触摸的位置和时间的信息,以及触摸事件的其他细节。

获取MontionEvent对象的方法有:

1.重载Activity中的onTouchEvent(MotionEvent event)方法;

2.View对象调用View.setOnTouchListener接口实现onTouch(View v, MotionEvent event)方法;

获得MontionEvent对象后,可以通过以下常用方法进一步获取触控事件的具体信息:

    event.getAction() //获取触控动作比如ACTION_DOWN
    event.getPointerCount(); //获取触控点的数量,比如2则可能是两个手指同时按压屏幕
    event.getPointerId(nID); //对于每个触控的点的细节,我们可以通过一个循环执行getPointerId方法获取索引
    event.getX(nID); //获取第nID个触控点的x位置
    event.getY(nID); //获取第nID个点触控的y位置
    event.getPressure(nID); //LCD可以感应出用户的手指压力,当然具体的级别由驱动和物理硬件决定的
    event.getDownTime() //按下开始时间
    event.getEventTime() // 事件结束时间
    event.getEventTime()-event.getDownTime()); //总共按下时花费时间

    触控对象中的主要相关常量:

    /**
     * 用于多点触控进行操作 
     */
    public static final int ACTION_MASK = 0xff;
    
    /**
     * 手指按下时
     */
    public static final int ACTION_DOWN = 0;
    
    /**
     * 手指放开时
     */
    public static final int ACTION_UP = 1;
    
    /**
     * 移动操作时
     */
    public static final int ACTION_MOVE = 2;
    
    /**
     * 用户无规则的操作时可能触发. 此操作用于表明,一个触摸序列在未发生任何实际操作的情况下结束.
     */
    public static final int ACTION_CANCEL = 3;
    
    /**
     * 触摸操作发生在窗口之外,但仍然能够找到该操作的特殊情况下设置.
     */
    public static final int ACTION_OUTSIDE = 4;
    
    /**
     * 
     */
    public static final int ACTION_POINTER_DOWN = 5;
    
    /**
     * 
     */
    public static final int ACTION_POINTER_UP = 6;
    
    /**
     * 
     */
    public static final int ACTION_HOVER_MOVE = 7;
    
    /**
     * Android3.1开始引入的常量,来自于输入设备(如鼠标),而非触摸屏.
     */
    public static final int ACTION_SCROLL = 8;

二、MotionEvent对象处理过程

范例视图如下:

activity_main.xml代码如下: 

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:orientation="vertical" >

        <LinearLayout
            android:id="@+id/layout1"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:orientation="vertical"
            android:tag="true_Layout" >

            <com.example.d_motionevent.TrueButton
                android:id="@+id/trueButton1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:tag="true_Btn_上"
                android:text="true_Btn_上" />

            <com.example.d_motionevent.FalseButton
                android:id="@+id/falseButton1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:tag="false_Btn_上"
                android:text="false_Btn_上" />

        </LinearLayout>

        <LinearLayout
            android:id="@+id/layout2"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:background="#ff00ff"
            android:orientation="vertical"
            android:tag="false_Layout" >

            <com.example.d_motionevent.TrueButton
                android:id="@+id/trueButton2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:tag="true_Btn_下"
                android:text="true_Btn_下" />

            <com.example.d_motionevent.FalseButton
                android:id="@+id/falseButton2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:tag="false_Btn_下"
                android:text="false_Btn_下" />

        </LinearLayout>
    </LinearLayout>

</RelativeLayout>

自定义Button类代码如下:

package com.example.d_motionevent;

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

/**
 * 
 * @author zeng
 */
public class BooleanButton extends Button
{
    public BooleanButton(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }
    
    protected boolean myValue()
    {
        return false;
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        String myTag = this.getTag().toString();
        
        Log.e(myTag, "===========" + "Button 的 onTouchEvent 方法" + "===========");
        Log.e(myTag, MainActivity.describeEvent(this, event));
        Log.e(myTag, "父类 onTouchEvent() = " + super.onTouchEvent(event));
        Log.e(myTag, "该button touch = " + myValue());
        return myValue();
    }
}

TrueButton类代码:

package com.example.d_motionevent;

import android.content.Context;
import android.util.AttributeSet;

/**
 * onTouchEvent返回true的Button.
 * @author zeng
 */
public class TrueButton extends BooleanButton
{
    public TrueButton(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }
    
    @Override
    protected boolean myValue()
    {
        return true;
    }
}

 FalseButton类代码:

package com.example.d_motionevent;

import android.content.Context;
import android.util.AttributeSet;

/**
 * onTouchEvent返回false的Button.
 * @author zeng
 */
public class FalseButton extends BooleanButton
{
    public FalseButton(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }
}

MainActivity.class代码如下:

package com.example.d_motionevent;

import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.Button;

/**
 * 地址:http://glblong.blog.51cto.com/3058613/1557683
 * @author zeng
 */
public class MainActivity extends Activity implements OnTouchListener
{
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        View layout1 = findViewById(R.id.layout1);
        layout1.setOnTouchListener(this);
        
        Button trueButton1 = (Button) findViewById(R.id.trueButton1);
        trueButton1.setOnTouchListener(this);
        Button falseButton1 = (Button) findViewById(R.id.falseButton1);
        falseButton1.setOnTouchListener(this);
        
        View layout2 = findViewById(R.id.layout2);
        layout2.setOnTouchListener(this);
        
        Button trueButton2 = (Button) findViewById(R.id.trueButton2);
        trueButton2.setOnTouchListener(this);
        Button falseButton2 = (Button) findViewById(R.id.falseButton2);
        falseButton2.setOnTouchListener(this);
    }
    
    @Override
    public boolean onTouch(View v, MotionEvent event)
    {
        String myTag = v.getTag().toString();
        
        Log.e(myTag, "===========" + "Activity 的 onTouch 方法" + "===========");
        Log.e(myTag, "被点击的View = " + myTag);
        Log.e(myTag, describeEvent(v, event));
        
        if ("true".equals(myTag.substring(0, 4)))
        {
            Log.e(myTag, " == true");
            return true;
        }
        else
        {
            Log.e(myTag, " == false");
            return false;
        }
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        return super.onTouchEvent(event);
    }
    
    protected static String describeEvent(View view, MotionEvent event)
    {
        StringBuilder sb = new StringBuilder(300);
        
        sb.append("Action: ").append(event.getAction()).append("\n");// 获取触控动作比如ACTION_DOWN
        sb.append("相对坐标: ").append(event.getX()).append("  *  ").append(event.getY()).append("   ");
        sb.append("绝对坐标: ").append(event.getRawX()).append("  *  ").append(event.getRawY()).append("\n");
        
        if (event.getX() < 0 || event.getX() > view.getWidth() || event.getY() < 0 || event.getY() > view.getHeight())
        {
            sb.append("未点击在View范围内");
        }
        
        sb.append("Edge flags: ").append(event.getEdgeFlags()).append("  ");// 边缘标记,但是看设备情况,很可能始终返回0
        sb.append("Pressure: ").append(event.getPressure()).append("  ");// 压力值,0-1之间,看情况,很可能始终返回1
        sb.append("Size: ").append(event.getSize()).append("\n");// 指压范围
        sb.append("Down time: ").append(event.getDownTime()).append("ms   ");
        sb.append("Event time: ").append(event.getEventTime()).append("ms   ");
        sb.append("Elapsed: ").append(event.getEventTime() - event.getDownTime()).append("ms\n");
        
        return sb.toString();
    }
}

点击过程分析:


1.点击上部的【true_Btn_上】,运行日志如下:

此时执行的是Activity上的onTouch方法并返回了True。

onTouch()方法返回true,因为编码TrueButton的目的是为返回true。返回true会告诉Android,MotionEvent对象已经被使用,不能将它提供给其他方法。

它还告诉Android,继续将此触摸序列的触摸事件发送到此方法。这就是为什么我们在ACTION_DOWN事件后还会看到ACTION_UP或ACTION_MOVE等其他事件。

当触摸【true_Btn_上】时,按钮并没有高亮颜色变化。这是因为,onTouch()是在调用任何按钮方法之前调用的,而且onTouch()方法返回了true,所以Android就不会再调用【true_Btn_上】按钮的onTouchEvent()方法了。

如果在onTouch()方法中,在返回true的行之前添加一行"v.onTouchEvent(event);",那么将会看到触摸时按钮会有颜色变化了。

2.点击上部的【false_Btn_上】,运行日志如下:

点击后,false_btn按钮会处于常亮状态。效果如图:

Android接收到MotionEvent对象中的ACTION_DOWN事件,将它传递给MainActivity类中的onTouch()方法。onTouch()执行后返回false。

这个过程告诉Android,onTouch()方法未使用该事件,所以Android寻找要调用的下一个方法,也就是范例中的FalseButton类中重写的onTouchEvent()方法。参见BooleanButton.java中的onTouchEvent()方法,先执行父类的onTouchEvent()方法,然后再返回了false。此时打印出来的Logcat日志与之前的完全一样,因为我们仍然在同一个View对象FalseButton中。

根据日志可以看到,父类希望从onTouchEvent()返回true,但是FalseButton的该方法返回了false。通过返回false,再次告诉Android我们未使用此事件,所以Android不会将ACTION_UP事件发送到我们的按钮,以至于该按钮不知道手指是否已离开了触摸屏。这也是为什么FalseButton在被按下时一直停留在被按下的颜色状态。

简而言之,每次从收到MotionEvent对象的UI对象返回false时,Android就会停止将MotionEvent对象继续发送到该UI对象,同时还会不断的查找另一个UI对象来使用MotionEvent对象。

接着看日志,Android两次尝试找到ACTION_DOWN事件的使用者但都失败了,现在它前进道应用程序中下一个可能接收该事件的View,也就是按钮底层的布局true_Layout。

此时,Android再次调用了MainActivity类中的onTouch()方法,但是现在接收事件的对象变成了true_Layout。接收到的MotionEvent对象的所有信息也与之前相同,只有Y坐标除外,因为点击位置的Y坐标相对于布局左上角要比相对于按钮的左上角的距离来得大。因为true_Layout的onTouch()方法返回true,所以Android将触摸事件的剩余信息发送到了布局。

3.点击上部的【true_Btn_上】按钮不放,同时触摸其他区域。

触摸【true_Btn_上】按钮,在离开按钮之前,其他手指在屏幕上其他区域移动。此时,Logcat将显示接收触摸事件的对象都是【true_Btn_上】按钮。甚至手指离开【true_Btn_上】按钮,而另一手指仍然在屏幕上移动时,接收到触摸事件的仍然还是【true_Btn_上】按钮。

当从onTouch()返回true时,触摸事件序列将与【true_Btn_上】按钮相关联。这告诉了Android,它可以停止查找用来接收MotionEvent对象的UI对象了,只需将此触摸序列的所有未来MotionEvent对象发送给我们。即使在拖动手指时遇到了另一个视图,我们仍然会绑定到此序列的原始视图。

4.点击下部的【false_Btn_下】按钮,运行日志如下:

原理与前几点相同。此时若按住按钮的手指离开前,其他手指继续触摸屏幕,则将不再继续输出日志,因为Android找不到接收MotionEvent对象并返回true结果的UI对象,直到开始下一个新触摸序列。

范例源码见附件。地址:http://glblong.blog.51cto.com/3058613/1557683

三、MotionEvent回收

MotionEvent类中有个recycle()方法,但是不要通过此方法对MotionEvent对象进行回收。如果回调方法没有使用MotionEvent对象,并且返回了false,MotionEvent对象可能会被传递到其他某个方法、视图或我们的活动。

MotionEvent类中还有个obtain()方法,通过此方法可以创建一个MotionEvent的副本或者全新的MotionEvent,这个副本或全新的事件对象是在完成之后应该回收的对象。

时间: 2024-11-08 19:18:59

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

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

   其他相关博文:    Android笔记:触摸事件的分析与总结----MotionEvent对象    Android笔记:触摸事件的分析与总结----TouchEvent处理机制 Android中的事件类型分为按键事件和屏幕触摸事件.TouchEvent是屏幕触摸事件的基础事件,要深入了解屏幕触摸事件的处理机制,就必须掌握TouchEvent在整个触摸事件中的转移和处理过程.此处将对TouchEvent处理机制的学习做个小小的总结和备记. 当屏幕中包含一个ViewGroup,而这个Vie

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

一.多点触控 当多点同时触摸屏幕时,系统将会产生如下的触摸事件: 1.ACTION_DOWN:触摸屏幕的第一个点.此时手势开始.该点的数据通常在MotionEvent事件队列索引位置0处. 2.ACTION_POINTER_DOWN:除了第一个点的其他触摸点数据.该点的数据的索引位置由getActionIndex()方法返回. 3.ACTION_MOVE:在手势过程中发生的一次变化. 4.ACTION_POINTER_UP:当不是第一个点的其他点UP后触发. 5.ACTION_UP:当手势中的最

Android ViewGroup 触摸事件传递机制

引言 上一篇博客我们学习了Android View 触摸事件传递机制,不了解的同学可以查看Android View 触摸事件传递机制.今天继续学习Android触摸事件传递机制,这篇博客将和大家一起探讨ViewGroup的触摸事件传递机制. 示例 示例代码如下: public class MainActivity extends ActionBarActivity { private String TAG = "MainActivity"; private MyViewGroup pa

Android View 触摸事件传递机制

PS:以现在的眼光看以前写的博客感觉写的很烂,或许或一段时间再看现在的博客会有同样的感觉.所以每时每刻都去学习,去发现和理解新的东西. 引言 由于之前写的一篇关于Android事件传递顺序的博客质量太差,可能是理解不到位的原因,故最近又花了许多时间再次去看Android源码,看完之后有了新的理解,所以打算重新整理这篇博客.理解Android触摸事件传递机制有助于日后的开发以及自定义一些手势效果等.注意:这篇博客是基于Android2.0源码来分析的,不管老版本还是新版本的Android,其内部触

一个demo让你彻底理解Android中触摸事件的分发

注:本文涉及的demo的地址:https://github.com/absfree/TouchDispatch 1. 触摸动作及事件序列 (1)触摸事件的动作 触摸动作一共有三种:ACTION_DOWN.ACTION_MOVE.ACTION_UP.当用户手指接触屏幕时,便产生一个动作为ACTION_DOWN的触摸事件,此时若用户的手指立即离开屏幕,会产生一个动作为ACTION_UP的触摸事件:若用户手指接触屏幕后继续滑动,当滑动距离超过了系统中预定义的距离常数,则产生一个动作为ACTION_MO

IOS学习笔记-触摸事件

一.事件传递的过程1.用户手指触摸屏幕,产生一个事件对象 2.系统会将这个事件对象添加到事件队列(先进先出)中 3.由UIApplication取出事件队列中的事件对象进行处理 4.UIApplication会先将事件对象传递给主要的UIWindow(当然,要先检测UIWindow是否能够接收和传递事件) 5.UIWindow会遍历所有的子控件,看看触摸点有没有落在某个子控件上面: 1> 如果触摸点没有落在子控件上,那么就由UIWindow直接处理这个事件,事件传递完毕 2> 如果触摸点落在子

Android 手势&amp;触摸事件

在刚开始学Android的时候,就觉得Google的文档不咋样,在研究手势时,更加的感觉Google的文档写得实在是太差了.很多常量,属性和方法,居然连个描述都没有.没有描述也就罢了,但是OnGestureListener里手势这么多,它也没有一个介绍说明,在没有进行不断才尝试之前,谁能搞懂onLongPress和onShowPress,onScroll和onFling的关系与差别吗?Google真的需要在文档方面做一次大手术了.不过好在经过鄙人不断反复的尝试.从个人的角度为这几个手势动作做出了

Android ViewGroup触摸事件拦截详解

前言 在自定义ViewGroup中,有时候需要实现触摸事件拦截,比如ListView下拉刷新就是典型的触摸事件拦截的例子.触摸事件拦截就是在触摸事件被parent view拦截,而不会分发给其child,即使触摸发生在该child身上.被拦截的事件会转到parent view的onTouchEvent方法中进行处理.但是这个交互过程还是挺复杂的,有多种情况,今天我们就来分析一下吧.这篇分析文章已经放了一段时间了,如果有任何问题请高人指出. 触摸事件的分发 简单来说触摸事件的分发会经过这么几个顺序

android屏幕触摸事件机制(转)

android中的事件类型分为按键事件和屏幕触摸事件,Touch事件是屏幕触摸事件的基础事件,有必要对它进行深入的了解.一个最简单的屏幕触摸动作触发了一系列Touch事件:ACTION_DOWN->ACTION_MOVE->ACTION_MOVE->ACTION_MOVE...->ACTION_MOVE->ACTION_UP当屏幕中包含一个ViewGroup,而这个ViewGroup又包含一个子view,这个时候android系统如何处理Touch事件呢?到底是ViewGro