Android(Java):Android 事件分发机制
转载请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/21696315),请尊重他人的辛勤劳动成果,谢谢!
今天这篇文章主要分析的是Android的事件分发机制,采用例子加源码的方式让大家深刻的理解Android事件分发的具体情况,虽然网上很多Android的事件分发的文章,有些还写的不错,但是我还是决定写这篇文章,用我自己的思维方式来帮助大家理解Android事件分发,Android事件分发到底有多重要呢?相信很多Android开发者都明白吧,这个我就不介绍了,我也写了很多篇文章里面涉及到Android的事件处理的问题,可能不理解Android事件分发的朋友会有点难理解吧,不过没关系,相信看了这篇文章的你会对Android事件分发有进一步的理解。我这篇文章分析的源码是Android 2.2的源码, 也许你会说,干嘛不去分析最新的源码呢?我这里要解释一下,Android 2.2的源码跟最新的源码在功能效果方面是一样的,只不过最新的源码相对于Android 2.2来说更加健壮一些, Android 2.2的事件处理的代码几乎都写在一个方法体里面,而最新的源码分了很多个方法写,如果用最新的源码调用方法会绕来绕去的,相信你看的也会晕,出于这个考虑,我就拿Android 2.2的源码来给大家分析。
ViewGroup的事件分发机制
我们用手指去触摸Android手机屏幕,就会产生一个触摸事件,但是这个触摸事件在底层是怎么分发的呢?这个我还真不知道,这里涉及到操作硬件(手机屏幕)方面的知识,也就是Linux内核方面的知识,我也没有了解过这方面的东西,所以我们可能就往上层来分析分析,我们知道Android中负责与用户交互,与用户操作紧密相关的四大组件之一是Activity, 所以我们有理由相信Activity中存在分发事件的方法,这个方法就是dispatchTouchEvent(),我们先看其源码吧
- publicbooleandispatchTouchEvent(MotionEventev){
- //如果是按下状态就调用onUserInteraction()方法,onUserInteraction()方法
- //是个空的方法,我们直接跳过这里看下面的实现
- if(ev.getAction()==MotionEvent.ACTION_DOWN){
- onUserInteraction();
- }
- if(getWindow().superDispatchTouchEvent(ev)){
- returntrue;
- }
- //getWindow().superDispatchTouchEvent(ev)返回false,这个事件就交给Activity
- //来处理,Activity的onTouchEvent()方法直接返回了false
- returnonTouchEvent(ev);
- }
[java] view plain copy print ?
- /**
- *Usedbycustomwindows,suchasDialog,topassthetouchscreenevent
- *furtherdowntheviewhierarchy.Applicationdevelopersshould
- *notneedtoimplementorcallthis.
- *
- */
- publicabstractbooleansuperDispatchTouchEvent(MotionEventevent);
- publicbooleansuperDispatchTouchEvent(KeyEventevent){
- returnmDecor.superDispatcTouchEvent(event);
- }
我们先新建一个项目,取名AndroidTouchEvent,然后直接用模拟器运行项目, MainActivity的布局文件为 [html] view plain copy print ?
- <RelativeLayoutxmlns: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"
- tools:context=".MainActivity">
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
- android:layout_centerVertical="true"
- android:text="@string/hello_world"/>
- </RelativeLayout>
我们看到最顶层就是PhoneWindow$DecorView,接着DecorView下面有一个LinearLayout, LinearLayout下面有两个FrameLayout
上面那个FrameLayout是用来显示标题栏的,这个Demo中是一个TextView,当然我们还可以定制我们的标题栏,利用getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE,R.layout.XXX); xxx就是我们自定义标题栏的布局XML文件
下面的FrameLayout是用来装载ContentView的,也就是我们在Activity中利用setContentView()方法设置的View,现在我们知道了,原来我们利用setContentView()设置Activity的View的外面还嵌套了这么多的东西
我们来理清下思路,Activity的最顶层窗体是PhoneWindow,而PhoneWindow的最顶层View是DecorView,接下来我们就看DecorView类的superDispatchTouchEvent()方法
[java] view plain copy print ?- publicbooleansuperDispatchTouchEvent(MotionEventevent){
- returnsuper.dispatchTouchEvent(event);
- }
- /**
- *{@inheritDoc}
- */
- @Override
- publicbooleandispatchTouchEvent(MotionEventev){
- finalintaction=ev.getAction();
- finalfloatxf=ev.getX();
- finalfloatyf=ev.getY();
- finalfloatscrolledXFloat=xf+mScrollX;
- finalfloatscrolledYFloat=yf+mScrollY;
- finalRectframe=mTempRect;
- //这个值默认是false,然后我们可以通过requestDisallowInterceptTouchEvent(booleandisallowIntercept)方法
- //来改变disallowIntercept的值
- booleandisallowIntercept=(mGroupFlags&FLAG_DISALLOW_INTERCEPT)!=0;
- //这里是ACTION_DOWN的处理逻辑
- if(action==MotionEvent.ACTION_DOWN){
- //清除mMotionTarget,每次ACTION_DOWN都很设置mMotionTarget为null
- if(mMotionTarget!=null){
- mMotionTarget=null;
- }
- //disallowIntercept默认是false,就看ViewGroup的onInterceptTouchEvent()方法
- if(disallowIntercept||!onInterceptTouchEvent(ev)){
- ev.setAction(MotionEvent.ACTION_DOWN);
- finalintscrolledXInt=(int)scrolledXFloat;
- finalintscrolledYInt=(int)scrolledYFloat;
- finalView[]children=mChildren;
- finalintcount=mChildrenCount;
- //遍历其子View
- for(inti=count-1;i>=0;i--){
- finalViewchild=children[i];
- //如果该子View是VISIBLE或者该子View正在执行动画,表示该View才
- //可以接受到Touch事件
- if((child.mViewFlags&VISIBILITY_MASK)==VISIBLE
- ||child.getAnimation()!=null){
- //获取子View的位置范围
- child.getHitRect(frame);
- //如Touch到屏幕上的点在该子View上面
- if(frame.contains(scrolledXInt,scrolledYInt)){
- //offsettheeventtotheview'scoordinatesystem
- finalfloatxc=scrolledXFloat-child.mLeft;
- finalfloatyc=scrolledYFloat-child.mTop;
- ev.setLocation(xc,yc);
- child.mPrivateFlags&=~CANCEL_NEXT_UP_EVENT;
- //调用该子View的dispatchTouchEvent()方法
- if(child.dispatchTouchEvent(ev)){
- //如果child.dispatchTouchEvent(ev)返回true表示
- //该事件被消费了,设置mMotionTarget为该子View
- mMotionTarget=child;
- //直接返回true
- returntrue;
- }
- //Theeventdidn'tgethandled,trythenextview.
- //Don'tresettheevent'slocation,it'snot
- //necessaryhere.
- }
- }
- }
- }
- }
- //判断是否为ACTION_UP或者ACTION_CANCEL
- booleanisUpOrCancel=(action==MotionEvent.ACTION_UP)||
- (action==MotionEvent.ACTION_CANCEL);
- if(isUpOrCancel){
- //如果是ACTION_UP或者ACTION_CANCEL,将disallowIntercept设置为默认的false
- //假如我们调用了requestDisallowInterceptTouchEvent()方法来设置disallowIntercept为true
- //当我们抬起手指或者取消Touch事件的时候要将disallowIntercept重置为false
- //所以说上面的disallowIntercept默认在我们每次ACTION_DOWN的时候都是false
- mGroupFlags&=~FLAG_DISALLOW_INTERCEPT;
- }
- //Theeventwasn'tanACTION_DOWN,dispatchittoourtargetif
- //wehaveone.
- finalViewtarget=mMotionTarget;
- //mMotionTarget为null意味着没有找到消费Touch事件的View,所以我们需要调用ViewGroup父类的
- //dispatchTouchEvent()方法,也就是View的dispatchTouchEvent()方法
- if(target==null){
- //Wedon'thaveatarget,thismeanswe'rehandlingthe
- //eventasaregularview.
- ev.setLocation(xf,yf);
- if((mPrivateFlags&CANCEL_NEXT_UP_EVENT)!=0){
- ev.setAction(MotionEvent.ACTION_CANCEL);
- mPrivateFlags&=~CANCEL_NEXT_UP_EVENT;
- }
- returnsuper.dispatchTouchEvent(ev);
- }
- //这个if里面的代码ACTION_DOWN不会执行,只有ACTION_MOVE
- //ACTION_UP才会走到这里,假如在ACTION_MOVE或者ACTION_UP拦截的
- //Touch事件,将ACTION_CANCEL派发给target,然后直接返回true
- //表示消费了此Touch事件
- if(!disallowIntercept&&onInterceptTouchEvent(ev)){
- finalfloatxc=scrolledXFloat-(float)target.mLeft;
- finalfloatyc=scrolledYFloat-(float)target.mTop;
- mPrivateFlags&=~CANCEL_NEXT_UP_EVENT;
- ev.setAction(MotionEvent.ACTION_CANCEL);
- ev.setLocation(xc,yc);
- if(!target.dispatchTouchEvent(ev)){
- }
- //clearthetarget
- mMotionTarget=null;
- //Don'tdispatchthiseventtoourownview,becausewealready
- //sawitwhenintercepting;wejustwanttogivethefollowing
- //eventtothenormalonTouchEvent().
- returntrue;
- }
- if(isUpOrCancel){
- mMotionTarget=null;
- }
- //finallyoffsettheeventtothetarget'scoordinatesystemand
- //dispatchtheevent.
- finalfloatxc=scrolledXFloat-(float)target.mLeft;
- finalfloatyc=scrolledYFloat-(float)target.mTop;
- ev.setLocation(xc,yc);
- if((target.mPrivateFlags&CANCEL_NEXT_UP_EVENT)!=0){
- ev.setAction(MotionEvent.ACTION_CANCEL);
- target.mPrivateFlags&=~CANCEL_NEXT_UP_EVENT;
- mMotionTarget=null;
- }
- //如果没有拦截ACTION_MOVE,ACTION_DOWN的话,直接将Touch事件派发给target
- returntarget.dispatchTouchEvent(ev);
- }
我们点击屏幕上面的TextView来看看Touch是如何分发的,先看看ACTION_DOWN
在DecorView这一层会直接调用ViewGroup的dispatchTouchEvent(), 先看18行,每次ACTION_DOWN都会将mMotionTarget设置为null,mMotionTarget是什么?我们先不管,继续看代码,走到25行, disallowIntercept默认为false,我们再看ViewGroup的onInterceptTouchEvent()方法
[java] view plain copy print ?- publicbooleanonInterceptTouchEvent(MotionEventev){
- returnfalse;
- }
LinearLayout调用dispatchTouchEvent()的逻辑跟DecorView是一样的,所以也是遍历LinearLayout的两个FrameLayout,判断Touch的是哪个FrameLayout,很明显是下面那个,调用下面那个FrameLayout的dispatchTouchEvent(), 所以LinearLayout的dispatchTouchEvent()卡在51也没继续下去
继续调用FrameLayout的dispatchTouchEvent()方法,和上面一样的逻辑,下面的FrameLayout也只有一个Child,就是RelativeLayout,FrameLayout的dispatchTouchEvent()继续卡在51行,先执行RelativeLayout的dispatchTouchEvent()方法
执行RelativeLayout的dispatchTouchEvent()方法逻辑还是一样的,循环遍历RelativeLayout里面的孩子,里面只有一个TextView, 所以这里就调用TextView的dispatchTouchEvent(), TextView并没有dispatchTouchEvent()这个方法,于是找TextView的父类View,在看View的dispatchTouchEvent()的方法之前,我们先理清下上面这些ViewGroup执行dispatchTouchEvent()的思路,我画了一张图帮大家理清下(这里没有画出onInterceptTouchEvent()方法)
上面的ViewGroup的Touch事件分发就告一段落先,因为这里要调用TextView(也就是View)的dispatchTouchEvent()方法,所以我们先分析View的dispatchTouchEvent()方法在将上面的继续下去
View的Touch事件分发机制
我们还是先看View的dispatchTouchEvent()方法的源码
[java] view plain copy print ?- publicbooleandispatchTouchEvent(MotionEventevent){
- if(mOnTouchListener!=null&&(mViewFlags&ENABLED_MASK)==ENABLED&&
- mOnTouchListener.onTouch(this,event)){
- returntrue;
- }
- returnonTouchEvent(event);
- }
第一个条件mOnTouchListener就是我们调用View的setTouchListener()方法设置的
第二个条件是判断View是否为enabled的, View一般都是enabled,除非你手动设置为disabled
第三个条件就是OnTouchListener接口的onTouch()方法的返回值了,如果调用了setTouchListener()设置OnTouchListener,并且onTouch()方法返回true,View的dispatchTouchEvent()方法就直接返回true,否则就执行View的onTouchEvent() 并返回View的onTouchEvent()的值
现在你了解了View的onTouchEvent()方法和onTouch()的关系了吧,为什么Android提供了处理Touch事件onTouchEvent()方法还要增加一个OnTouchListener接口呢?我觉得OnTouchListener接口是对处理Touch事件的屏蔽和扩展作用吧,屏蔽作用我就不举例介绍了,看上面的源码就知道了,我就说下扩展吧,比如我们要打印View的Touch的点的坐标,我们可以自定义一个View如下
- publicclassCustomViewextendsView{
- publicCustomView(Contextcontext,AttributeSetattrs){
- super(context,attrs);
- }
- publicCustomView(Contextcontext,AttributeSetattrs,intdefStyle){
- super(context,attrs,defStyle);
- }
- @Override
- publicbooleanonTouchEvent(MotionEventevent){
- Log.i("tag","X的坐标="+event.getX()+"Y的坐标="+event.getY());
- returnsuper.onTouchEvent(event);
- }
- }
- view.setOnTouchListener(newOnTouchListener(){
- @Override
- publicbooleanonTouch(Viewv,MotionEventevent){
- Log.i("tag","X的坐标="+event.getX()+"Y的坐标="+event.getY());
- returnv.onTouchEvent(event);
- }
- });
我们再看View的onTouchEvent()方法
[java] view plain copy print ?- publicbooleanonTouchEvent(MotionEventevent){
- finalintviewFlags=mViewFlags;
- if((viewFlags&ENABLED_MASK)==DISABLED){
- return(((viewFlags&CLICKABLE)==CLICKABLE||
- (viewFlags&LONG_CLICKABLE)==LONG_CLICKABLE));
- }
- //如果设置了Touch代理,就交给代理来处理,mTouchDelegate默认是null
- if(mTouchDelegate!=null){
- if(mTouchDelegate.onTouchEvent(event)){
- returntrue;
- }
- }
- //如果View是clickable或者longClickable的onTouchEvent就返回true,否则返回false
- if(((viewFlags&CLICKABLE)==CLICKABLE||
- (viewFlags&LONG_CLICKABLE)==LONG_CLICKABLE)){
- switch(event.getAction()){
- caseMotionEvent.ACTION_UP:
- booleanprepressed=(mPrivateFlags&PREPRESSED)!=0;
- if((mPrivateFlags&PRESSED)!=0||prepressed){
- booleanfocusTaken=false;
- if(isFocusable()&&isFocusableInTouchMode()&&!isFocused()){
- focusTaken=requestFocus();
- }
- if(!mHasPerformedLongPress){
- removeLongPressCallback();
- if(!focusTaken){
- if(mPerformClick==null){
- mPerformClick=newPerformClick();
- }
- if(!post(mPerformClick)){
- performClick();
- }
- }
- }
- if(mUnsetPressedState==null){
- mUnsetPressedState=newUnsetPressedState();
- }
- if(prepressed){
- mPrivateFlags|=PRESSED;
- refreshDrawableState();
- postDelayed(mUnsetPressedState,
- ViewConfiguration.getPressedStateDuration());
- }elseif(!post(mUnsetPressedState)){
- mUnsetPressedState.run();
- }
- removeTapCallback();
- }
- break;
- caseMotionEvent.ACTION_DOWN:
- if(mPendingCheckForTap==null){
- mPendingCheckForTap=newCheckForTap();
- }
- mPrivateFlags|=PREPRESSED;
- mHasPerformedLongPress=false;
- postDelayed(mPendingCheckForTap,ViewConfiguration.getTapTimeout());
- break;
- caseMotionEvent.ACTION_CANCEL:
- mPrivateFlags&=~PRESSED;
- refreshDrawableState();
- removeTapCallback();
- break;
- caseMotionEvent.ACTION_MOVE:
- finalintx=(int)event.getX();
- finalinty=(int)event.getY();
- //当手指在View上面滑动超过View的边界,
- intslop=mTouchSlop;
- if((x<0-slop)||(x>=getWidth()+slop)||
- (y<0-slop)||(y>=getHeight()+slop)){
- //Outsidebutton
- removeTapCallback();
- if((mPrivateFlags&PRESSED)!=0){
- removeLongPressCallback();
- mPrivateFlags&=~PRESSED;
- refreshDrawableState();
- }
- }
- break;
- }
- returntrue;
- }
- returnfalse;
- }
我们在看57行的ACTION_DOWN, 新建一个CheckForTap,我们看看CheckForTap是什么
[java] view plain copy print ?- privatefinalclassCheckForTapimplementsRunnable{
- publicvoidrun(){
- mPrivateFlags&=~PREPRESSED;
- mPrivateFlags|=PRESSED;
- refreshDrawableState();
- if((mViewFlags&LONG_CLICKABLE)==LONG_CLICKABLE){
- postCheckForLongClick(ViewConfiguration.getTapTimeout());
- }
- }
- }
由于考虑到文章篇幅的问题,我就不继续分析View的长按事件和点击事件了,在这里我直接得出结论吧
长按事件是在ACTION_DOWN中执行,点击事件是在ACTION_UP中执行,要想执行长按事件,这个View必须是longclickable的, 也许你会纳闷,一般的View不是longClickable为什么也会执行长按事件呢?我们要执行长按事件必须要调用setOnLongClickListener()设置OnLongClickListener接口,我们看看这个方法的源码
[java] view plain copy print ?- publicvoidsetOnLongClickListener(OnLongClickListenerl){
- if(!isLongClickable()){
- setLongClickable(true);
- }
- mOnLongClickListener=l;
- }
要想执行点击事件,这个View就必须要消费ACTION_DOWN和ACTION_MOVE事件,并且没有设置OnLongClickListener的情况下,如果设置了OnLongClickListener的情况下,需要onLongClick()返回false才能执行到onClick()方法,也许你又会纳闷,一般的View默认是不消费touch事件的,这不是和你上面说的相违背嘛,我们要向执行点击事件必须要调用setOnClickListener()来设置OnClickListener接口,我们看看这个方法的源码就知道了
[java] view plain copy print ?- publicvoidsetOnClickListener(OnClickListenerl){
- if(!isClickable()){
- setClickable(true);
- }
- mOnClickListener=l;
- }
对于View的Touch事件的分发机制算是告一段落了,从上面我们可以得出TextView的dispatchTouchEvent()的返回false的,即不消费Touch事件。我们就要往上看RelativeLayout的dispatchTouchEvent()方法的51行,由于TextView.dispatchTouchEvent()为false, 导致mMotionTarget没有被赋值,还是null, 继续往下走执行RelativeLayout的dispatchTouchEvent()方法, 来到第84行, 判断target是否为null,这个target就是mMotionTarget,满足条件,执行92行的 super.dispatchTouchEvent(ev)代码并返回, 这里调用的是RelativeLayout父类View的dispatchTouchEvent()方法,由于RelativeLayout没有设置onTouchListener, 所以这里直接调用RelativeLayout(其实就是View, 因为RelativeLayout没有重写onTouchEvent())的onTouchEvent()方法 由于RelativeLayout既不是clickable的也是longClickable的,所以其onTouchEvent()方法false, RelativeLayout的dispatchTouchEvent()也是返回false,这里就执行完了RelativeLayout的dispatchTouchEvent()方法
继续执行FrameLayout的dispatchTouchEvent()的第51行,由于RelativeLayout.dispatchTouchEvent()返回的是false, 跟上面的逻辑是一样的, 也是执行到92行的super.dispatchTouchEvent(ev)代码并返回,然后执行FrameLayout的onTouchEvent()方法,而FrameLayout的onTouchEvent()也是返回false,所以FrameLayout的dispatchTouchEvent()方法返回false,执行完毕FrameLayout的dispatchTouchEvent()方法
在上面的我就不分析了,大家自行分析一下,跟上面的逻辑是一样的,我直接画了个图来帮大家理解下(这里没有画出onInterceptTouchEvent()方法)
所以我们点击屏幕上面的TextView的事件分发流程是上图那个样子的,表示Activity的View都不消费ACTION_DOWN事件,所以就不能在触发ACTION_MOVE, ACTION_UP等事件了,具体是为什么?我还不太清楚,毕竟从Activity到TextView这一层是分析不出来的,估计是在底层实现的。
但如果将TextView换成Button,流程是不是还是这个样子呢?答案不是,我们来分析分析一下,如果是Button , Button是一个clickable的View,onTouchEvent()返回true, 表示他一直消费Touch事件,所以Button的dispatchTouchEvent()方法返回true, 回到RelativeLayout的dispatchTouchEvent()方法的51行,满足条件,进入到if方法体,设置mMotionTarget为Button,然后直接返回true, RelativeLayout的dispatchTouchEvent()方法执行完毕,不会调用到RelativeLayout的onTouchEvent()方法
然后到FrameLayout的dispatchTouchEvent()方法的51行,由于RelativeLayout.dispatchTouchEvent()返回true, 满足条件,进入if方法体,设置mMotionTarget为RelativeLayout,注意下,这里的mMotionTarget跟RelativeLayout的dispatchTouchEvent()方法的mMotionTarget不是同一个哦,因为他们是不同的方法中的,然后返回true
同理FrameLayout的dispatchTouchEvent()也是返回true,DecorView的dispatchTouchEvent()方法也返回true, 还是画一个流程图(这里没有画出onInterceptTouchEvent()方法)给大家理清下
从上面的流程图得出一个结论,Touch事件是从顶层的View一直往下分发到手指按下的最里面的View,如果这个View的onTouchEvent()返回false,即不消费Touch事件,这个Touch事件就会向上找父布局调用其父布局的onTouchEvent()处理,如果这个View返回true,表示消费了Touch事件,就不调用父布局的onTouchEvent()
接下来我们用一个自定义的ViewGroup来替换RelativeLayout,自定义ViewGroup代码如下
[java] view plain copy print ?- packagecom.example.androidtouchevent;
- importandroid.content.Context;
- importandroid.util.AttributeSet;
- importandroid.view.MotionEvent;
- importandroid.widget.RelativeLayout;
- publicclassCustomLayoutextendsRelativeLayout{
- publicCustomLayout(Contextcontext,AttributeSetattrs){
- super(context,attrs,0);
- }
- publicCustomLayout(Contextcontext,AttributeSetattrs,intdefStyle){
- super(context,attrs,defStyle);
- }
- @Override
- publicbooleanonTouchEvent(MotionEventevent){
- returnsuper.onTouchEvent(event);
- }
- @Override
- publicbooleanonInterceptTouchEvent(MotionEventev){
- returntrue;
- }
- }
我们就重写了onInterceptTouchEvent(),返回true, RelativeLayout默认是返回false, 然后再CustomLayout布局中加一个Button ,如下图
我们这次不从DecorView的dispatchTouchEvent()分析了,直接从CustomLayout的dispatchTouchEvent()分析
我们先看ACTION_DOWN 来到25行,由于我们重写了onInterceptTouchEvent()返回true, 所以不走这个if里面,直接往下看代码,来到84行, target为null,所以进入if方法里面,直接调用super.dispatchTouchEvent()方法, 也就是View的dispatchTouchEvent()方法,而在View的dispatchTouchEvent()方法中是直接调用View的onTouchEvent()方法,但是CustomLayout重写了onTouchEvent(),所以这里还是调用CustomLayout的onTouchEvent(), 这个方法返回false, 不消费Touch事件,所以不会在触发ACTION_MOVE,ACTION_UP等事件了,这里我再画一个流程图吧(含有onInterceptTouchEvent()方法的)
好了,就分析到这里吧,差不多分析完了,还有一种情况没有分析到,例如我将CustomLayout的代码改成下面的情形,Touch事件又是怎么分发的呢?我这里就不带大家分析了
[java] view plain copy print ?- packagecom.example.androidtouchevent;
- importandroid.content.Context;
- importandroid.util.AttributeSet;
- importandroid.view.MotionEvent;
- importandroid.widget.RelativeLayout;
- publicclassCustomLayoutextendsRelativeLayout{
- publicCustomLayout(Contextcontext,AttributeSetattrs){
- super(context,attrs,0);
- }
- publicCustomLayout(Contextcontext,AttributeSetattrs,intdefStyle){
- super(context,attrs,defStyle);
- }
- @Override
- publicbooleanonTouchEvent(MotionEventevent){
- returnsuper.onTouchEvent(event);
- }
- @Override
- publicbooleanonInterceptTouchEvent(MotionEventev){
- if(ev.getAction()==MotionEvent.ACTION_MOVE){
- returntrue;
- }
- returnsuper.onInterceptTouchEvent(ev);
- }
- }
1.Activity的最顶层Window是PhoneWindow,PhoneWindow的最顶层View是DecorView
2.一个clickable或者longClickable的View会永远消费Touch事件,不管他是enabled还是disabled的
3.View的长按事件是在ACTION_DOWN中执行,要想执行长按事件该View必须是longClickable的,并且不能产生ACTION_MOVE
4.View的点击事件是在ACTION_UP中执行,想要执行点击事件的前提是消费了ACTION_DOWN和ACTION_MOVE,并且没有设置OnLongClickListener的情况下,如设置了OnLongClickListener的情况,则必须使onLongClick()返回false
5.如果View设置了onTouchListener了,并且onTouch()方法返回true,则不执行View的onTouchEvent()方法,也表示View消费了Touch事件,返回false则继续执行onTouchEvent()
6.Touch事件是从最顶层的View一直分发到手指touch的最里层的View,如果最里层View消费了ACTION_DOWN事件(设置onTouchListener,并且onTouch()返回true 或者onTouchEvent()方法返回true)才会触发ACTION_MOVE,ACTION_UP的发生,如果某个ViewGroup拦截了Touch事件,则Touch事件交给ViewGroup处理
7.Touch事件的分发过程中,如果消费了ACTION_DOWN,而在分发ACTION_MOVE的时候,某个ViewGroup拦截了Touch事件,就像上面那个自定义CustomLayout,则会将ACTION_CANCEL分发给该ViewGroup下面的Touch到的View,然后将Touch事件交给ViewGroup处理,并返回true
更多相关文章
- Android 4.1源码编译找不到资源文件解决办法
- Android中EditText 设置 imeOptions 无效问题的解决方法
- Android有两种方法检测USB设备插入
- Android消息机制Handler源码分析
- Android系统源码基础知识讲解
- android 回车键事件编程
- Android优秀实例源码
- android android屏幕禁止休眠和锁屏的方法
- Android向服务器传接和接收数据的方法汇总