玩轉Android UI事件 頂 原

      (本文最早於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事件模擬 {}




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章