Android事件分發(fā)機(jī)制(下) View的事件處理
綜述
在上篇文章Android中的事件分發(fā)機(jī)制(上)——ViewGroup的事件分發(fā)中,對(duì)ViewGroup的事件分發(fā)進(jìn)行了詳細(xì)的分析。在文章的最后ViewGroup的dispatchTouchEvent方法調(diào)用dispatchTransformedTouchEvent方法成功將事件傳遞給ViewGroup的子View。并交由子View進(jìn)行處理。那么現(xiàn)在就來分析一下子View接收到事件以后是如何處理的。
View的事件處理
對(duì)于這里描述的View,它是ViewGroup的父類,并不包含任何的子元素。這也就意味著View無法再次向下對(duì)事件進(jìn)行分發(fā)操作,因此在View中并不存在onInterceptTouchEvent方法,也不會(huì)對(duì)事件做出攔截操作。它所做的事情就是對(duì)所接收的事件進(jìn)行處理。下面就開看一下View如何對(duì)事件進(jìn)行處理的。
既然ViewGroup將事件交由View的dispatchTouchEvent方。那么首先在這里就來看一下dispatchTouchEvent里面做了什么事情。
public boolean dispatchTouchEvent(MotionEvent event) {
......
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
......
return result;
}
在View的dispatchTouchEvent方法中對(duì)事件處理的核心部分體現(xiàn)在上述代碼中。onFilterTouchEventForSecurity方法表示當(dāng)前接收事件的view是否處于被遮蓋狀態(tài),View處于被遮蓋狀態(tài)表示當(dāng)前View不位于頂部,該view被其它View所覆蓋。如果當(dāng)前View被遮蓋,那么該View不會(huì)對(duì)事件進(jìn)行處理。
public interface OnTouchListener {
boolean onTouch(View v, MotionEvent event);
}
public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l;
}
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
在結(jié)合上述一段代碼可以看到,通過setOnTouchListener方法設(shè)置OnTouchListener以后,若是當(dāng)前View處于可用狀態(tài),那么條件li != null && li.mOnTouchListener !=null && (mViewFlags & ENABLED_MASK) == ENABLED必然為true。這時(shí)候程序便會(huì)回調(diào)OnTouchListener中的onTouch方法,若是在onTouch方法中返回true,便不會(huì)在執(zhí)行View的onTouchEvent方法。從這里我們能夠看到,一旦設(shè)置了OnTouchListener,那么OnTouchListener的優(yōu)先級(jí)要高于onTouchEvent。
有一點(diǎn)需要注意,在程序中設(shè)置了OnTouchListener以后,對(duì)于OnTouchListener中的onTouch的返回值并不代表View中的dispatchTouchEvent方法所返回的值。在onTouch方法返回true的時(shí)候,表示事件成功被當(dāng)前View所消耗,這時(shí)候result被置為true并且不再執(zhí)行onTouchEvent,所以dispatchTouchEvent也就返回true。可是一旦在onTouch方法中返回false。這時(shí)候便會(huì)調(diào)用onTouchEvent方法,如果事件被onTouchEvent成功處理,并返回true,result依然會(huì)被置為true,dispatchTouchEvent自然而然的也就返回true。
下面在進(jìn)入View的onTouchEvent方法一探究竟。對(duì)于onTouchEvent方法里的內(nèi)容比較多,在這里分段查看。
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
在這里可以看出對(duì)于不可用的View,如果他們的一些點(diǎn)擊事件可用的話,依然能夠成功的消費(fèi)事件,只是它不會(huì)為該事件做出響應(yīng)。對(duì)于View的這些點(diǎn)擊之間默認(rèn)為不可用,但是對(duì)于不同的的View他們的默認(rèn)值不一樣。例如在ImageView中點(diǎn)擊事件依然為不可用,但是在Button中點(diǎn)擊事件為可用。當(dāng)然如果手動(dòng)為它們?cè)O(shè)置監(jiān)聽事件,那么這些監(jiān)聽事件都將會(huì)自動(dòng)被設(shè)為可用狀態(tài)。從如下源碼中可以看出。
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
public void setOnLongClickListener(@Nullable OnLongClickListener l) {
if (!isLongClickable()) {
setLongClickable(true);
}
getListenerInfo().mOnLongClickListener = l;
}
public void setOnContextClickListener(@Nullable OnContextClickListener l) {
if (!isContextClickable()) {
setContextClickable(true);
}
getListenerInfo().mOnContextClickListener = l;
}
下面接著看OnTouchEvent里面代碼。
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
這里首先判斷是否對(duì)事件設(shè)置了代理,如果對(duì)事件設(shè)置了代理,便會(huì)執(zhí)行TouchDelegate的onTouchEvent方法。mTouchDelegate默認(rèn)值為null,可以通過View的setTouchDelegate方法來設(shè)置代理。對(duì)于TouchDelegate在后續(xù)的文章中在進(jìn)行詳細(xì)分析,在這里就不在過多描述。
最后看一下View是如何處理事件的,對(duì)于接收的事件整個(gè)處理過程比較復(fù)雜,在這里就從宏觀上來整體看一下它的處理機(jī)制。
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
......
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
......
break;
......
}
return true;
}
如果View的點(diǎn)擊事件處于可用狀態(tài)的話,便會(huì)對(duì)于這些事件進(jìn)行處理,并且返回true。當(dāng)一個(gè)事件序列完成以后調(diào)用performClick方法,下面看下這個(gè)performClick方法。
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
從上面代碼中可以看出,如果我們?cè)O(shè)置了OnClickListener,便會(huì)調(diào)用它的onClick方法。從這一路下來我們可以看出對(duì)于View的onClick事件,在最后才會(huì)被調(diào)用,可見onClick的優(yōu)先級(jí)是最低的。
總結(jié)
在這里對(duì)View的事件處理做一下總結(jié)。在ViewGroup將事件分發(fā)到View以后。在View里面通過OnTouchListener的onTouch和View中的onTouchEvent這兩個(gè)方法對(duì)事件進(jìn)行處理。對(duì)于onTouch方法是View提供給用戶的,方便用戶自己處理觸摸事件,而onTouchEvent是Android系統(tǒng)自己實(shí)現(xiàn)的接口。若是用戶設(shè)置了OnTouchListener,Android系統(tǒng)會(huì)首先調(diào)用OnTouchListener的onTouch方法。若是在onTouch方法中返回true,就不在執(zhí)行View的onTouchEvent方法。只有在onTouch中返回了false才會(huì)執(zhí)行onTouchEvent。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
android實(shí)現(xiàn)一個(gè)圖片驗(yàn)證碼倒計(jì)時(shí)功能
本文通過實(shí)例代碼給大家介紹了android實(shí)現(xiàn)一個(gè)圖片驗(yàn)證碼倒計(jì)時(shí)功能,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2017-11-11
Android 簡(jiǎn)單的照相機(jī)程序的實(shí)例代碼
終于經(jīng)過多次找錯(cuò),修改把一個(gè)簡(jiǎn)單的照相機(jī)程序完成了,照相類代碼如下:2013-05-05
實(shí)例講解Android Fragment的兩種使用方法
今天小編就為大家分享一篇關(guān)于實(shí)例講解Android Fragment的兩種使用方法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-03-03
Android App后臺(tái)震動(dòng)的實(shí)現(xiàn)步驟詳解
這篇文章主要為大家介紹了Android App后臺(tái)震動(dòng)的實(shí)現(xiàn)步驟詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
Android之用PopupWindow實(shí)現(xiàn)彈出菜單的方法詳解
本篇文章是對(duì)在Android中,用PopupWindow實(shí)現(xiàn)彈出菜單的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06
Android EditText輸入框?qū)崿F(xiàn)下拉且保存最近5個(gè)歷史記錄思路詳解
今天給大家介紹Android EditText輸入框?qū)崿F(xiàn)下拉且保存最近5個(gè)歷史記錄功能,android實(shí)現(xiàn)文本框下拉利用sharedpreferences來保存每次app啟動(dòng)和關(guān)閉時(shí)已經(jīng)填寫的數(shù)值,具體代碼跟隨小編一起看看吧2021-07-07
Cocos2d-x入門教程(詳細(xì)的實(shí)例和講解)
這篇文章主要介紹了Cocos2d-x入門教程,包括詳細(xì)的實(shí)例、講解以及實(shí)現(xiàn)過程,需要的朋友可以參考下2014-04-04

