这个内容距离上一次隔得有点远,连我自己都有点忘记了,
无奈公司的电脑系统又挂了,今天重装完后发现此前准备的都没了。
于是只能自己再来写一遍打log分析,也算加深印象。
顺便说一句,以前不知道为什么给每位coder配一台mac也算公司福利,
在用过了敝司"超强"的电脑后,我算明白了。
废话不说,下面我们就来说一下今天需要研究的事件传递的方向。
今天主要研究的就是Touch事件在android系统中的传递路线。
也就是用户手指按下屏幕后,在整个操作系统内部是如何传递的。
比如说按下了button,
1.那么这个触摸事件就直接传递给了Button么,还是中间经历了一些波折?
2.有没有可能按下button后事件不传递给Button?如果可能怎么做到?
3.几个控件叠在一起的情况下,按在那个位置,到底事件是默认如何传递的?
我们如果想按照自己的意愿传递那该怎么办?
废话不多说,我们先写一个测试代码,根据测试代码中输出的Log来进行分析,推理。
我们今天的测试模型因为要考虑事件在整个系统的传递,那么事件一定会传到一些我们需要的控件,那么当事件传递到某些控件时,这些控件自身肯定也会有一些方法被调用,因此为了看清楚,我们就需要自定义一些控件,进行log的输出查看。
我准备自定义三个控件:
OutLinearLayout.java--->最外层的布局view。占满整个屏幕
InsideButton.java--->自定义的button
InnerLinearLayout.java-->里层的布局view,和button平行放置在最外层。
给InsideButton,和InnerLinearLayout都复写一些事件传递相关的方法,不绑定监听器先,绑定的活动我们上一次分析过。
那先上代码吧:
布局文件:activity_main.xml:
<com.example.testtouch.OutLinearLayout 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:orientation="vertical"
android:gravity="center"
tools:context=".MainActivity" >
<!-- 为了能看的出来所以给它加了背景颜色为红色 -->
<com.example.testtouch.InnerLinearLayout
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ff0000"
/>
<com.example.testtouch.InnerButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="自定义button" />
</com.example.testtouch.OutLinearLayout>
OutLinearLayout.java
/**
* 最外层的linearlayout
* 复写了
* onTouchEvent()
* dispatchTouchEvent();
* onInterceptTouchEvent();
* @author szxgg
*
*/
public class OutLinearLayout extends LinearLayout{
public OutLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("Touch", "OutLinearLayout..dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("Touch", "OutLinearLayout..onTouchEvent");
return super.onTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("Touch", "OutLinearLayout..onInterceptTouchEvent..return is "+super.onInterceptTouchEvent(ev));
return super.onInterceptTouchEvent(ev);
}
}
InnerButton.java
**
* 自定义的button
* 复写了onTouchEvent();
* dispatchTouchEvent();
* @author szxgg
*
*/
public class InnerButton extends Button{
public InnerButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("Touch", "InnerButton...onTouchEvent..");
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d("Touch", "InnerButton...dispatchTouchEvent..");
return super.dispatchTouchEvent(event);
}
}
InnerLinearLayout.java
package com.example.testtouch;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;
/**
* 里层自定义的Layout
* 重写了
* onTouchEvent()
* dispatchTouchEvent()
* onInterceptTouchEvent();
* @author szxgg */
public class InnerLinearLayout extends LinearLayout{
public InnerLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("Touch", "InnerLinearLayout...onTouchEvent");
return super.onTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("Touch", "InnerLinearLayout...onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("Touch", "InnerLinearLayout...dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
}
mainActivity中什么都没有写,将程序运行起来。
下面看Log
单独点击空白区域:
11-17 18:54:25.217: D/Touch(7201): OutLinearLayout..dispatchTouchEvent
11-17 18:54:25.217: D/Touch(7201): OutLinearLayout..onInterceptTouchEvent..return is false
11-17 18:54:25.218: D/Touch(7201): OutLinearLayout..onTouchEvent
分析:点击空白区域,事件触发了最外层的layout,所以触发了最外层layout的方法。
执行顺序是:dispatchTouchEvent-->onInterceptTouchEvent(return false)-->onTouchEvent
点击button:
11-17 18:54:59.224: D/Touch(7201): OutLinearLayout..dispatchTouchEvent
11-17 18:54:59.224: D/Touch(7201): OutLinearLayout..onInterceptTouchEvent..return is false
11-17 18:54:59.224: D/Touch(7201): InnerButton...dispatchTouchEvent..
11-17 18:54:59.224: D/Touch(7201): InnerButton...onTouchEvent..
11-17 18:54:59.241: D/Touch(7201): OutLinearLayout..dispatchTouchEvent
11-17 18:54:59.241: D/Touch(7201): OutLinearLayout..onInterceptTouchEvent..return is false
11-17 18:54:59.241: D/Touch(7201): InnerButton...dispatchTouchEvent..
11-17 18:54:59.241: D/Touch(7201): InnerButton...onTouchEvent..
11-17 18:54:59.252: D/Touch(7201): OutLinearLayout..dispatchTouchEvent
11-17 18:54:59.253: D/Touch(7201): OutLinearLayout..onInterceptTouchEvent..return is false
11-17 18:54:59.253: D/Touch(7201): InnerButton...dispatchTouchEvent..
11-17 18:54:59.253: D/Touch(7201): InnerButton...onTouchEvent..
11-17 18:54:59.253: D/Touch(7201): OutLinearLayout..dispatchTouchEvent
11-17 18:54:59.253: D/Touch(7201): OutLinearLayout..onInterceptTouchEvent..return is false
11-17 18:54:59.253: D/Touch(7201): InnerButton...dispatchTouchEvent..
11-17 18:54:59.254: D/Touch(7201): InnerButton...onTouchEvent..
点击InnerLinearLayout:
11-17 18:55:17.037: D/Touch(7201): OutLinearLayout..dispatchTouchEvent
11-17 18:55:17.037: D/Touch(7201): OutLinearLayout..onInterceptTouchEvent..return is false
11-17 18:55:17.037: D/Touch(7201): InnerLinearLayout...dispatchTouchEvent
11-17 18:55:17.037: D/Touch(7201): InnerLinearLayout...onInterceptTouchEvent
11-17 18:55:17.038: D/Touch(7201): InnerLinearLayout...onTouchEvent
11-17 18:55:17.038: D/Touch(7201): OutLinearLayout..onTouchEvent
点击button和点击innerLayout有什么区别?
不考虑Log打印的次数,单看事件的执行顺序我们会发现呈现的规律如上红色的标注:
button在执行完onTouchEvent后好像就结束了,
就开始下一次的OutLayout的dispatch方法了
而innerLayout执行完onTouchEvent方法后,还没有结束,
又调回到了outLinearLayout的onTouchEvent()方法。
我们后面会分析这些现象的原因。
首先根据log来提取一些共性的地方:
当一个事件在屏幕上时,比如说按在button上,事件并不是直接先到button的,而是先由外部慢慢的传到内部。
流程:
activity.dispatchTouchEvent()--最外层的事件处理--第二层的事件处理--最里层的事件处理--activity.onTouchEvent();
如果事件在这个环节中没有被任何控件所拦截,那么它的处理流程就是这样的。
我们现在再单分析一层事件是如何处理的:
这个就是最外层的outLinearLayout的log:
dispatchTouchEvent()--onInterceptTouchEvent()--onTouchEvent();
dispatchTouchEvent()-->对事件进行分发,在activity.java中也有这个方法,将事件分到了rootView
onInterceptTouchEvent()-->是否拦截该事件,返回true就是拦截,
如果拦截事件就不在像下分发了,而是直接执行完自己的onTouchEvent()就结束。
如果不拦截就先不调用自己的onTouchEvent,
而是将事件分到下一层(如果有下一层的话,如果没有下一层就还是执行自己的OnTouchEvent());
onTouchEvent()--执行完了自己的触摸事件。
返回true的话事件到此为止,也就不在像上返回了,如果为false,事件还会像上返回。
所以我们了解了三个方法的主要作用:
dispatch是用来分发的,intercept用来判断是不是像下传递的,注意:即使当前view拦截了也不代表这个view
就处理这个事件,而是不是处理这个事件是由onTouchEvent的返回值来确定的。
对于可被点击的控件,onTouchEvent父类中天生返回true.
如果一个事件在一次触发中被拦截后,那么在这次触发的其他时间都不会再询问是否需要拦截,而都是被默认当做拦截来看待,一次触发是指手指头按下到抬起的一个过程。
因此在3层结构中我们如何让第二层响应这个事件?
那就是首先要拦截(拦截是viewGroup才有的),拦截下来后还要消费这个事件,就是onIntercept和onTouch都要返回true.可是在onTouch中不仅有返回的处理,还有包括对onClick的处理,因此不能直接这么复写,而是将那个需要消费的view中的clickable属性设为true也是可以的。
流程图如下:
以上是我们基于log推断出来的,那么是不是真的这么走的呢,
这个要看源码,源码分析我们下次看,非常复杂。
Touch事件光记住这些概念远远不够,在写代码的时候一定要多用多思考,才能好好掌握!