教育行業(yè)A股IPO第一股(股票代碼 003032)

全國咨詢/投訴熱線:400-618-4000

Android培訓(xùn)實戰(zhàn)教程之事件傳遞

更新時間:2015年12月28日14時02分 來源:傳智播客Android培訓(xùn)學(xué)院 瀏覽次數(shù):

Touch事件傳遞和攔截機制

關(guān)于touch事件的主要方法介紹
1. ViewGroup的子類(容器類,如:LinearLayout,RelativeLayout等)
    1. dispatchTouchEvent(MotionEvent ev)  事件的分發(fā)
    2. onInterceptTouchEvent(MotionEvent ev)  事件的攔截
    3. onTouchEvent(MotionEvent ev)  事件的處理
2. View的子類(非容器類,如:TextView,ImageView等)
    1. dispatchTouchEvent(MotionEvent ev)  事件的分發(fā)
    2. onTouchEvent(MotionEvent ev)  事件的處理

 區(qū)別:
    非容器類沒有onInterceptTouchEvent(MotionEvent ev)  事件的攔截



下面是測試案例的主要代碼

1. 打印日志的類

        public class LogUtils {
            public static void printLog(String tag,MotionEvent ev,String touchName) {
                switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    //按下
                    Log.i(tag, touchName + "按下");
                    break;
                case MotionEvent.ACTION_MOVE:
                    //按下
                    Log.d(tag, touchName + "移動");
                    break;
                case MotionEvent.ACTION_UP:
                    //按下
                    Log.e(tag, touchName + "松開");
                    break;
        
                default:
                    break;
                }
            }
        }

2. 第一個容器類(為了方便布局,本案例中用FrameLayout做基類,在事件攔截機制中和直接繼承ViewGroup類一致,因為FrameLayout沒有覆蓋ViewGroup的上面三個touch方法)
        
        public class ViewGroupOne extends FrameLayout {
        
            public ViewGroupOne(Context context, AttributeSet attrs) {
                super(context, attrs);
                // TODO Auto-generated constructor stub
            }
        
            public ViewGroupOne(Context context) {
                super(context);
                // TODO Auto-generated constructor stub
            }
            
            //分發(fā)事件
            @Override
            public boolean dispatchTouchEvent(MotionEvent ev) {
                // TODO Auto-generated method stub
                LogUtils.printLog("ViewGroupOne",ev, "dispatchTouchEvent");
                  
                return super.dispatchTouchEvent(ev);
            }
            
            //攔截事件
            @Override
            public boolean onInterceptTouchEvent(MotionEvent ev) {
                // TODO Auto-generated method stub
                LogUtils.printLog("ViewGroupOne",ev, "onInterceptTouchEvent");
                return super.onInterceptTouchEvent(ev);
            }
            
            //處理事件
            @Override
            public boolean onTouchEvent(MotionEvent ev) {
                // TODO Auto-generated method stub
                LogUtils.printLog("ViewGroupOne",ev, "onTouchEvent");
                return super.onTouchEvent(ev);
            }
        
        }

3. 第二個容器類
            
        public class ViewGroupTwo extends FrameLayout {
        
            public ViewGroupTwo(Context context, AttributeSet attrs) {
                super(context, attrs);
                // TODO Auto-generated constructor stub
            }
        
            public ViewGroupTwo(Context context) {
                super(context);
                // TODO Auto-generated constructor stub
            }
            
            //分發(fā)事件
            @Override
            public boolean dispatchTouchEvent(MotionEvent ev) {
                // TODO Auto-generated method stub
                LogUtils.printLog("ViewGroupTwo",ev, "dispatchTouchEvent");
                return super.dispatchTouchEvent(ev);
            }
            
            //攔截事件
            @Override
            public boolean onInterceptTouchEvent(MotionEvent ev) {
                // TODO Auto-generated method stub
                LogUtils.printLog("ViewGroupTwo",ev, "onInterceptTouchEvent");
                return super.onInterceptTouchEvent(ev);
            }
            
            //處理事件
            @Override
            public boolean onTouchEvent(MotionEvent event) {
                // TODO Auto-generated method stub
                LogUtils.printLog("ViewGroupTwo",event, "onTouchEvent");
                return super.onTouchEvent(event);
            }
        
        }

