Android筆記:觸摸事件的分析與總結----MotionEvent對象

   其餘相關博文:java

   Android筆記:觸摸事件的分析與總結----MotionEvent對象android

   Android筆記:觸摸事件的分析與總結----TouchEvent處理機制app

    Android筆記:觸摸事件的分析與總結----多點觸控ide


1、MotionEvent對象佈局

    當用戶觸摸屏幕時,將建立一個MontionEvent對象。MotionEvent包含了關於發生觸摸的位置和時間的信息,以及觸摸事件的其餘細節。測試

    獲取MontionEvent對象的方法有:ui

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

    2.View對象調用View.setOnTouchListener接口實現onTouch(View v, MotionEvent event)方法;編碼


    得到MontionEvent對象後,能夠經過如下經常使用方法進一步獲取觸控事件的具體信息:spa

    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;

    


2、MotionEvent對象處理過程

    範例視圖以下:

    wKiom1QlAaOy17zsAADq23QwuSQ833.jpg

    

    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_上】,運行日誌以下:

wKioL1QfvF6zXGbRAAfjcv460KQ430.jpg

    此時執行的是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_上】,運行日誌以下:

wKiom1QfmRKzSA05AAn2vtE-E5o910.jpg


    點擊後,false_btn按鈕會處於常亮狀態。效果如圖:

wKioL1QfnXvC-z0IAAG57335zho319.jpg


    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_下】按鈕,運行日誌以下:

wKioL1Qf6Lbi0sWQAAdADS-H8rs285.jpg

    原理與前幾點相同。此時若按住按鈕的手指離開前,其餘手指繼續觸摸屏幕,則將再也不繼續輸出日誌,由於Android找不到接收MotionEvent對象並返回true結果的UI對象,直到開始下一個新觸摸序列。


    範例源碼見附件。地址:http://glblong.blog.51cto.com/3058613/1557683


3、MotionEvent回收

    MotionEvent類中有個recycle()方法,可是不要經過此方法對MotionEvent對象進行回收。若是回調方法沒有使用MotionEvent對象,而且返回了false,MotionEvent對象可能會被傳遞到其餘某個方法、視圖或咱們的活動。

    MotionEvent類中還有個obtain()方法,經過此方法能夠建立一個MotionEvent的副本或者全新的MotionEvent,這個副本或全新的事件對象是在完成以後應該回收的對象。




4、小結

1.onTouch()方法與View類中的onTouchEvent()方法的區別

onTouch()是View.setOnTouchListener後調用的方法,若是一個View對象setOnTouchListener後,同時又重寫了View類自身的onTouchEvent()方法,那麼當屏幕觸摸時會先調用哪一個方法呢?


每當事件產生時,都要先經由dispatchTouchEvent方法來分發。看下View類中的dispatchTouchEvent()源碼,以下:

    /**
     * Pass the touch screen motion event down to the target view, or this view
     * if it is the target.
     * 
     * @param event
     *            The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    public boolean dispatchTouchEvent(MotionEvent event)
    {
        // 用於測試目,直接忽略
        if (mInputEventConsistencyVerifier != null)
        {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }
         
        // 上面代碼的第2個標註。 未被其餘窗口遮蓋
        if (onFilterTouchEventForSecurity(event))
        {
            // noinspection SimplifiableIfStatement
            if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event))
            {
                // 若是有監聽器,執行監聽器的,不在執行當前視圖的onTouchEvent方法
                return true;
            }
            // 執行當前視圖的onTouchEvent方法
            if (onTouchEvent(event))
            {
                return true;
            }
        }
         
        // 用於測試目,直接忽略
        if (mInputEventConsistencyVerifier != null)
        {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }

dispatchTouchEvent()方法裏先是判斷了是否有監聽器,而後纔會去判斷onTouchEvent(event)。所以會先執行onTouch()方法,若是監聽器的onTouch()返回false,則繼續執行View的onTouchEvent()。



2.觸摸事件的傳遞過程

1).觸摸事件最早被最頂層的UI對象(好比範例中的Btn)接收,若是該View對象接收後返回false,則繼續傳向其上一層UI對象(好比範例中的layout),依此類推,直到最底層的Activity。

2).同一個View對象,接收到觸摸事件時,若是有設置監聽器,先執行View.setOnTouchListener裏的onTouch(),而後執行View類中的onTouchEvent(event)方法。

3).當一個UI對象接收到觸摸事件並返回true時,同時多指觸摸的其餘事件序列都會被該UI對象接收,直至下一個新的觸摸事件序列產生。

4).當各層的UI對象接收觸摸事件都返回false時,此刻同時多指觸摸,由此產生的其餘事件序列將找不到接收的UI對象。由於當前原始的接收UI對象返回false,系統會一直查找下一個可能接收事件並返回true的UI對象但卻又沒法找到。

相關文章
相關標籤/搜索