如何理解MotionEvent对象
如何理解MotionEvent對象,相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。
一、MotionEvent對象
當(dāng)用戶觸摸屏幕時,將創(chuàng)建一個MontionEvent對象。MotionEvent包含了關(guān)于發(fā)生觸摸的位置和時間的信息,以及觸摸事件的其他細(xì)節(jié)。
獲取MontionEvent對象的方法有:
1.重載Activity中的onTouchEvent(MotionEvent event)方法;
2.View對象調(diào)用View.setOnTouchListener接口實現(xiàn)onTouch(View v, MotionEvent event)方法;
獲得MontionEvent對象后,可以通過以下常用方法進(jìn)一步獲取觸控事件的具體信息:
event.getAction()//獲取觸控動作比如ACTION_DOWN event.getPointerCount();//獲取觸控點的數(shù)量,比如2則可能是兩個手指同時按壓屏幕 event.getPointerId(nID);//對于每個觸控的點的細(xì)節(jié),我們可以通過一個循環(huán)執(zhí)行g(shù)etPointerId方法獲取索引 event.getX(nID);//獲取第nID個觸控點的x位置 event.getY(nID);//獲取第nID個點觸控的y位置 event.getPressure(nID);//LCD可以感應(yīng)出用戶的手指壓力,當(dāng)然具體的級別由驅(qū)動和物理硬件決定的 event.getDownTime()//按下開始時間 event.getEventTime()//事件結(jié)束時間 event.getEventTime()-event.getDownTime());//總共按下時花費(fèi)時間
觸控對象中的主要相關(guān)常量:
/** *用于多點觸控進(jìn)行操作 */ publicstaticfinalintACTION_MASK=0xff; /** *手指按下時 */ publicstaticfinalintACTION_DOWN=0; /** *手指放開時 */ publicstaticfinalintACTION_UP=1; /** *移動操作時 */ publicstaticfinalintACTION_MOVE=2; /** *用戶無規(guī)則的操作時可能觸發(fā).此操作用于表明,一個觸摸序列在未發(fā)生任何實際操作的情況下結(jié)束. */ publicstaticfinalintACTION_CANCEL=3; /** *觸摸操作發(fā)生在窗口之外,但仍然能夠找到該操作的特殊情況下設(shè)置. */ publicstaticfinalintACTION_OUTSIDE=4; /** * */ publicstaticfinalintACTION_POINTER_DOWN=5; /** * */ publicstaticfinalintACTION_POINTER_UP=6; /** * */ publicstaticfinalintACTION_HOVER_MOVE=7; /** *Android3.1開始引入的常量,來自于輸入設(shè)備(如鼠標(biāo)),而非觸摸屏. */ publicstaticfinalintACTION_SCROLL=8;
二、MotionEvent對象處理過程
范例視圖如下:
activity_main.xml代碼如下:
<RelativeLayoutxmlns: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類代碼如下:
packagecom.example.d_motionevent;
importandroid.content.Context;
importandroid.util.AttributeSet;
importandroid.util.Log;
importandroid.view.MotionEvent;
importandroid.widget.Button;
/**
*
*@authorzeng
*/
publicclassBooleanButtonextendsButton
{
publicBooleanButton(Contextcontext,AttributeSetattrs)
{
super(context,attrs);
}
protectedbooleanmyValue()
{
returnfalse;
}
@Override
publicbooleanonTouchEvent(MotionEventevent)
{
StringmyTag=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,"該buttontouch="+myValue());
returnmyValue();
}
}
TrueButton類代碼:
packagecom.example.d_motionevent;
importandroid.content.Context;
importandroid.util.AttributeSet;
/**
*onTouchEvent返回true的Button.
*@authorzeng
*/
publicclassTrueButtonextendsBooleanButton
{
publicTrueButton(Contextcontext,AttributeSetattrs)
{
super(context,attrs);
}
@Override
protectedbooleanmyValue()
{
returntrue;
}
}
FalseButton類代碼:
packagecom.example.d_motionevent;
importandroid.content.Context;
importandroid.util.AttributeSet;
/**
*onTouchEvent返回false的Button.
*@authorzeng
*/
publicclassFalseButtonextendsBooleanButton
{
publicFalseButton(Contextcontext,AttributeSetattrs)
{
super(context,attrs);
}
}
MainActivity.class代碼如下:
packagecom.example.d_motionevent;
importandroid.os.Bundle;
importandroid.app.Activity;
importandroid.util.Log;
importandroid.view.MotionEvent;
importandroid.view.View;
importandroid.view.View.OnTouchListener;
importandroid.widget.Button;
/**
*地址:http://glblong.blog.51cto.com/3058613/1557683
*@authorzeng
*/
publicclassMainActivityextendsActivityimplementsOnTouchListener
{
@Override
protectedvoidonCreate(BundlesavedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Viewlayout1=findViewById(R.id.layout1);
layout1.setOnTouchListener(this);
ButtontrueButton1=(Button)findViewById(R.id.trueButton1);
trueButton1.setOnTouchListener(this);
ButtonfalseButton1=(Button)findViewById(R.id.falseButton1);
falseButton1.setOnTouchListener(this);
Viewlayout2=findViewById(R.id.layout2);
layout2.setOnTouchListener(this);
ButtontrueButton2=(Button)findViewById(R.id.trueButton2);
trueButton2.setOnTouchListener(this);
ButtonfalseButton2=(Button)findViewById(R.id.falseButton2);
falseButton2.setOnTouchListener(this);
}
@Override
publicbooleanonTouch(Viewv,MotionEventevent)
{
StringmyTag=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");
returntrue;
}
else
{
Log.e(myTag,"==false");
returnfalse;
}
}
@Override
publicbooleanonTouchEvent(MotionEventevent)
{
returnsuper.onTouchEvent(event);
}
protectedstaticStringdescribeEvent(Viewview,MotionEventevent)
{
StringBuildersb=newStringBuilder(300);
sb.append("Action:").append(event.getAction()).append("\n");//獲取觸控動作比如ACTION_DOWN
sb.append("相對坐標(biāo):").append(event.getX()).append("*").append(event.getY()).append("");
sb.append("絕對坐標(biāo):").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范圍內(nèi)");
}
sb.append("Edgeflags:").append(event.getEdgeFlags()).append("");//邊緣標(biāo)記,但是看設(shè)備情況,很可能始終返回0
sb.append("Pressure:").append(event.getPressure()).append("");//壓力值,0-1之間,看情況,很可能始終返回1
sb.append("Size:").append(event.getSize()).append("\n");//指壓范圍
sb.append("Downtime:").append(event.getDownTime()).append("ms");
sb.append("Eventtime:").append(event.getEventTime()).append("ms");
sb.append("Elapsed:").append(event.getEventTime()-event.getDownTime()).append("ms\n");
returnsb.toString();
}
}
點擊過程分析:
1.點擊上部的【true_Btn_上】,運(yùn)行日志如下:
此時執(zhí)行的是Activity上的onTouch方法并返回了True。
onTouch()方法返回true,因為編碼TrueButton的目的是為返回true。返回true會告訴Android,MotionEvent對象已經(jīng)被使用,不能將它提供給其他方法。
它還告訴Android,繼續(xù)將此觸摸序列的觸摸事件發(fā)送到此方法。這就是為什么我們在ACTION_DOWN事件后還會看到ACTION_UP或ACTION_MOVE等其他事件。
當(dāng)觸摸【true_Btn_上】時,按鈕并沒有高亮顏色變化。這是因為,onTouch()是在調(diào)用任何按鈕方法之前調(diào)用的,而且onTouch()方法返回了true,所以Android就不會再調(diào)用【true_Btn_上】按鈕的onTouchEvent()方法了。
如果在onTouch()方法中,在返回true的行之前添加一行"v.onTouchEvent(event);",那么將會看到觸摸時按鈕會有顏色變化了。
2.點擊上部的【false_Btn_上】,運(yùn)行日志如下:
點擊后,false_btn按鈕會處于常亮狀態(tài)。效果如圖:
Android接收到MotionEvent對象中的ACTION_DOWN事件,將它傳遞給MainActivity類中的onTouch()方法。onTouch()執(zhí)行后返回false。
這個過程告訴Android,onTouch()方法未使用該事件,所以Android尋找要調(diào)用的下一個方法,也就是范例中的FalseButton類中重寫的onTouchEvent()方法。參見BooleanButton.java中的onTouchEvent()方法,先執(zhí)行父類的onTouchEvent()方法,然后再返回了false。此時打印出來的Logcat日志與之前的完全一樣,因為我們?nèi)匀辉谕粋€View對象FalseButton中。
根據(jù)日志可以看到,父類希望從onTouchEvent()返回true,但是FalseButton的該方法返回了false。通過返回false,再次告訴Android我們未使用此事件,所以Android不會將ACTION_UP事件發(fā)送到我們的按鈕,以至于該按鈕不知道手指是否已離開了觸摸屏。這也是為什么FalseButton在被按下時一直停留在被按下的顏色狀態(tài)。
簡而言之,每次從收到MotionEvent對象的UI對象返回false時,Android就會停止將MotionEvent對象繼續(xù)發(fā)送到該UI對象,同時還會不斷的查找另一個UI對象來使用MotionEvent對象。
接著看日志,Android兩次嘗試找到ACTION_DOWN事件的使用者但都失敗了,現(xiàn)在它前進(jìn)道應(yīng)用程序中下一個可能接收該事件的View,也就是按鈕底層的布局true_Layout。
此時,Android再次調(diào)用了MainActivity類中的onTouch()方法,但是現(xiàn)在接收事件的對象變成了true_Layout。接收到的MotionEvent對象的所有信息也與之前相同,只有Y坐標(biāo)除外,因為點擊位置的Y坐標(biāo)相對于布局左上角要比相對于按鈕的左上角的距離來得大。因為true_Layout的onTouch()方法返回true,所以Android將觸摸事件的剩余信息發(fā)送到了布局。
3.點擊上部的【true_Btn_上】按鈕不放,同時觸摸其他區(qū)域。
觸摸【true_Btn_上】按鈕,在離開按鈕之前,其他手指在屏幕上其他區(qū)域移動。此時,Logcat將顯示接收觸摸事件的對象都是【true_Btn_上】按鈕。甚至手指離開【true_Btn_上】按鈕,而另一手指仍然在屏幕上移動時,接收到觸摸事件的仍然還是【true_Btn_上】按鈕。
當(dāng)從onTouch()返回true時,觸摸事件序列將與【true_Btn_上】按鈕相關(guān)聯(lián)。這告訴了Android,它可以停止查找用來接收MotionEvent對象的UI對象了,只需將此觸摸序列的所有未來MotionEvent對象發(fā)送給我們。即使在拖動手指時遇到了另一個視圖,我們?nèi)匀粫壎ǖ酱诵蛄械脑家晥D。
4.點擊下部的【false_Btn_下】按鈕,運(yùn)行日志如下:
原理與前幾點相同。此時若按住按鈕的手指離開前,其他手指繼續(xù)觸摸屏幕,則將不再繼續(xù)輸出日志,因為Android找不到接收MotionEvent對象并返回true結(jié)果的UI對象,直到開始下一個新觸摸序列。
范例源碼見附件。地址:http://glblong.blog.51cto.com/3058613/1557683
三、MotionEvent回收
MotionEvent類中有個recycle()方法,但是不要通過此方法對MotionEvent對象進(jìn)行回收。如果回調(diào)方法沒有使用MotionEvent對象,并且返回了false,MotionEvent對象可能會被傳遞到其他某個方法、視圖或我們的活動。
MotionEvent類中還有個obtain()方法,通過此方法可以創(chuàng)建一個MotionEvent的副本或者全新的MotionEvent,這個副本或全新的事件對象是在完成之后應(yīng)該回收的對象。
四、小結(jié)
1.onTouch()方法與View類中的onTouchEvent()方法的區(qū)別
onTouch()是View.setOnTouchListener后調(diào)用的方法,如果一個View對象setOnTouchListener后,同時又重寫了View類自身的onTouchEvent()方法,那么當(dāng)屏幕觸摸時會先調(diào)用哪個方法呢?
每當(dāng)事件產(chǎn)生時,都要先經(jīng)由dispatchTouchEvent方法來分發(fā)。看下View類中的dispatchTouchEvent()源碼,如下:
/**
*Passthetouchscreenmotioneventdowntothetargetview,orthisview
*ifitisthetarget.
*
*@paramevent
*Themotioneventtobedispatched.
*@returnTrueiftheeventwashandledbytheview,falseotherwise.
*/
publicbooleandispatchTouchEvent(MotionEventevent)
{
//用于測試目,直接忽略
if(mInputEventConsistencyVerifier!=null)
{
mInputEventConsistencyVerifier.onTouchEvent(event,0);
}
//上面代碼的第2個標(biāo)注。未被其他窗口遮蓋
if(onFilterTouchEventForSecurity(event))
{
//noinspectionSimplifiableIfStatement
if(mOnTouchListener!=null&&(mViewFlags&ENABLED_MASK)==ENABLED&&mOnTouchListener.onTouch(this,event))
{
//如果有監(jiān)聽器,執(zhí)行監(jiān)聽器的,不在執(zhí)行當(dāng)前視圖的onTouchEvent方法
returntrue;
}
//執(zhí)行當(dāng)前視圖的onTouchEvent方法
if(onTouchEvent(event))
{
returntrue;
}
}
//用于測試目,直接忽略
if(mInputEventConsistencyVerifier!=null)
{
mInputEventConsistencyVerifier.onUnhandledEvent(event,0);
}
returnfalse;
}
dispatchTouchEvent()方法里先是判斷了是否有監(jiān)聽器,然后才會去判斷onTouchEvent(event)。因此會先執(zhí)行onTouch()方法,如果監(jiān)聽器的onTouch()返回false,則繼續(xù)執(zhí)行View的onTouchEvent()。
2.觸摸事件的傳遞過程
1).觸摸事件最先被最頂層的UI對象(比如范例中的Btn)接收,如果該View對象接收后返回false,則繼續(xù)傳向其上一層UI對象(比如范例中的layout),依此類推,直到最底層的Activity。
2).同一個View對象,接收到觸摸事件時,如果有設(shè)置監(jiān)聽器,先執(zhí)行View.setOnTouchListener里的onTouch(),然后執(zhí)行View類中的onTouchEvent(event)方法。
3).當(dāng)一個UI對象接收到觸摸事件并返回true時,同時多指觸摸的其他事件序列都會被該UI對象接收,直至下一個新的觸摸事件序列產(chǎn)生。
4).當(dāng)各層的UI對象接收觸摸事件都返回false時,此刻同時多指觸摸,由此產(chǎn)生的其他事件序列將找不到接收的UI對象。因為當(dāng)前原始的接收UI對象返回false,系統(tǒng)會一直查找下一個可能接收事件并返回true的UI對象但卻又無法找到。
總結(jié)
以上是生活随笔為你收集整理的如何理解MotionEvent对象的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 解决找不到steam_api64.dll
- 下一篇: zhs16gbk对应mysql_数据库的