什么是Touch事件?java
一个Touch事件在用户点击屏幕(ACTION_DOWN
)时产生,抬起手指(ACTION_UP
)时结束,而Touch事件又被封装到MotionEvent
当中。安全
Touch事件整体能够分为如下几类。bash
事件类型 | 说明 |
---|---|
ACTION_DOWN | 手指按下 |
ACTION_UP | 手指抬起 |
ACTION_MOVE | 手势移动 |
ACTION_POINTER_DOWN | 多个手指按下 |
ACTION_POINTER_UP | 多个手指抬起 |
ACTION_CANCEL | 取消事件 |
获取Action最多见的方式就是使用MotionEvent
的getAction()
方法,getAction()
方法能够获ACTION_DONW
、ACTION_UP
、ACTION_MOVE
、以及ACTION_CANCEL
等事件,咱们分析事件传递时基本也是分析这些事件。app
ACTION_POINTER_DOWN
和ACTION_POINTER_UP
,则得和ACTION_MASK
相与才能获得。ide
从事件定义也能够知道,一个事件老是以ACTION_DOWN
做为开始,在手势移动过程当中会重复产生多个ACTION_MOVE
事件,用户操做结束事件的标志为ACTION_UP
,而意外终止事件则会触发ACTION_CANCEL
。函数
事件传递时,老是会从最下层开始向上层传递,也就是说出发点为Activity,逐层向上传递。当上层View响应事件后,下层的View将再也不会再响应。在后面的代码中咱们会分析为何会是这种现象。源码分析
作完准备工做后开始正式对事件传递流程进行分析,先分析最上层的View如何处理事件。布局
咱们先看一下在咱们的代码中事件会如何执行。自定义一个TestViewpost
public class TestView extends View {
public TestView(Context context) {
super(context);
}
public TestView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("test", "View onTouchEvent"+ event.getAction());
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d("test", "View dispatchTouchEvent"+ event.getAction() );
return super.dispatchTouchEvent(event);
}
}
复制代码
在自定义View当中存在两个事件分发相关方法onTouchEvent
以及dispatchTouchEvent
,咱们先关注一下现象,后文经过源码分析他们两个的关系。测试
在XML代码中对TestView进行引用,这一步很简单,只是简单地引用就不贴代码了。在Activity当中为TestView定义事件。
public class MainActivity extends AppCompatActivity implements View.OnTouchListener {
private TestView testView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
testView2 = (TestView2) findViewById(R.id.testView2);
View rootview = button.getRootView();
testView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("test", "onClick" + "------" + v);
}
});
testView.setOnTouchListener(this);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("test", "-----onTouch------" + event.getAction() + "------" + v);
return false;
}
}
复制代码
在Activity当中给TestView定义OnClickListener以及TochLintener。运行程序,轻轻点击TestView看一下结果:
D/test: View dispatchTouchEvent0
D/test: -----onTouch------0------com.example.liubohua.viewtestapplication.TestView{40f09bf V.ED..C.. ........ 0,0-1080,90 #7f070071 app:id/testView}
D/test: View onTouchEvent0
D/test: View dispatchTouchEvent1
D/test: -----onTouch------1------com.example.liubohua.viewtestapplication.TestView{40f09bf V.ED..C.. ...P.... 0,0-1080,90 #7f070071 app:id/testView}
D/test: View onTouchEvent1
D/test: onClick------com.example.liubohua.viewtestapplication.TestView{40f09bf V.ED..C.. ...P.... 0,0-1080,90 #7f070071 app:id/testView}
咱们在代码中是直接把Action打印的,在输出日志中变成了0,1,这也对应咱们的ACTION_DOWN
和ACTION_UP
两个事件.咱们以一种Action
为一组分析整个事件调用流程。
dispatchTouchEvent
->onTouch
->onTouchEvent
。onClick
方法,这是为何呢? 接下来稍微修改一下咱们测试代码。修改OnToUch
方法返回值为true.@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("test", "-----onTouch------" + event.getAction() + "------" + v);
return false; ------>return true;
}
复制代码
在看输出的结果:
D/test: View dispatchTouchEvent0
D/test: -----onTouch------0------com.example.liubohua.viewtestapplication.TestView{40f09bf V.ED..C.. ........ 0,0-1080,90 #7f070071 app:id/testView}
D/test: View dispatchTouchEvent2
D/test: -----onTouch------2------com.example.liubohua.viewtestapplication.TestView{40f09bf V.ED..C.. ........ 0,0-1080,90 #7f070071 app:id/testView}
D/test: View dispatchTouchEvent1
D/test: -----onTouch------1------com.example.liubohua.viewtestapplication.TestView{40f09bf V.ED..C.. ........ 0,0-1080,90 #7f070071 app:id/testView}
ON_DOWN和
ACTION_UP两个事件.咱们以一种
Action`为一组分析整个事件调用流程。
dispatchTouchEvent
->onTouch
接结束了,后面的onTouchEvent
没有再执行。OnTouch
的操做,仍是让onTouch
返回false
,而后修改onTouchEvent
的返回值为false
。@Override
public boolean onTouchEvent(MotionEvent event) {
" " ------->super.onTouchEvent(event);
Log.d("test", "View onTouchEvent"+ event.getAction());
return super.onTouchEvent(event);------->return fasle;
}
复制代码
再来看一下输出结果:
D/test: View dispatchTouchEvent0
D/test: -----onTouch------0------com.example.liubohua.viewtestapplication.TestView{40f09bf V.ED..C.. ........ 0,0-1080,90 #7f070071 app:id/testView}
D/test: View onTouchEvent0
执行顺序变成dispatchTouchEvent->onTouch->onTouchEvent,可是整个事件分发过程当中,只剩下了ACTION_DOWN这一种事件,只输出了Action=0的状况,也就是ACTION_DOWN以后的事件都再也不响应,怎么回事呢。
从上面的实例来看,dispatchTouchEvent毫无疑问就是我整个事件分发的入口了,咱们从这里入手。话很少说上源码。
//返回结果定义在方法内部变量result当中,当result返回true时,表示事件被消费,再也不继续向下分发,为false时继续向下分发
public boolean dispatchTouchEvent(MotionEvent event) {
if (event.isTargetAccessibilityFocus()) {
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// 事件能够被关注并正常分发
event.setTargetAccessibilityFocus(false);
}
//result用来存储函数最后的返回结果。
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
//当actionMasked == MotionEvent.ACTION_DOWN中止滑动事件
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
//判断窗口window是否被遮挡,方法返回为true,事件能够被继续分发,false再也不继续分发
if (onFilterTouchEventForSecurity(event)) {
//View当前是否被激活,而且有滚动事件发生
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//ListenerInfo是一个内部类,定义了一些监听事件
ListenerInfo li = mListenerInfo;
//这里的判断条件分别为li != nul
//li.mOnTouchListener != null,这个li.mOnTouchListener变量就是经过setOnTouchEvent赋值的。
//当前View是被激活的状态
//li.mOnTouchListener.onTouch(this, event)在咱们setOnTouchEvent内部有操做,当在这里咱们设置View的TouchEvent事件,当返回为true时,reslult=true表示消耗这个事件,将再也不继续往下传递。
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//当result=false才会执行onTouchEvent(event),这也就解释了为何当onTouch返回true时onTouchEvent(event)再也不执行。
//onTouchEvent(event)也返回true时,result=true
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest // of the gesture. if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); } return result; } 复制代码
dispatchTouchEvent
这个类代码很少,结合注释,方法内的处理主要有:
onTouch
事件返回true时,表明事件被消费掉。if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
复制代码
!result
显然不成立,因此onTouchEvent(event)也就不会执行。if (!result && onTouchEvent(event)) {
result = true;
}
复制代码
分析到这里,dispatchTouchEvent、OnTouch和OnTouchEevent的调用关系应该是清楚了,咱们来总结一下:
dispatchTouchEvent做为事件分发的入口,在任什么时候候都会优先执行,在dispatchTouchEvent函数内部调用了onTouch和OnTouchEvent方法。
当onTouch返回True时,dispatchTouchEvent返回值也为True,OnTouchEvent方法将再也不执行。
当onTouch返回False时,会执行OnTouchEvent方法,而OnTouchEvent方法的返回值也就决定了dispatchTouchEvent的返回值
当dispatchTouchEvent返回true时表示消费事件,后续的事件将继续响应;当dispatchTouchEvent返回Fasle是表示不消费事件,不会再响应后续事件。
分析了半天,咱们最经常使用的setOnCLickListener怎么没有出现呢,何时会执行onClick方法。咱们接着看OnTouchEvent的代码:
onTouchEvent
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//当前视图是否可被执行点击、长按等操做
//可经过java代码或者xml代码设置enable状态或者clickable状态。
//当这些状态为false时,则clickable = false,不然为true。
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//判断视图是否已经被销毁
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
//一个已经销毁的视图,被点击时依旧消费事件,只是不能响应事件
return clickable;
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
//判断是否可点击
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// 判断是否能够获取焦点,若是能够,则获取焦点
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//最终ACTION_UP要执行的方法,post到UI Thread的中的一个Runnable,若是不存在message queue,则直接执行performClick();
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
// 判断当前view是不是在滚动器当中
boolean isInScrollingContainer = isInScrollingContainer();
//若是是在滚动器当中,在滚动器当中的话延迟返回事件,延迟时间为 ViewConfiguration.getTapTimeout()=100毫秒
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// 不在滚动器当中则当即作出响应
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
// 判断当前滑动事件是否还在当前view当中
if (!pointInView(x, y, mTouchSlop)) {
// 若是超出view,则取消所事件
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}
复制代码
onTouchEvent
里面作的事情也很少,主要是分类处理各个不一样的Action事件,仍是结合注释,咱们分析一下大概流程:
clickable
的变量,这个变量在View包括继承自View的自定义View当中默认是fasle的,可是在Button、TextView当中默认就是true。当咱们设置了setOnClickListener时会也会执行clickable=true。以后即是对不一样Action的处理。final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
复制代码
ACTION_DOWN
。这一部分主要是判断是否在滚动器中,当在滚动器中会有100毫秒的延迟响应,用来判断是否要响应事件。再也不滚动器则直接响应事件。ACTION_MOVE
只是作了处理响应事件的操做。public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
//这里实际上就是调用了咱们的setOnClickListerner.onClick()方法
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
复制代码
看到这里咱们知道setOnClickListener方法在调用到action = MotionEvent.ACTION_UP方法以后才会执行。这也就解释了为何在上面的实例当中onClick方法老是在最后执行,而当TouchEvent返回false以后setOnClickListener方法则不会再执行。由于这时候TouchEvent只会响应MotionEvent.ACTION_DOWN事件,而setOnClickListener是在View响应MotionEvent.ACTION_UP事件以后才执行。
与测试View时的基本流程同样,咱们自定义一个TestLayout
用来输出事件的调用顺序,具体代码以下:
public class TestLayout extends LinearLayout {
public TestLayout(Context context) {
super(context);
}
public TestLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public TestLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("test", "TestLayout onInterceptTouchEvent-- action=" + ev.getAction());
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d("test","TestLayout dispatchTouchEvent-- action=" + event.getAction());
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("test","TestLayout onTouchEvent-- action=" + event.getAction());
return super.onTouchEvent(event);
}
}
复制代码
由于LinearLayout
是直接继承自ViewGroup
的,因此为了布局方便这里咱们的自定义TestView
选择继承LinearLayout
。
在ViewGroup
当中涉及到事件分发的方法,相比View来讲除了dispatchTouchEvent
和onTouchEvent
外还有onInterceptTouchEvent
。 为了测试方便,咱们把TestView
的代码改成最开始的样子:
TestView
public class TestView extends View {
public TestView(Context context) {
super(context);
}
public TestView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("test", "View onTouchEvent"+ event.getAction());
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d("test", "View dispatchTouchEvent"+ event.getAction() );
return super.dispatchTouchEvent(event);
}
}
复制代码
XML依然只是引入最简单的布局,这里省略掉,看一下Activity当中的设置:
public class MainActivity extends AppCompatActivity implements View.OnTouchListener {
private TestView testView;
private TestLayout testLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
testView = (TestView) findViewById(R.id.testView);
testLayout = (TestLayout) findViewById(R.id.test_layout);
testLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("test", "TestLayout onClick" + "------" + v);
}
});
testView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("test", "onClick" + "------" + v);
}
});
testLayout.setOnTouchListener(this);
testView.setOnTouchListener(this);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("test", "-----onTouch------" + event.getAction() + "------" + v);
return false;
}
}
复制代码
运行程序,点击TestView
所在区域,看一下输出的结果:
D/test: TestLayout dispatchTouchEvent-- action=0
D/test: TestLayout onInterceptTouchEvent-- action=0
D/test: View dispatchTouchEvent0
D/test: -----onTouch------0------com.example.liubohua.viewtestapplication.TestView{40f09bf V.ED..C.. ........ 0,0-1080,90 #7f070071 app:id/testView}
D/test: View onTouchEvent0
D/test: TestLayout dispatchTouchEvent-- action=2
D/test: TestLayout onInterceptTouchEvent-- action=2
D/test: View dispatchTouchEvent2
D/test: -----onTouch------2------com.example.liubohua.viewtestapplication.TestView{40f09bf V.ED..C.. ...P.... 0,0-1080,90 #7f070071 app:id/testView}
D/test: View onTouchEvent2
D/test: TestLayout dispatchTouchEvent-- action=1
D/test: TestLayout onInterceptTouchEvent-- action=1
D/test: View dispatchTouchEvent1
D/test: -----onTouch------1------com.example.liubohua.viewtestapplication.TestView{40f09bf V.ED..C.. ...P.... 0,0-1080,90 #7f070071 app:id/testView}
D/test: View onTouchEvent1
D/test: onClick------com.example.liubohua.viewtestapplication.TestView{40f09bf V.ED..C.. ...P.... 0,0-1080,90 #7f070071 app:id/testView}
输出的日志有点多,咱们以一个Action
为一组进行分析,以前咱们也说过,一个Action
表明一类的Touch事件。
dispatchTouchEvent
,而后是onInterceptTouchEvent
,这两个都是TestLayout
的方法,以后的事件被传递到了TestView当中,按照咱们以前说的顺序dispatchTouchEvent
->onTouch
->onTouchEvent
进行事件分发。View
的onClick
事件,这就与咱们以前分析的View当中的事件分发机制不谋而合。咱们以前说ViewGroup的事件分发与dispatchTouchEvent
、onTouchEvent
、onInterceptTouchEvent
三个方法有关,可是从这个例子当中咱们发现所执行的方法只有dispatchTouchEvent
和onInterceptTouchEvent
,那么onTouchEvent
在何时执行呢?咱们设置的OnTouch
方法以及OnClick
方法在何时调用呢?
带着疑问咱们点击一下TestView
之外的区域,再查看输出日志:
D/test: TestLayout dispatchTouchEvent-- action=0
D/test: TestLayout onInterceptTouchEvent-- action=0
D/test: -----onTouch------0------com.example.liubohua.viewtestapplication.TestLayout{15fd3de V.E...C.. ........ 0,0-1080,1680 #7f070074 app:id/test_layout}
D/test: TestLayout onTouchEvent-- action=0
D/test: TestLayout dispatchTouchEvent-- action=2
D/test: -----onTouch------2------com.example.liubohua.viewtestapplication.TestLayout{15fd3de V.E...C.. ...P.... 0,0-1080,1680 #7f070074 app:id/test_layout}
D/test: TestLayout onTouchEvent-- action=2
D/test: TestLayout dispatchTouchEvent-- action=1
D/test: -----onTouch------1------com.example.liubohua.viewtestapplication.TestLayout{15fd3de V.E...C.. ...P.... 0,0-1080,1680 #7f070074 app:id/test_layout}
D/test: TestLayout onTouchEvent-- action=1
D/test: TestLayout onClick------com.example.liubohua.viewtestapplication.TestLayout{15fd3de V.E...C.. ...P.... 0,0-1080,1680 #7f070074 app:id/test_layout}
一样的仍是以不一样的Action来分组分析:
dispatchTouchEvent
->onInterceptTouchEvent
->onTouch
->onTouchEvent
,并且四个方法所有都是TestLayout内的方法输出,没有了View
的参与瞬间清爽了不少。dispatchTouchEvent
->onTouch
->onTouchEvent
,到了这里onInterceptTouchEvent
方法又不见了。onInterceptTouchEvent
方法,在输出末尾还读了TestLayOut
的onClick
事件,这么任性究竟是要闹哪样。我再看到这里的时候也是一头雾水,这事件执行的还真是随意,一会一个样子。没办法仍是去源码里面找答案吧。
与View同样,整个事件分发的入口依然是dispatchTouchEvent
,咱们从这里入手。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// 若是事件指向了一个可达的视图,则事件正常调度分发。
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
//进行安全策略检查,返回true正常进行下一步操做
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
//当action为MotionEvent.ACTION_DOWN时作一些初始化操做
if (actionMasked == MotionEvent.ACTION_DOWN) {
//在这里把全部的TouchTarget置空。这里须要说明一下,当TouchTarget不为null时,表示已经找到可以处理touch事件的目标。
//touchTarget是一个链表,用来存储能够用来响应事件的view
cancelAndClearTouchTargets(ev);
//重置了全部的TouchState
resetTouchState();
}
// intercepted变量用来记录是否须要拦截事件
final boolean intercepted;
//当actionMasked == MotionEvent.ACTION_DOWN或者已经找到能够处理事件的view时知足判断条件
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//disallowIntercept为是否开启禁止拦截的标志,当disallowIntercept=true时禁止拦截,不然开启
//调用requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法能够设置是否关闭拦截
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//判断disallowIntercept=false,开启拦截
if (!disallowIntercept) {
//执行onInterceptTouchEvent方法,也就是咱们本身定义的回调
//默认onInterceptTouchEvent(ev)方法返回false,不拦截事件,当返回true时表示事件拦截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
//判断disallowIntercept=true,赋值intercepted = false,表示不拦截事件
intercepted = false;
}
} else {
// 当没有View处理事件,而且不是ACTION_DOWN时,继续拦截。
intercepted = true;
}
//如何事件被拦截,或者有View处理该事件,则正常处理。
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// canceled变量初始化,
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// split变量决定是否吧事件分发给多个子View,能够经过setMotionEventSplittingEnabled(boolean split)方法进行设置
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//没有取消而且没有拦截事件时进入判断
if (!canceled && !intercepted) {
//查找优先处理action的view,
//在View当中能够经过getRootView().getParent().requestSendAccessibilityEvent(View child, AccessibilityEvent event));方法进行设置
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//不知足条件时表示不须要再从新寻找响应事件的View
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
//判断childrenCount是否等于0而且newTouchTarget == null
if (newTouchTarget == null && childrenCount != 0) {
//获取事件的坐标值,没有在坐标内的View不须要再响应事件
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//倒序遍历全部的子View,这里采用一种倒序的方式,在java代码中后addView或者在xml当中后添加的布局会先被响应事件。
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//若是存在优先要处理Action的View,则进行下次循环直到找到View
//当childWithAccessibilityFocus=null时正常分发事件到每个View
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
//若是当前点击的位置没有被子View占据,则直接进入下一次循环
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//在TouchTarget当中在此寻找该View,若是找到了直接退出循环,没有找到则继续向下执行
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
//递归的方式在子View的dispatchTouchEvent方法,寻找能够响应action的View,当返回为True时,进入循环,表示找到能够响应的View。
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//将找到的View加入到TouchTarget当中
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
//找到View 跳出循环
break;
}
// The accessibility focus didn't handle the event, so clear // the flag and do a normal dispatch to all children. ev.setTargetAccessibilityFocus(false); } if (preorderedList != null) preorderedList.clear(); } //当不能再找到能够响应action的子View时,将TouchTarget链表指到最初找到的View if (newTouchTarget == null && mFirstTouchTarget != null) { // Did not find a child to receive the event. // Assign the pointer to the least recently added target. newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } } //当没有View消费事件时,mFirstTouchTarget == null if (mFirstTouchTarget == null) { // 这里调用dispatchTransformedTouchEvent,其实也就是调用ViewGroup自身TouchEvent事件 handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // Dispatch to touch targets, excluding the new touch target if we already // dispatched to it. Cancel touch targets if necessary. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; //alreadyDispatchedToNewTouchTarget=true表示在上面遍历View的过程当中已经消费了事件 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { //调用子View的dispatchOnTouchEvent进行事件处理 final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } // Update list of touch targets for pointer up or cancel, if needed. if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { resetTouchState(); } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { final int actionIndex = ev.getActionIndex(); final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); } } if (!handled && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); } return handled; } 复制代码
整个代码有点多,有兴趣的能够逐行分析。这里咱们挑出重点步骤分析一下。
首先是初始化的部分,初始化是在ACTION_DOWN产生时发生的。这里有一个很是重要的操做就是把touchTarget是链表置空。
touchTarget链表自己是用来存储在一次分发过程当中可响应事件的View的链表,每一次ACTION_DOWN产生事件产生,都表明一个新的事件开始,这个时候清空touchTarget从新寻找能够响应的View,一旦找到能够相应的View,就存储进来,在后续的ACTION_MOVE等事件发生时,直接在touchTarget链表中寻找事件的消费者。
//当action为MotionEvent.ACTION_DOWN时作一些初始化操做
if (actionMasked == MotionEvent.ACTION_DOWN) {
//在这里把全部的TouchTarget置空。这里须要说明一下,当TouchTarget不为null时,表示已经找到可以处理touch事件的目标。
//touchTarget是一个链表,用来存储能够用来响应事件的view
cancelAndClearTouchTargets(ev);
//重置了全部的TouchState
resetTouchState();
}
复制代码
初始化以后是对拦截器的判断,onInterceptTouchEvent
的执行与否就和这里密切相关。
首先初始化了一个标志位disallowIntercept
,这个标志位用来标记时候禁用拦截器,在咱们的代码中能够经过requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法来对disallowIntercept
的值进行设置。
当disallowIntercept
=false时表示不由用拦截器,正常执行onInterceptTouchEvent(ev);
方法,而且将该方法的返回值返回给intercepted
变量用来标志是否拦截事件。
当disallowIntercept
=true时表示禁用拦截器,执行intercepted = false;
也就是对事件不进行拦截。
这里也就解释了为何点击ViewGroup空白处时onInterceptTouchEvent(ev)
只调用一次了
//intercepted变量用来记录是否须要拦截事件
final boolean intercepted;
//当actionMasked == MotionEvent.ACTION_DOWN或者已经找到能够处理事件的view时知足判断条件
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//disallowIntercept为是否开启禁止拦截的标志,当disallowIntercept=true时禁止拦截,不然开启
//调用requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法能够设置是否关闭拦截
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//判断disallowIntercept=false,开启拦截
if (!disallowIntercept) {
//执行onInterceptTouchEvent方法,也就是咱们本身定义的回调
//默认onInterceptTouchEvent(ev)方法返回false,不拦截事件,当返回true时表示事件拦截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
//判断disallowIntercept=true,赋值intercepted = false,表示不拦截事件
intercepted = false;
}
} else {
// 当没有View处理事件,而且不是ACTION_DOWN时,继续拦截。
intercepted = true;
}
复制代码
接下来会对子View进行遍历,这里采用一个倒序遍历的方式,在java代码中后addView或者在xml当中后添加的布局会先被响应事件,这也符合咱们的视图逻辑,在最上层的View应该最优先响应事件。
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
复制代码
再往下执行,有一个判断语句if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))
当它返回true时表明找到了能够响应事件的View,返回Fasle表示没有找到。
dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)
方法中有个重要的参数child,当child!=null时,调用了子View的dispatchTouchEvent
方法判断是否对事件进行响应。若是child==null,则调用super.dispatchTouchEvent(event);
也就是View
的dispatchTouchEvent(event);
方法进行处理。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations // or filtering. The important part is the action, not the contents. final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } } 复制代码
当找到能够响应事件的View时进入判断,最终经过newTouchTarget = addTouchTarget(child, idBitsToAssign);
方法将子View添加到链表TouchTarget当中。
在最后一部分的代码里决定了具体后续将如何响应事件。
前面咱们分析过复合条件的View都会被存储到mFirstTouchTarget
所对应的链表当中,当mFirstTouchTarget == null
时就须要ViewGroup
本身处理事件,这个时候一样调用dispatchTransformedTouchEvent
方法,可是chlid
参数传参为null,上面咱们也分析过,会调用View
的dispatchTouchEvent(event)
方法。
当mFirstTouchTarget != null
表示存在能够响应事件的子View,一样也是调用dispatchTransformedTouchEvent
方法,可是chlid
参数传参为则传入可实际响应事件的View。
//当没有View消费事件时,mFirstTouchTarget == null
if (mFirstTouchTarget == null) {
// 这里调用dispatchTransformedTouchEvent,其实也就是调用ViewGroup自身TouchEvent事件
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
//alreadyDispatchedToNewTouchTarget=true表示在上面遍历View的过程当中已经消费了事件
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//调用子View的dispatchOnTouchEvent进行事件处理
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
复制代码
分析到这里ViewGroup
的事件分发也基本完成了。这里没有再分析onTouchEevent
和onTouch
方法,这两个方法和View
当中的处理并无区别。
事件分发到这里就结束了?在咱们的本身的布局当中看是这样的,可是除了在咱们XML中定义的布局以外,Android视图还有默认的Activity
层级。
以前的布局都不变,再Activity
当中作出修改:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i("test", "MainActivity--dispatchTouchEvent--action=" + ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public void onUserInteraction() {
Log.d("test", "MainActivity--onUserInteraction");
super.onUserInteraction();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("test", "MainActivity--onTouchEvent--action=" + event.getAction());
return super.onTouchEvent(event);
}
复制代码
这三个都是Activity提供的可重写的方法,再点击一下看看如何输出日志:
I/test: MainActivity--dispatchTouchEvent--action=0
D/test: MainActivity--onUserInteraction
D/test: TestLayout dispatchTouchEvent-- action=0
D/test: TestLayout onInterceptTouchEvent-- action=0false
D/test: -----onTouch------0------com.example.liubohua.viewtestapplication.TestLayout{6de08b5 V.E...C.. ........ 0,0-1920,864 #7f070074 app:id/test_layout}
D/test: TestLayout onTouchEvent-- action=0
I/test: MainActivity--dispatchTouchEvent--action=1
D/test: TestLayout dispatchTouchEvent-- action=1
D/test: -----onTouch------1------com.example.liubohua.viewtestapplication.TestLayout{6de08b5 V.E...C.. ...P.... 0,0-1920,864 #7f070074 app:id/test_layout}
D/test: TestLayout onTouchEvent-- action=1
依然是分组分析。
Activity
的dispatchTouchEvent
->onUserInteraction
,接着执行TestLayout
的dispatchTouchEvent
->onInterceptTouchEvent
->onTouch
->onTouchEvent
。Activity
的dispatchTouchEvent
,接着执行TestLayout
的dispatchTouchEvent
->onTouch
->onTouchEvent
。话很少说,我们直接看源码。
dispatchTouchEvent
一样仍是整个事件的入口
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
复制代码
这里代码不多,咱们逐行分析。
当MotionEvent.ACTION_DOWN事件产生时,就执行了onUserInteraction()
,没有任何前置条件,在一次事件流程中必定会执行且只执行一次。
public void onUserInteraction() {
}
复制代码
onUserInteraction()
里面没有任何实现,这个方法提供出来就是为用户提供的。
接下执行getWindow
,获取到一个PhoneWindow
实例,去PhoneWindow
当中在看一下superDispatchTouchEvent(ev)
方法都作了些什么。
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
复制代码
又调用了mDecor
的方法,咱们再跟进去看看。
public class DecorView extends FrameLayout{
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
}
复制代码
好了到这里咱们就应明了了,DecorView
里面直接调用了super.dispatchTouchEvent(event)
,而这个super
是谁呢?就是FrameLayout
,FrameLayout
的dispatchTouchEvent(event)
就是ViewGoup
的dispatchTouchEvent(event)
。
到这里差很少咱们的事件分发就结束了,从Activity一路分发到了View当中。可是在本文中并无介绍Touch具体是从哪里产生并分发到咱们的Activity当中的,有兴趣的能够了解一下Android 事件来源介绍的很详细。