簡(jiǎn)單講解Android開(kāi)發(fā)中觸摸和點(diǎn)擊事件的相關(guān)編程方法
在Android上,不止一個(gè)途徑來(lái)偵聽(tīng)用戶和應(yīng)用程序之間交互的事件。對(duì)于用戶界面里的事件,偵聽(tīng)方法就是從與用戶交互的特定視圖對(duì)象截獲這些事件。視圖類(lèi)提供了相應(yīng)的手段。
在各種用來(lái)組建布局的視圖類(lèi)里面,你可能會(huì)注意到一些公共的回調(diào)方法看起來(lái)對(duì)用戶界面事件有用。這些方法在該對(duì)象的相關(guān)動(dòng)作發(fā)生時(shí)被Android框架調(diào)用。比如,當(dāng)一個(gè)視圖(如一個(gè)按鈕)被觸摸時(shí),該對(duì)象上的onTouchEvent()方法會(huì)被調(diào)用。不過(guò),為了偵聽(tīng)這個(gè)事件,你必須擴(kuò)展這個(gè)類(lèi)并重寫(xiě)該方法。很明顯,擴(kuò)展每個(gè)你想使用的視圖對(duì)象(只是處理一個(gè)事件)是荒唐的。這就是為什么視圖類(lèi)也包含了一個(gè)嵌套接口的集合,這些接口含有實(shí)現(xiàn)起來(lái)簡(jiǎn)單得多的回調(diào)函數(shù)。這些接口叫做事件偵聽(tīng)器event listeners,是用來(lái)截獲用戶和你的界面交互動(dòng)作的"門(mén)票"。
當(dāng)你更為普遍的使用事件偵聽(tīng)器來(lái)偵聽(tīng)用戶動(dòng)作時(shí),總有那么一次你可能得為了創(chuàng)建一個(gè)自定義組件而擴(kuò)展一個(gè)視圖類(lèi)。也許你想擴(kuò)展按鈕Button類(lèi)來(lái)使某些事更花哨。在這種情況下,你將能夠使事件處理器event handlers類(lèi)來(lái)為你的類(lèi)定義缺省事件行為。
事件偵聽(tīng)器Event Listeners
事件偵聽(tīng)器是視圖View類(lèi)的接口,包含一個(gè)單獨(dú)的回調(diào)方法。這些方法將在視圖中注冊(cè)的偵聽(tīng)器被用戶界面操作觸發(fā)時(shí)由Android框架調(diào)用。下面這些回調(diào)方法被包含在事件偵聽(tīng)器接口中:
onClick():包含于View.OnClickListener。當(dāng)用戶觸摸這個(gè)item(在觸摸模式下),或者通過(guò)瀏覽鍵或跟蹤球聚焦在這個(gè)item上,然后按下"確認(rèn)"鍵或者按下跟蹤球時(shí)被調(diào)用。
onLongClick():包含于View.OnLongClickListener。當(dāng)用戶觸摸并控制住這個(gè)item(在觸摸模式下),或者通過(guò)瀏覽鍵或跟蹤球聚焦在這個(gè)item上,然后保持按下"確認(rèn)"鍵或者按下跟蹤球(一秒鐘)時(shí)被調(diào)用。
onFocusChange():包含于View.OnFocusChangeListener。當(dāng)用戶使用瀏覽鍵或跟蹤球?yàn)g覽進(jìn)入或離開(kāi)這個(gè)item時(shí)被調(diào)用。
onKey():包含于View.OnKeyListener。當(dāng)用戶聚焦在這個(gè)item上并按下或釋放設(shè)備上的一個(gè)按鍵時(shí)被調(diào)用。
onTouch():包含于View.OnTouchListener。當(dāng)用戶執(zhí)行的動(dòng)作被當(dāng)做一個(gè)觸摸事件時(shí)被調(diào)用,包括按下,釋放,或者屏幕上任何的移動(dòng)手勢(shì)(在這個(gè)item的邊界內(nèi))。
onCreateContextMenu():包含于View.OnCreateContextMenuListener。當(dāng)正在創(chuàng)建一個(gè)上下文菜單的時(shí)候被調(diào)用(作為持續(xù)的"長(zhǎng)點(diǎn)擊"動(dòng)作的結(jié)果)。
這些方法是它們相應(yīng)接口的唯一"住戶"。要定義這些方法并處理你的事件,在你的活動(dòng)中實(shí)現(xiàn)這個(gè)嵌套接口或定義它為一個(gè)匿名類(lèi)。然后,傳遞你的實(shí)現(xiàn)的一個(gè)實(shí)例給各自的View.set...Listener() 方法。(比如,調(diào)用setOnClickListener()并傳遞給它你的OnClickListener實(shí)現(xiàn)。)
下面的例子說(shuō)明了如何為一個(gè)按鈕注冊(cè)一個(gè)點(diǎn)擊偵聽(tīng)器:
// Create an anonymous implementation of OnClickListener
private OnClickListener mCorkyListener = new OnClickListener() {
public void onClick(View v) {
// do something when the button is clicked
}
};
protected void onCreate(Bundle savedValues) {
...
// Capture our button from layout
Button button = (Button)findViewById(R.id.corky);
// Register the onClick listener with the implementation above
button.setOnClickListener(mCorkyListener);
...
}
你可能會(huì)發(fā)現(xiàn)把OnClickListener作為活動(dòng)的一部分來(lái)實(shí)現(xiàn)會(huì)便利的多。這將避免額外的類(lèi)加載和對(duì)象分配。比如:
public class ExampleActivity extends Activity implements OnClickListener {
protected void onCreate(Bundle savedValues) {
...
Button button = (Button)findViewById(R.id.corky);
button.setOnClickListener(this);
}
// Implement the OnClickListener callback
public void onClick(View v) {
// do something when the button is clicked
}
...
}
注意上面例子中的onClick()回調(diào)沒(méi)有返回值,但是一些其它事件偵聽(tīng)器必須返回一個(gè)布爾值。原因和事件相關(guān)。對(duì)于其中一些,原因如下:
onLongClick() – 返回一個(gè)布爾值來(lái)指示你是否已經(jīng)消費(fèi)了這個(gè)事件而不應(yīng)該再進(jìn)一步處理它。也就是說(shuō),返回true 表示你已經(jīng)處理了這個(gè)事件而且到此為止;返回false 表示你還沒(méi)有處理它和/或這個(gè)事件應(yīng)該繼續(xù)交給其他on-click偵聽(tīng)器。
onKey() –返回一個(gè)布爾值來(lái)指示你是否已經(jīng)消費(fèi)了這個(gè)事件而不應(yīng)該再進(jìn)一步處理它。也就是說(shuō),返回true 表示你已經(jīng)處理了這個(gè)事件而且到此為止;返回false 表示你還沒(méi)有處理它和/或這個(gè)事件應(yīng)該繼續(xù)交給其他on-key偵聽(tīng)器。
onTouch() - 返回一個(gè)布爾值來(lái)指示你的偵聽(tīng)器是否已經(jīng)消費(fèi)了這個(gè)事件。重要的是這個(gè)事件可以有多個(gè)彼此跟隨的動(dòng)作。因此,如果當(dāng)接收到向下動(dòng)作事件時(shí)你返回false,那表明你還沒(méi)有消費(fèi)這個(gè)事件而且對(duì)后續(xù)動(dòng)作也不感興趣。那么,你將不會(huì)被該事件中的其他動(dòng)作調(diào)用,比如手勢(shì)或最后出現(xiàn)向上動(dòng)作事件。
記住按鍵事件總是遞交給當(dāng)前焦點(diǎn)所在的視圖。它們從視圖層次的頂層開(kāi)始被分發(fā),然后依次向下,直到到達(dá)恰當(dāng)?shù)哪繕?biāo)。如果你的視圖(或者一個(gè)子視圖)當(dāng)前擁有焦點(diǎn),那么你可以看到事件經(jīng)由dispatchKeyEvent()方法分發(fā)。除了從你的視圖截獲按鍵事件,還有一個(gè)可選方案,你還可以在你的活動(dòng)中使用onKeyDown() and onKeyUp()來(lái)接收所有的事件。
注意: Android 將首先調(diào)用事件處理器,其次是類(lèi)定義中合適的缺省處理器。這樣,從這些事情偵聽(tīng)器中返回true 將停止事件向其它事件偵聽(tīng)器傳播并且也會(huì)阻塞視圖中的缺事件處理器的回調(diào)函數(shù)。因此當(dāng)你返回true時(shí)確認(rèn)你希望終止這個(gè)事件。
事件處理器Event Handlers
如果你從視圖創(chuàng)建一個(gè)自定義組件,那么你將能夠定義一些回調(diào)方法被用作缺省的事件處理器。在創(chuàng)建自定義組件Building Custom Components的文檔中,你將學(xué)習(xí)到一些用作事件處理的通用回調(diào)函數(shù),包括:
- onKeyDown(int, KeyEvent) - 當(dāng)一個(gè)新的按鍵事件發(fā)生時(shí)被調(diào)用。
- onKeyUp(int, KeyEvent) - 當(dāng)一個(gè)向上鍵事件發(fā)生時(shí)被調(diào)用。
- onTrackballEvent(MotionEvent) - 當(dāng)一個(gè)跟蹤球運(yùn)動(dòng)事件發(fā)生時(shí)被調(diào)用。
- onTouchEvent(MotionEvent) - 當(dāng)一個(gè)觸摸屏移動(dòng)事件發(fā)生時(shí)調(diào)用。
- onFocusChanged(boolean, int, Rect) - 當(dāng)視圖獲得或者丟失焦點(diǎn)時(shí)被調(diào)用。
你應(yīng)該知道還有一些其它方法,并不屬于視圖類(lèi)的一部分,但可以直接影響你處理事件的方式。所以,當(dāng)在一個(gè)布局里管理更復(fù)雜的事件時(shí),考慮一下這些方法:
- Activity.dispatchTouchEvent(MotionEvent) - 這允許你的活動(dòng)可以在分發(fā)給窗口之前捕獲所有的觸摸事件。
- ViewGroup.onInterceptTouchEvent(MotionEvent) - 這允許一個(gè)視圖組ViewGroup 在分發(fā)給子視圖時(shí)觀察這些事件。
- ViewParent.requestDisallowInterceptTouchEvent(boolean) - 在一個(gè)父視圖之上調(diào)用這個(gè)方法來(lái)表示它不應(yīng)該通過(guò)onInterceptTouchEvent(MotionEvent)來(lái)捕獲觸摸事件。
觸摸模式Touch Mode
當(dāng)用戶使用方向鍵或跟蹤球?yàn)g覽用戶界面時(shí),有必要給用戶可操作的item(比如按鈕)設(shè)置焦點(diǎn),這樣用戶可以知道哪個(gè)item將接受輸入。不過(guò),如果這個(gè)設(shè)備有觸摸功能,而且用戶通過(guò)觸摸來(lái)和界面交互,那么就沒(méi)必要高亮items,或者設(shè)定焦點(diǎn)到一個(gè)特定的視圖。這樣,就有一個(gè)交互模式 叫"觸摸模式"。
對(duì)于一個(gè)具備觸摸功能的設(shè)備,一旦用戶觸摸屏幕,設(shè)備將進(jìn)入觸摸模式。自此以后,只有isFocusableInTouchMode()為真的視圖才可以被聚焦,比如文本編輯部件。其他可觸摸視圖,如按鈕,在被觸摸時(shí)將不會(huì)接受焦點(diǎn);它們將只是在被按下時(shí)簡(jiǎn)單的觸發(fā)on-click偵聽(tīng)器。任何時(shí)候用戶按下方向鍵或滾動(dòng)跟蹤球,這個(gè)設(shè)備將退出觸摸模式,然后找一個(gè)視圖來(lái)接受焦點(diǎn),用戶也許不會(huì)通過(guò)觸摸屏幕的方式來(lái)恢復(fù)界面交互。
觸摸模式狀態(tài)的維護(hù)貫穿整個(gè)系統(tǒng)(所有窗口和活動(dòng))。為了查詢當(dāng)前狀態(tài),你可以調(diào)用isInTouchMode() 來(lái)查看這個(gè)設(shè)備當(dāng)前是否處于觸摸模式中。
處理焦點(diǎn)Handling Focus
框架將根據(jù)用戶輸入處理常規(guī)的焦點(diǎn)移動(dòng)。這包含當(dāng)視圖刪除或隱藏,或者新視圖出現(xiàn)時(shí)改變焦點(diǎn)。視圖通過(guò)isFocusable()方法表明它們想獲取焦點(diǎn)的意愿。
要改變視圖是否可以接受焦點(diǎn),可以調(diào)用setFocusable()。在觸摸模式中,你可以通過(guò)isFocusableInTouchMode()查詢一個(gè)視圖是否允許接受焦點(diǎn)。你可以通過(guò)setFocusableInTouchMode()方法來(lái)改變它。焦點(diǎn)移動(dòng)基于一個(gè)在給定方向查找最近鄰居的算法。少有的情況是,缺省算法可能和開(kāi)發(fā)者的意愿行為不匹配。在這些情況下,你可以通過(guò)下面布局文件中的XML屬性提供顯式的重寫(xiě):nextFocusDown, nextFocusLeft, nextFocusRight, 和nextFocusUp。為失去焦點(diǎn)的視圖增加這些屬性之一。定義屬性值為擁有焦點(diǎn)的視圖的ID。比如:
<LinearLayout
android:orientation="vertical"
... >
<Button android:id="@+id/top"
android:nextFocusUp="@+id/bottom"
... />
<Button android:id="@+id/bottom"
android:nextFocusDown="@+id/top"
... />
</LinearLayout>
通常,在這個(gè)豎向布局中,從第一個(gè)按鈕向上瀏覽或者從第二個(gè)按鈕向下都不會(huì)移動(dòng)到其它地方?,F(xiàn)在這個(gè)頂部按鈕已經(jīng)定義了底部按鈕為nextFocusUp (反之亦然),瀏覽焦點(diǎn)將從上到下和從下到上循環(huán)移動(dòng)。
如果你希望在用戶界面中聲明一個(gè)可聚焦的視圖(通常不是這樣),可以在你的布局定義中,為這個(gè)視圖增加android:focusable XML 屬性。把它的值設(shè)置成true。你還可以通過(guò)android:focusableInTouchMode在觸摸模式下聲明一個(gè)視圖為可聚焦。
想請(qǐng)求一個(gè)接受焦點(diǎn)的特定視圖,調(diào)用requestFocus()。
要偵聽(tīng)焦點(diǎn)事件(當(dāng)一個(gè)視圖獲得或者失去焦點(diǎn)時(shí)被通知到),使用onFocusChange(),如上面事件偵聽(tīng)器Event Listeners所描述的那樣。
觸摸事件、點(diǎn)擊事件的區(qū)別
針對(duì)屏幕上的一個(gè)View控件,Android如何區(qū)分應(yīng)當(dāng)觸發(fā)onTouchEvent,還是onClick,亦或是onLongClick事件?
在Android中,一次用戶操作可以被不同的View按次序分別處理,并將完全響應(yīng)了用戶一次UI操作稱(chēng)之為消費(fèi)了該事件(consume),那么Android是按什么次序?qū)⑹录鬟f的呢?又在什么情況下判定為消費(fèi)了該事件?
搞清楚這些問(wèn)題對(duì)于編寫(xiě)出能正確響應(yīng)UI操作的代碼是很重要的,尤其當(dāng)屏幕上的不同View需要針對(duì)此次UI操作做出各種不同響應(yīng)的時(shí)候更是如此,一個(gè)典型例子就是用戶在桌面上放置了一個(gè)Widget,那么當(dāng)用戶針對(duì)widget做各種操作時(shí),桌面本身有的時(shí)候要對(duì)用戶的操作做出響應(yīng),有時(shí)忽略。只有搞清楚事件觸發(fā)和傳遞的機(jī)制才有可能保證在界面布局非常復(fù)雜的情況下,UI控件仍然能正確響應(yīng)用戶操作。
1. onTouchEvent
onTouchEvent中要處理的最常用的3個(gè)事件就是:ACTION_DOWN、ACTION_MOVE、ACTION_UP。
這三個(gè)事件標(biāo)識(shí)出了最基本的用戶觸摸屏幕的操作,含義也很清楚。雖然大家天天都在用它們,但是有一點(diǎn)請(qǐng)留意,ACTION_DOWN事件作為起始事件,它的重要性是要超過(guò)ACTION_MOVE和ACTION_UP的,如果發(fā)生了ACTION_MOVE或者ACTION_UP,那么一定曾經(jīng)發(fā)生了ACTION_DOWN。
從Android的源代碼中能看到基于這種不同重要性的理解而實(shí)現(xiàn)的一些交互機(jī)制,SDK中也有明確的提及,例如在ViewGroup的onInterceptTouchEvent方法中,如果在ACTION_DOWN事件中返回了true,那么后續(xù)的事件將直接發(fā)給onTouchEvent,而不是繼續(xù)發(fā)給onInterceptTouchEvent。
2. onClick、onLongClick與onTouchEvent
曾經(jīng)看過(guò)一篇帖子提到,如果在View中處理了onTouchEvent,那么就不用再處理onClick了,因?yàn)锳ndroid只會(huì)觸發(fā)其中一個(gè)方法。這個(gè)理解是不太正確的,針對(duì)某個(gè)view,用戶完成了一次觸碰操作,顯然從傳感器上得到的信號(hào)是手指按下和抬起兩個(gè)操作,我們可以理解為一次Click,也可以理解為發(fā)生了一次ACTION_DOWN和ACTION_UP,那么Android是如何理解和處理的呢?
在Android中,onClick、onLongClick的觸發(fā)是和ACTION_DOWN及ACTION_UP相關(guān)的,在時(shí)序上,如果我們?cè)谝粋€(gè)View中同時(shí)覆寫(xiě)了onClick、onLongClick及onTouchEvent的話,onTouchEvent是最先捕捉到ACTION_DOWN和ACTION_UP事件的,其次才可能觸發(fā)onClick或者onLongClick。主要的邏輯在View.java中的onTouchEvent方法中實(shí)現(xiàn)的:
case MotionEvent.ACTION_DOWN:
mPrivateFlags |= PRESSED;
refreshDrawableState();
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
postCheckForLongClick();
}
break;
case MotionEvent.ACTION_UP:
if ((mPrivateFlags & PRESSED) != 0) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (!mHasPerformedLongPress) {
if (mPendingCheckForLongPress != null) {
removeCallbacks(mPendingCheckForLongPress);
}
if (!focusTaken) {
performClick();
}
}
…
break;
可以看到,Click的觸發(fā)是在系統(tǒng)捕捉到ACTION_UP后發(fā)生并由performClick()執(zhí)行的,performClick里會(huì)調(diào)用先前注冊(cè)的監(jiān)聽(tīng)器的onClick()方法:
public boolean performClick() {
…
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
LongClick的觸發(fā)則是從ACTION_DOWN開(kāi)始,由postCheckForLongClick()方法完成:
private void postCheckForLongClick() {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.rememberWindowAttachCount();
postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout());
}
可以看到,在ACTION_DOWN事件被捕捉后,系統(tǒng)會(huì)開(kāi)始觸發(fā)一個(gè)postDelayed操作,delay的時(shí)間在Eclair2.1上為500ms,500ms后會(huì)觸發(fā)CheckForLongPress線程的執(zhí)行:
class CheckForLongPress implements Runnable {
…
public void run() {
if (isPressed() && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
if (performLongClick()) {
mHasPerformedLongPress = true;
}
}
}
…
}
如果各種條件都滿足,那么在CheckForLongPress中執(zhí)行performLongClick(),在這個(gè)方法中將調(diào)用onLongClick():
public boolean performLongClick() {
…
if (mOnLongClickListener != null) {
handled = mOnLongClickListener.onLongClick(View.this);
}
…
}
從實(shí)現(xiàn)中可以看到onClick()和onLongClick()方法是由ACTION_DOWN和ACTION_UP事件捕捉后根據(jù)各種情況最終確定是否觸發(fā)的,也就是說(shuō)如果我們?cè)谝粋€(gè)Activity或者View中同時(shí)監(jiān)聽(tīng)或者覆寫(xiě)了onClick(),onLongClick()和onTouchEvent()方法,并不意味著只會(huì)發(fā)生其中一種。
- 解析Android開(kāi)發(fā)中多點(diǎn)觸摸的實(shí)現(xiàn)方法
- android 多點(diǎn)觸摸圖片縮放的具體實(shí)現(xiàn)方法
- Android在Fragment中實(shí)現(xiàn)監(jiān)聽(tīng)觸摸事件
- Android修改源碼解決Alertdialog觸摸對(duì)話框邊緣消失的問(wèn)題
- Android 觸摸事件監(jiān)聽(tīng)(Activity層,ViewGroup層,View層)詳細(xì)介紹
- android命令行模擬輸入事件(文字、按鍵、觸摸等)
- Android中SurfaceView和view畫(huà)出觸摸軌跡
- Android實(shí)現(xiàn)手勢(shì)滑動(dòng)多點(diǎn)觸摸放大縮小圖片效果
- android中處理各種觸摸事件的方法淺談
- Android檢測(cè)手機(jī)多點(diǎn)觸摸點(diǎn)數(shù)的方法
相關(guān)文章
如何使用Spring工具類(lèi)動(dòng)態(tài)匹配url
這篇文章主要介紹了如何使用Spring工具類(lèi)動(dòng)態(tài)匹配url,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
關(guān)于Tomcat出現(xiàn)The origin server did not find a current represent
這篇文章主要介紹了關(guān)于Tomcat出現(xiàn)The origin server did not find a current representation for the target resourc...的問(wèn)題,感興趣的小伙伴們可以參考一下2020-08-08
java版簡(jiǎn)單的猜數(shù)字游戲?qū)嵗a
猜數(shù)字游戲是一款經(jīng)典的游戲,該游戲說(shuō)簡(jiǎn)單也很簡(jiǎn)單,說(shuō)不簡(jiǎn)單確實(shí)也很難,那么下面這篇文章主要給大家介紹了java版簡(jiǎn)單的猜數(shù)字游戲的相關(guān)資料,文中給出了詳細(xì)的實(shí)現(xiàn)分析和示例代碼供大家參考學(xué)習(xí),需要的朋友們下面來(lái)一起看看吧。2017-05-05
使用javaMail實(shí)現(xiàn)發(fā)送郵件
這篇文章主要為大家詳細(xì)介紹了使用javaMail實(shí)現(xiàn)發(fā)送郵件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08
如何寫(xiě)好一個(gè)Spring組件的實(shí)現(xiàn)步驟
這篇文章主要介紹了如何寫(xiě)好一個(gè)Spring組件的實(shí)現(xiàn)步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06

