(本文最早於2012-3-15 17:12日發表於QQ空間 進入我的空間並查找標題“玩轉Android UI事件”)
研究了下 Android 可視組件的事件處理機制,本想用文字來闡述,但是發現太複雜,文字不太適合用來表達邏輯,遂改用程序代碼來表述。讀完本程序,你將會對 Android UI 事件處理機制有一個全新的認識。若能充分利用事件傳遞的這些特性,你寫自定義組件就可以隨心所欲,遊刃有餘了。
package com.test;
import android.view.MotionEvent;
import android.view.View;
/**
* Android可視組件的觸摸事件傳遞是通過調用dispatchTouchEvent(MotionEvent event)
* 或dispatchKeyEvent(KeyEvent event)來實現事件的捕獲、目標和冒泡三個階段,調用是從最父層的組件開始,如Activity。
* 不過這裏僅闡述了觸摸點擊事件,至於鍵盤事件則比較簡單,暫不贅述。
*
* 這是一個對Android事件機制的模擬,讀完本程序,你便明白了事件的處理過程。這裏包含了幾種不同組件中事件的傳遞和處理方式。
* 這裏作了一個融合,而在實際API類當中,根據不同的類有不同的實現方法,具體請看源碼,不過我沒來得及看。
*
* 注意不要試圖運行本程序,本程序只是表達了執行邏輯,個人認爲用程序比用文字的方式表達的更加清晰。
* 程序是最好的語言嘛,對於程序員來說。
*/
public class Android事件模擬 {
/**是否應該向子組件傳遞事件**/
boolean childConsume = false;
/**本組件是Activity**/
boolean isActivity = true | false;
/**本組件是ViewGroup**/
boolean isViewGroup = true | false;
/**本組件是View而不是ViewGroup**/
boolean isView = true | false;
/**子組件是View(包括ViewGroup)**/
boolean childIsView = true | false;
/**有無子組件或者事件源是否在直接或間接子組件上**/
boolean hasChild = true | false;
/**模擬子視圖組件**/
Child child = new Child();
/**外部監聽器**/
View.OnTouchListener touchListener = null;
View.OnLongClickListener longClickListener = null;
View.OnClickListener clickListener = null;
/**本類對象,非Activity**/
View view = null;
/**默認狀態下觸摸事件的邏輯處理方法,當前對象層級視圖組件觸摸事件處理的入口**/
public boolean dispatchTouchEvent(MotionEvent event) {
/**本方法的返回值**/
boolean b = false;
if(isActivity) {//如果本對象是Activity。//完全由onTouchEvent()決定本方法的返回值
if(event.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
childConsume = hasChild ? true : false;
if(childConsume) {
if(child.dispatchTouchEvent(event)) {//注意子組件的dispatchTouchEvent(event)處理過程跟本方法相同
childConsume = true;
b = true;
}else {
childConsume = false;
b = onTouchEvent(event);
}
}else {
b = onTouchEvent(event);
}
}else if(childConsume) {//即使後續事件中child.dispatchTouchEvent(event)返回false,也繼續傳遞
if(child.dispatchTouchEvent(event)) {
b = true;
}else {
b = onTouchEvent(event);
}
}else {
b = onTouchEvent(event);
}
}else if(isViewGroup) {//如果本對象是ViewGroup
boolean intercept = false;
if(event.getAction() == MotionEvent.ACTION_DOWN) {
intercept = onInterceptTouchEvent(event);//不管有沒有子組件,本方法都將執行
childConsume = (hasChild && ! intercept) ? true : false;
if(childConsume) {
if(child.dispatchTouchEvent(event)) {
childConsume = true;
b = true;
}else {
childConsume = false;
if(touchListener != null) {
b = touchListener.onTouch(view, event);//調用外部監聽器
}
if(!b) b = onTouchEvent(event);
}
}else {
childConsume = false;
if(touchListener != null) {
b = touchListener.onTouch(view, event);
}
if(!b) b = onTouchEvent(event);
}
}else if(event.getAction() == MotionEvent.ACTION_MOVE) {
if(childConsume) {
intercept = onInterceptTouchEvent(event);
if(intercept) {
childConsume = false;
event.setAction(MotionEvent.ACTION_CANCEL);//重點,後續事件被攔截,將變爲取消事件並繼續傳遞
child.dispatchTouchEvent(event);
b = true;//此時不論child.dispatchTouchEvent(event)返回什麼值,本方法都直接返回true
}else {
b = child.dispatchTouchEvent(event);//即使返回false也直接返回,區別於Activity
}
}else {
if(touchListener != null) {
b = touchListener.onTouch(view, event);
}
if(!b) b = onTouchEvent(event);
}
}else if(event.getAction() == MotionEvent.ACTION_UP) {
if(childConsume) {
intercept = onInterceptTouchEvent(event);
if(intercept) {
childConsume = false;
event.setAction(MotionEvent.ACTION_CANCEL);//重點,後續事件被攔截,將變爲取消事件並繼續傳遞
child.dispatchTouchEvent(event);
b = true;//此時不管child.dispatchTouchEvent(event)返回什麼值,本方法都直接返回true
}else {
b = child.dispatchTouchEvent(event);//即時返回false也直接返回,區別於Activity
}
}else {
if(touchListener != null) {
b = touchListener.onTouch(view, event);
}
if(!b) b = onTouchEvent(event);//注意onClick事件是在onTouchEvent()內部處理的
}
}
}else if(isView) {//如果本對象是不能添加子組件的View,如:Button、EditText
if(touchListener != null) {
b = touchListener.onTouch(view, event);
}
if(!b) b = onTouchEvent(event);
}
return b;
}
/**Activity類特有,重寫它,可用於在事件捕獲階段,事件到達本層容器而未進行任何其他處理之前做些事情**/
public void onUserInteraction() {}
/**ViewGroup類特有,其返回值表示是否攔截事件,以決定事件是否繼續傳遞**/
public boolean onInterceptTouchEvent(MotionEvent event) {
return true | false;
}
/**Activity默認返回false,View默認返回true。返回值會決定後續的事件傳遞情況。可以根據需要自定義返回值。**/
public boolean onTouchEvent(MotionEvent event) {
/*
* 當Activity收到MotionEvent.ACTION_DOWN事件的冒泡返回,則會啓動一個LongClick計時線程,之後若View組件樹中
* 有任何一個組件在延時之內收到MotionEvent.ACTION_UP或MotionEvent.ACTION_CANCEL事件,則計時終止。
* 因此LongClick事件在兩種狀況下觸發:
*
* a、若父級Activity收到child.dispatchTouchEvent(event)的返回值爲false時;
* b、長按某組件,MotionEvent.ACTION_UP或MotionEvent.ACTION_CANCEL事件發生在LongClick之後時。
*
* LongClick事件觸發時,從葉子層組件向根層組件依次調用View.OnLongClickListener.onLongClick(View v)方法。
* 該方法優先於組件本身默認的longClick相關處理方法,若返回值爲false,會使組件繼續調用默認的處理方法,
* 否則不執行默認的處理方法並給View.OnClickListener.onClick(View v)一個不要執行的標識。
* 但返回值不會影響上層組件對該方法的調用。
*/
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
/* Post本對象到LongClick計時線程 */
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
/* 終止LongClick計時線程 */
/* 執行View.OnClickListener.onClick(View v)。若View.OnLongClickListener.onLongClick(View v)先執行,
* 則根據其返回值決定是否執行View.OnClickListener.onClick(View v) */
break;
}
return true | false;
}
/**添加外部監聽器。View類特有(包括ViewGroup),注意無論調用多少次本方法,只有最後一個監聽器起作用,即覆蓋**/
public void setOnTouchListener(View.OnTouchListener l) {
touchListener = l;
}
/**添加外部監聽器。View類特有(包括ViewGroup),注意無論調用多少次本方法,只有最後一個監聽器起作用,即覆蓋**/
public void setOnClickListener(View.OnClickListener l) {
clickListener = l;
}
/**添加外部監聽器。View類特有(包括ViewGroup),注意無論調用多少次本方法,只有最後一個監聽器起作用,即覆蓋**/
public void setOnLongClickListener(View.OnLongClickListener l) {
longClickListener = l;
}
}
class Child extends Android事件模擬 {}