4. 非容器類

        public class MyView extends View {
        
            public MyView(Context context, AttributeSet attrs) {
                super(context, attrs);
                // TODO Auto-generated constructor stub
            }
        
            public MyView(Context context) {
                super(context);
                // TODO Auto-generated constructor stub
            }
        
            // 分發(fā)事件
            @Override
            public boolean dispatchTouchEvent(MotionEvent ev) {
                // TODO Auto-generated method stub
                LogUtils.printLog("MyView",ev, "dispatchTouchEvent");
                return super.dispatchTouchEvent(ev);
            }
        
            // 處理事件
            @Override
            public boolean onTouchEvent(MotionEvent event) {
                // TODO Auto-generated method stub
                LogUtils.printLog("MyView",event, "onTouchEvent");
                return super.onTouchEvent(event);
            }
        
        }

5. 主界面的布局

        <RelativeLayout xmlns: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"
            android:paddingBottom="@dimen/activity_vertical_margin"
            android:paddingLeft="@dimen/activity_horizontal_margin"
            android:paddingRight="@dimen/activity_horizontal_margin"
            android:paddingTop="@dimen/activity_vertical_margin"
            tools:context=".MainActivity" >
        
            <com.itheima13.eventdemo.ViewGroupOne
                android:layout_width="200dip"
                android:layout_height="200dip"
                android:background="#ff0000" >
        
                <com.itheima13.eventdemo.ViewGroupTwo
                    android:layout_width="150dip"
                    android:layout_height="150dip"
                    android:background="#00ff00" >
        
                    <com.itheima13.eventdemo.MyView
                        android:layout_width="100dip"
                        android:layout_height="100dip"
                        android:background="#0000ff" />
                </com.itheima13.eventdemo.ViewGroupTwo>
            </com.itheima13.eventdemo.ViewGroupOne>
        
        </RelativeLayout>

    預(yù)覽結(jié)果如下圖(紅色是ViewGroupOne,綠色是ViewGroupTwo,藍色是MyView):
buju.jpg



上面原始代碼都是默認的情況,運行后點擊藍色區(qū)域(按下,移動,松開)結(jié)果如圖:
touchmoren.jpg

總結(jié): 默認情況只相應(yīng)touch事件的down狀態(tài),而且事件默認傳遞到最內(nèi)層的子控件(MyView)onTouchEvent方法,由于默認MyView的onTouchEvent的方法沒有處理,所以事件依次回傳至父控件(ViewGroupTwo,ViewGroupOne)的onTouchEvent方法,由于每層都沒處理,所以事件回傳至最外層父控件后消失


由于情況復(fù)雜,下面以三個方法為核心介紹所有情況。(一個案例測試一種情況,每個案例的代碼改變都是在上面的基礎(chǔ)代碼上修改測試,也就是每個案例每種情況的代碼都是獨立的,只需參考上面的基本代碼和案例中每種情況修改的代碼即可。)

dispatchTouchEvent(MotionEvent ev)方法

