溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何理解MotionEvent對象

發布時間:2021-11-26 17:09:41 來源:億速云 閱讀:168 作者:柒染 欄目:移動開發

如何理解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對象處理過程

    范例視圖如下:

    如何理解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_上】,運行日志如下:

如何理解MotionEvent對象

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

如何理解MotionEvent對象

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

如何理解MotionEvent對象

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

如何理解MotionEvent對象

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

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

三、MotionEvent回收

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

    MotionEvent類中還有個obtain()方法,通過此方法可以創建一個MotionEvent的副本或者全新的MotionEvent,這個副本或全新的事件對象是在完成之后應該回收的對象。

四、小結

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

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

每當事件產生時,都要先經由dispatchTouchEvent方法來分發??聪耉iew類中的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對象但卻又無法找到。

看完上述內容,你們掌握如何理解MotionEvent對象的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女