教育行业A股IPO第一股(股票代码 003032)

全国咨询/投诉热线:400-618-4000

Android培训实战教程之事件传递

更新时间:2015年12月28日14时02分 来源:传智播客Android培训学院 浏览次数:

Touch事件传递和拦截机制

关于touch事件的主要方法介绍
1. ViewGroup的子类(容器类,如:LinearLayout,RelativeLayout等)
    1. dispatchTouchEvent(MotionEvent ev)  事件的分发
    2. onInterceptTouchEvent(MotionEvent ev)  事件的拦截
    3. onTouchEvent(MotionEvent ev)  事件的处理
2. View的子类(非容器类,如:TextView,ImageView等)
    1. dispatchTouchEvent(MotionEvent ev)  事件的分发
    2. onTouchEvent(MotionEvent ev)  事件的处理

 区别:
    非容器类没有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
            }
            
            //分发事件
            @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
            }
            
            //分发事件
            @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
            }
        
            // 分发事件
            @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>

    预览结果如下图(红色是ViewGroupOne,绿色是ViewGroupTwo,蓝色是MyView):
buju.jpg



上面原始代码都是默认的情况,运行后点击蓝色区域(按下,移动,松开)结果如图:
touchmoren.jpg

总结: 默认情况只相应touch事件的down状态,而且事件默认传递到最内层的子控件(MyView)onTouchEvent方法,由于默认MyView的onTouchEvent的方法没有处理,所以事件依次回传至父控件(ViewGroupTwo,ViewGroupOne)的onTouchEvent方法,由于每层都没处理,所以事件回传至最外层父控件后消失


由于情况复杂,下面以三个方法为核心介绍所有情况。(一个案例测试一种情况,每个案例的代码改变都是在上面的基础代码上修改测试,也就是每个案例每种情况的代码都是独立的,只需参考上面的基本代码和案例中每种情况修改的代码即可。)

dispatchTouchEvent(MotionEvent ev)方法

* 有三种情况
    * 直接返回true 
        * 只修改ViewGroupTwo的dispatchTouchEvent方法测试
        
                //分发事件
                @Override
                public boolean dispatchTouchEvent(MotionEvent ev) {
                    // TODO Auto-generated method stub
                    LogUtils.printLog("ViewGroupTwo",ev, "dispatchTouchEvent");
                    return true;
                }

         运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
       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);
                }

        运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
  dispatchtrue2.jpg
        
        结果分析:
        事件传递至子控件的dispatchTouchEvent终止。事件只有按顺序传递,没有回传情况。并且事件的三种情况(down,move,up)都有处理回调
    
    * 直接返回false
        * 只修改ViewGroupTwo的dispatchTouchEvent方法测试
                
                @Override
                public boolean dispatchTouchEvent(MotionEvent ev) {
                    // TODO Auto-generated method stub
                    LogUtils.printLog("ViewGroupTwo",ev, "dispatchTouchEvent");
                    return false;//super.dispatchTouchEvent(ev);
                }

        运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
     diapatchfalse.jpg
        
        * 只修改MyView的dispatchTouchEvent方法测试
        
                // 分发事件
                @Override
                public boolean dispatchTouchEvent(MotionEvent ev) {
                    // TODO Auto-generated method stub
                    LogUtils.printLog("MyView",ev, "dispatchTouchEvent");
                    return false;//super.dispatchTouchEvent(ev);
                }

        运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
    dispatchfalse2.jpg
        
     结果分析:
     事件传递至当前控件的dispatchTouchEvent终止,并且事件回传给父控件的onTouchEvent方法,此过程中只相应事件的down状态

    * 直接调用super.dispatchTouchEvent(ev);

    结果分析:默认情况,直接调用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);
                }

        运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
     intercepttrue.jpg

    结果分析:touch事件被当前控件拦截,不传递给子控件,并调用自己的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);
                }

        运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
interceptfalse.jpg

   结果分析:touch事件传递给子控件,由子控件处理事件

    * 直接调用super.onInterceptTouchEvent(ev) (调用ViewGroup的方法)
    

        运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
interceptmoren.jpg
    结果分析: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);
                }

            运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
        touchtrue.jpg

    结果分析:down状态:当touch事件回传给当前控件的时候,由于返回ture,所有事件回传终止。针对move和up状态:事件到当前的onTouchEvent方法终止。也就是说子控件无法响应事件。
    
    * 直接返回false
        * 只修改ViewGroupTwo的onTouchEvent方法
            
                //处理事件
                @Override
                public boolean onTouchEvent(MotionEvent event) {
                    // TODO Auto-generated method stub
                    LogUtils.printLog("ViewGroupTwo",event, "onTouchEvent");
                    return false;//super.onTouchEvent(event);
                }

            运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
          touchfalse.jpg

    >结果分析:事件回传给父控件
    
    * 直接调用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);
                }

            运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
        touchmoren.jpg

    >结果分析:事件回传给父控件

事件反向拦截

主要是反向拦截父控件的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);
            }

    运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
  fanlanjieqian.jpg
* 申请反向拦截父控件的onInterceptTouchEvent的方法
    1. 在上面的代码基础上,添加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);方法调用,该方法是拦截父控件的onInterceptTouchEvent方法的执行(针对move,up状态)

    运行后对蓝色区域做了按下,移动,松开的操作,运行结果如下图:
fanlanjiehou.jpg

    >结果分析: 申请反向拦截后,父控件的onInterceptTouchEvent方法将不再执行,案例如:智慧北京项目的轮播图事件反拦截

    >源码解释:
        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) {
                    //继续设置父控件的拦截模式
                    mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
                }
            }

         dispatchTouchEvent方法代码片段:
            对上面设置的状态值mGroupFlags判断
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {//根据状态值是否调用onInterceptTouchEvent
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        要点解析:
            看运行效果图可知,down事件执行的时候,已经设置了反向拦截的状态,所有到move,up状态执行的时候,判断就起到了拦截的效果

#### 事件传递和拦截流程图(针对down事件的流程)
lanjietu.png


 实际操作会以下2点就可以了

1. 只需要做自己onTouchEvent方法的事件处理,并且返回值设置为true就可以满足自己事件处理
2. 申请不让父控件的onInterceptTouchEvent的方法执行


本文版权归传智播客Android培训学院所有,欢迎转载,转载请注明作者出处。谢谢!
作者:传智播客Android培训学院
首发:http://www.itcast.cn/android/
0 分享到:
和我们在线交谈!