* 有三種情況
    * 直接返回true 
        * 只修改ViewGroupTwo的dispatchTouchEvent方法測試
        
                //分發(fā)事件
                @Override
                public boolean dispatchTouchEvent(MotionEvent ev) {
                    // TODO Auto-generated method stub
                    LogUtils.printLog("ViewGroupTwo",ev, "dispatchTouchEvent");
                    return true;
                }

         運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
       dispatchtrue.jpg
        * 只修改MyView的dispatchTouchEvent方法測試
        
                @Override
                public boolean dispatchTouchEvent(MotionEvent ev) {
                    // TODO Auto-generated method stub
                    LogUtils.printLog("MyView",ev, "dispatchTouchEvent");
                    return true;//super.dispatchTouchEvent(ev);
                }

        運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
  dispatchtrue2.jpg
        
        結(jié)果分析:
        事件傳遞至子控件的dispatchTouchEvent終止。事件只有按順序傳遞,沒有回傳情況。并且事件的三種情況(down,move,up)都有處理回調(diào)
    
    * 直接返回false
        * 只修改ViewGroupTwo的dispatchTouchEvent方法測試
                
                @Override
                public boolean dispatchTouchEvent(MotionEvent ev) {
                    // TODO Auto-generated method stub
                    LogUtils.printLog("ViewGroupTwo",ev, "dispatchTouchEvent");
                    return false;//super.dispatchTouchEvent(ev);
                }

        運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
     diapatchfalse.jpg
        
        * 只修改MyView的dispatchTouchEvent方法測試
        
                // 分發(fā)事件
                @Override
                public boolean dispatchTouchEvent(MotionEvent ev) {
                    // TODO Auto-generated method stub
                    LogUtils.printLog("MyView",ev, "dispatchTouchEvent");
                    return false;//super.dispatchTouchEvent(ev);
                }

        運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
    dispatchfalse2.jpg
        
     結(jié)果分析:
     事件傳遞至當前控件的dispatchTouchEvent終止,并且事件回傳給父控件的onTouchEvent方法,此過程中只相應(yīng)事件的down狀態(tài)

    * 直接調(diào)用super.dispatchTouchEvent(ev);

    結(jié)果分析:默認情況,直接調(diào)用ViewGroup的dispatchTouchEvent(ev)方法,事件會傳遞給當前的控件的onInterceptTouchEvent(MotionEvent ev)方法。

onInterceptTouchEvent(MotionEvent ev) 方法
* 有三種情況
    * 直接返回true
        * 只修改ViewGroupTwo的onInterceptTouchEvent方法
        
                //攔截事件
                @Override
                public boolean onInterceptTouchEvent(MotionEvent ev) {
                    // TODO Auto-generated method stub
                    LogUtils.printLog("ViewGroupTwo",ev, "onInterceptTouchEvent");
                    return true;//super.onInterceptTouchEvent(ev);
                }

        運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
     intercepttrue.jpg

    結(jié)果分析:touch事件被當前控件攔截,不傳遞給子控件,并調(diào)用自己的onTouchEvent方法處理事件
    * 直接返回false
        * 只修改ViewGroupTwo的onInterceptTouchEvent方法
        
                //攔截事件
                @Override
                public boolean onInterceptTouchEvent(MotionEvent ev) {
                    // TODO Auto-generated method stub
                    LogUtils.printLog("ViewGroupTwo",ev, "onInterceptTouchEvent");
                    return false;//super.onInterceptTouchEvent(ev);
                }

        運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
interceptfalse.jpg

   結(jié)果分析:touch事件傳遞給子控件,由子控件處理事件

    * 直接調(diào)用super.onInterceptTouchEvent(ev) (調(diào)用ViewGroup的方法)
    

        運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
interceptmoren.jpg
    結(jié)果分析:touch事件傳遞給子控件,由子控件處理事件(與直接返回false類似)    

 onTouchEvent(MotionEvent ev) 方法
