android中touch事件的传递在android系统中是非常重要的,搞清楚这个:
1.可以自定义写出非常多优美的自定义控件---Android控件之搓以及国内产品经理之懒让国内android苦逼程序员都深有体会。
2.理解了手势的传递也可以在处理多层View嵌套引发的手势冲突问题时快速的找到问题的所在,进行bug的修复
3.手势传递运用了很多设计理念,包括著名的观察者模式,学习google大神的代码也可以提高自身的代码修养。
因此虽然是个菜逼,我们还是一起来研究研究view上的手势传递机制吧。
先从最简单的Button来研究,并且是点击button不考虑其它view的拦截。也就是事件从activity顺利的传递到了button这一层以及事件在button层处理的分析。
首先先上测试demo代码:
布局文件:activity_main.xml
<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" > <Button android:id="@+id/bt" android:layout_alignParentLeft="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="button" /> </RelativeLayout>
MainActivity.java
public class MainActivity extends Activity { private static final String TAG = "MainActivity"; private Button btn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn = (Button) findViewById(R.id.bt); btn.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { showLog("button ontouch...event is.."+event.getAction()); return false; } }); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { showLog("button onclick"); } }); } @Override public void onUserInteraction() { showLog("onUserInteraction"); } @Override public boolean onTouchEvent(MotionEvent event) { showLog("onTouchEvent...ev is "+event.getAction()); return super.onTouchEvent(event); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { showLog("dispatchTouchEvent...ev is "+ev.getAction()); return super.dispatchTouchEvent(ev); } public void showLog(String msg){ Log.d(TAG, msg); } }
btn.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { showLog("button ontouch...event is.."+event.getAction()); return false;//返回false } });然后程序运行时点击button,查看log
01-01 18:33:45.210: D/MainActivity(16155): dispatchTouchEvent...ev is 0
01-01 18:33:45.210: D/MainActivity(16155): onUserInteraction
01-01 18:33:45.210: D/MainActivity(16155): button ontouch...event is..0
01-01 18:33:45.310: D/MainActivity(16155): dispatchTouchEvent...ev is 1
01-01 18:33:45.310: D/MainActivity(16155): button ontouch...event is..1
01-01 18:33:45.320: D/MainActivity(16155): button onclick
再将该方法中返回true MainActivity.java
btn.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { showLog("button ontouch...event is.."+event.getAction()); return true;//返回true } });运行并查看log
01-01 18:32:42.560: D/MainActivity(15713): dispatchTouchEvent...ev is 0
01-01 18:32:42.560: D/MainActivity(15713): onUserInteraction
01-01 18:32:42.560: D/MainActivity(15713): button ontouch...event is..0
01-01 18:32:42.660: D/MainActivity(15713): dispatchTouchEvent...ev is 1
01-01 18:32:42.660: D/MainActivity(15713): button ontouch...event is..1
通过Log我们可以得出如下几个结论:
1.不论如何,点击事件一定是先执行了dispatchTouchEvent的,
并且按下执行一次,抬起执行一次
2.如果在onTouchListener()方法中返回false,则会执行button的onClick()方法;
如果返回为true,则不会执行
3.0表示按下,1表示抬起
4.执行的顺序是
按下:dispatchTouchEvent-->onUserInteraction-->button.setOnTouch()
抬起:dispatchTouchEvent-->button.setOnTouch()
----可能会有onClick,并且抬起不执行onUserInteraction
5.都没有执行复写的onTouchEvent()方法。
了解了结论后我们就需要去源码中寻找为什么会这么执行了。
基于上述是无论怎么点击一定会先执行dispatchTouchEvent方法,
因此就要去找寻父类中的dispatchTouchEvent方法了。
当前类父类是activity.java,因此跳转到这个界面
Activity.java
/** * Called to process touch screen events. You can override this to * intercept all touch screen events before they are dispatched to the * window. Be sure to call this implementation for touch screen events * that should be handled normally. * * @param ev The touch screen event. * * @return boolean Return true if this event was consumed. */ public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) {//所有的touch事件第一步都是按下屏幕,即第一下ev.getAction() == MotionEvent.ACTION_DOWN onUserInteraction(); //所以会走onUserInteraction()方法。 } //在根activity中寻找这个方法 public void onUserInteraction() { //什么都没有,因此默认这一步是不做任何处理的,当然我们也可以在子activity中复写这个方法进行一些处理 }
<span style="white-space:pre"> </span><pre name="code" class="java"><pre name="code" class="java">//然后开始执行这一步,这一步就会调用到当前window最基类View的dispatchTouchEvent()方法
if (getWindow().superDispatchTouchEvent(ev)) {//getWindow().superDispatchTouchEvent(ev)这个方法就会进行一大串复杂的操作,如果最后返回值为true,则activity的dispatchTouchEvent就返回ture//事件就被消费了,就不会再执行activity的onTouchEvent了,这也就是为什么并没有在点击button时执行onTouchEvent,因为上面那个方法一定返回true了,//也就是事件被拦截了 return true; } return onTouchEvent(ev); } 1.这个activity.java里的方法何时被调用?
看上面代码我们可以知道为什么都没有执行activity复写的onTouchEvent方法了,
因为在上一句代码已经被消费了,就不会再走下面的了
下面我们主要来分析getWindow().superDispatchTouchEvent(ev)这个方法
这个方法是走viewRoot中的DispatchTouchEvent(ev),一般activity中的布局的根view都是ViewGroup,所以会走ViewGroup中的DispatchTouchEvent(ev)方法
如果没有被拦截会逐层传递,直到传递到View的DispatchTouchEvent(ev)方法-->如果点击在了view上
但是这整个流程太麻烦,我们现在分析的是单个View的dispatchTouchEvent(ev)方法,因此我们假设在点击按钮时它的根view都没有进行拦截,从而就传到了button中,
那么我们接下来就要分析button中的dispatchTouchEvent(ev)方法了。
Button.java:
public class Button extends TextView { public Button(Context context) { this(context, null); } public Button(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.buttonStyle); } public Button(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); event.setClassName(Button.class.getName()); } @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(Button.class.getName()); } }发现button中并没有这个方法,那就找它的父类,TextView.java
View.java---DispatchTouchEvent()
先看对这个方法的注释:
/** * Pass the touch screen motion event down to the target view, or this * view if it is the target. * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. */这个方法在view中的作用?
这里面的主要代码:
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { return true; } if (onTouchEvent(event)) { return true; }
根据源码我们得知如果第一层if()都满足那么这个函数就不会在执行了,而是直接消费掉了,return true;如果没有都满足则会执下面的OnTouchEvent()方法在进行判断。
注意:当前类是View.java,不是我们此前说的Activity.java。虽然有些方法名字一样,但是不要混淆
那下面我们就判断一下在Button的情况下第一个If()中的条件是否满足:
1.li是否为空?根据代码我们得到
ListenerInfo li = mListenerInfo;因此判断li是否为空,也就是判断mListenerInfo是否为空
在查看源码:View.java
ListenerInfo getListenerInfo() { if (mListenerInfo != null) { return mListenerInfo; } mListenerInfo = new ListenerInfo(); return mListenerInfo; }也就是说只要调用这个函数,mListenerInfo一定不为空。什么时候会调用这个方法?
查看源码: View.java
public void setOnClickListener(OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l; }
public void setOnTouchListener(OnTouchListener l) { getListenerInfo().mOnTouchListener = l; }只要调用了这上述两个方法,那么li就不会为空。
回到MainActivity.java的代码中
btn.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { showLog("button ontouch...event is.."+event.getAction()); return false; } });调用了其中一个方法,因此li是不会为空的。
2.li.mOnTouchListener是否为空?
View.java的源码中:
public void setOnTouchListener(OnTouchListener l) { getListenerInfo().mOnTouchListener = l; }因此只要调用了这个函数,那么li.mOnTouchListener就不为空,如上所示,mainActivity中button也设置了,因此不为空
3.(mViewFlags & ENABLED_MASK) == ENABLED
这句话的意思就是view子类控件是不是可以被enable的,button是默认可以的,imageView就默认不可以
。因此这个也成立
4.那么只剩最后一项了,li.mOnTouchListener.onTouch(this, event)
如上所知这个li.mOnTouchListener就是你在调用setOnTouchListener时的接口,
因此它的onTouch方法就是你在mainActivity自己实现的方法。
所以如果你return true,它就只执行OnTouch而不执行OnClick,
因为到这一步就被截断了。
下面我们来考虑onTouch中返回false时的情况,如果返回false,那么就会走下面的函数了
View.java
if (onTouchEvent(event)) { return true; }也就是会调用OnTouchEvent,注意这个onTouchEvent是View中的,不是activity中被复写的。
因此activity中onTOuchEvent没有被调用,和view中的是没有关系的。
我们来看view中的onTouchEvent方法:
非常的长,主要看这一行View.java
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { //如果该view是可被点击的,则执行,否则就进入了 // 因为button天生是可被点击的,所以就进入这个判断 switch (event.getAction()) { //第一次走actionDown,抬起时就走了actionUp主要来看当是手指抬起时的情况,会执行一个这个函数
View.java
if (!post(mPerformClick)) { performClick();//onClick事件就是在这个方法里执行的 }我们转到这个方法:
View.java
/** * Call this view's OnClickListener, if it is defined. Performs all normal * actions associated with clicking: reporting accessibility event, playing * a sound, etc. * * @return True there was an assigned OnClickListener that was called, false * otherwise is returned. */ public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); return true; } return false; }其中li.mOnClickListener在setOnClickListener()时就已经传递了,因此会执行这行代码
li.mOnClickListener.onClick(this);这个也就是在mainActivity中所定义的那个方法:
btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { showLog("button onclick"); } });
总结下,当在一个Button上进行点击时,android内部的执行流程是:
1.执行当前activity的dispatchTouchEvent方法,
如果你在activity的子类中复写这个方法并且不调用super.dispatchTouchEvent()
那么整个事件流程就不会执行。
2.Activity.java中如果你是按下事件,那么会先执行onUserInteraction()方法。
然后会执行
getWindow().superDispatchTouchEvent(ev)方法,如果返回为true,就不会再执行activity中的OnTouchEvent方法了。
这个方法会执行到当前activity的rootView中的dispatchTouchEvent方法,
假设在这个传递过程中事件没有被拦截,
那么就会传递到button的dispatchTouchEvent方法。
button并没有复写这个方法,因此就会追溯到其父父类view中的这个方法
3.View.java
执行dispatchTouchEvent()方法,首先会进行一个判断,
对于绑定了touch监听器的button来说是否满足这个判断
就取决于touch监听器中的回调方法是不是返回true了,
如果返回true,那么这个方法就被截断,
不在执行,也就无法执行onClick中方法,
如果返回false.则会向下执行
4.View.java
onTouchEvent()方法,在这个方法中有对手势的判断,
但要是走到判断这一层必须要是这个view可以被点击,
就Button而言,他可以被点击,因此可以走到这一层判断。
在手势为up的时候,会执行一个方法叫做performClick()。
5.View.java中
performClick()中就执行了view的setOnClickListener()中的onClick方法。
这样也解释了为什么down会执行onUserInterAction,其他时候不执行
为什么点击button时不执行activity的OnTouchEvent
为什么Button中OnTouch返回true就不执行onClick了,返回false就执行
以及touch事件中各个方法的执行顺序。
抽空在分析touch事件在整个viewTree中的传递。从而晚上touch事件的效果