* 有三種情況
    * 直接返回true
        * 只修改ViewGroupTwo的onTouchEvent方法
            
                //處理事件
                @Override
                public boolean onTouchEvent(MotionEvent event) {
                    // TODO Auto-generated method stub
                    LogUtils.printLog("ViewGroupTwo",event, "onTouchEvent");
                    return true;//super.onTouchEvent(event);
                }

            運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
        touchtrue.jpg

    結(jié)果分析:down狀態(tài):當touch事件回傳給當前控件的時候,由于返回ture,所有事件回傳終止。針對move和up狀態(tài):事件到當前的onTouchEvent方法終止。也就是說子控件無法響應(yīng)事件。
    
    * 直接返回false
        * 只修改ViewGroupTwo的onTouchEvent方法
            
                //處理事件
                @Override
                public boolean onTouchEvent(MotionEvent event) {
                    // TODO Auto-generated method stub
                    LogUtils.printLog("ViewGroupTwo",event, "onTouchEvent");
                    return false;//super.onTouchEvent(event);
                }

            運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
          touchfalse.jpg

    >結(jié)果分析:事件回傳給父控件
    
    * 直接調(diào)用super.onTouchEvent(ev)
        * 只修改ViewGroupTwo的onTouchEvent方法
            
                //處理事件
                @Override
                public boolean onTouchEvent(MotionEvent event) {
                    // TODO Auto-generated method stub
                    LogUtils.printLog("ViewGroupTwo",event, "onTouchEvent");
                    return super.onTouchEvent(event);
                }

            運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
        touchmoren.jpg

    >結(jié)果分析:事件回傳給父控件

事件反向攔截

主要是反向攔截父控件的onInterceptTouchEvent

* 沒申請反向攔截父控件onInterceptTouchEvent的方法

    1. 只在ViewGroupTwo中覆蓋了onTouchEvent方法,并且事件消費
        
            //處理事件
            @Override
            public boolean onTouchEvent(MotionEvent event) {
                // TODO Auto-generated method stub
                LogUtils.printLog("ViewGroupTwo",event, "onTouchEvent");
                return true;//super.onTouchEvent(event);
            }

    運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
  fanlanjieqian.jpg
* 申請反向攔截父控件的onInterceptTouchEvent的方法
    1. 在上面的代碼基礎(chǔ)上,添加dispatchTouchEvent方法的覆蓋
    
            @Override
            public boolean dispatchTouchEvent(MotionEvent ev) {
                // TODO Auto-generated method stub
                requestDisallowInterceptTouchEvent(true);
                LogUtils.printLog("ViewGroupTwo",ev, "dispatchTouchEvent");
                return super.dispatchTouchEvent(ev);
            }
    注意多了requestDisallowInterceptTouchEvent(true);方法調(diào)用,該方法是攔截父控件的onInterceptTouchEvent方法的執(zhí)行(針對move,up狀態(tài))

    運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
fanlanjiehou.jpg

    >結(jié)果分析: 申請反向攔截后,父控件的onInterceptTouchEvent方法將不再執(zhí)行,案例如:智慧北京項目的輪播圖事件反攔截

    >源碼解釋:
        requestDisallowInterceptTouchEvent(true); 方法源碼如:

        public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        
                if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
                    // We're already in this state, assume our ancestors are too
                    return;
                }
        
                if (disallowIntercept) {
                    //主要是記錄攔截的標記 看dispatchTouchEvent方法的代碼片段
                    mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
                } else {
                    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
                }
        
                // Pass it up to our parent
                if (mParent != null) {
                    //繼續(xù)設(shè)置父控件的攔截模式
                    mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
                }
            }

         dispatchTouchEvent方法代碼片段:
            對上面設(shè)置的狀態(tài)值mGroupFlags判斷
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {//根據(jù)狀態(tài)值是否調(diào)用onInterceptTouchEvent
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        要點解析:
            看運行效果圖可知,down事件執(zhí)行的時候,已經(jīng)設(shè)置了反向攔截的狀態(tài),所有到move,up狀態(tài)執(zhí)行的時候,判斷就起到了攔截的效果

#### 事件傳遞和攔截流程圖(針對down事件的流程)
lanjietu.png


 實際操作會以下2點就可以了

1. 只需要做自己onTouchEvent方法的事件處理,并且返回值設(shè)置為true就可以滿足自己事件處理
2. 申請不讓父控件的onInterceptTouchEvent的方法執(zhí)行


本文版權(quán)歸傳智播客Android培訓(xùn)學(xué)院所有,歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明作者出處。謝謝!
作者:傳智播客Android培訓(xùn)學(xué)院
首發(fā):http://xamj520.com/android/
0 分享到:
和我們在線交談!