Android高级UI开发(三十二)事件分发2:ViewGroup事件分发机制
上一篇我们讲了View的事件分发,我们回忆一下当时总结的结论:
- View事件执行流程
dispatchTouchEvent---->onTouchListener(onTouch返回false)------> onTouchEvent---->onClick
2.dispatchTouchEvent的返回值对事件分发的影响
当返回true时,后续的事件,该View还会继续接收到,如Down---Up事件(Down事件已被消费,后续的Up就会继续接收到)
当返回false时,后续的事件,该View再也不能接收到,如Down---Up事件(接收完Down事件后,Up不会再分发给该VIEW,而是把Down事件转发给父容器ViewGroup,如果条件满足,UP事件以后也由ViewGroup接收,从而ViewGroup实现了单击函数)。
回忆着2点总结的原因是:ViewGroup的事件分发与View大部分都是雷同的。唯一的区别是ViewGroup多了一个
public boolean onInterceptTouchEvent(MotionEvent event) 函数,那我们今天研究ViewGroup的事件分发就从onInterceptTouchEvent函数说起。
配套Demo实验源码:https://download.csdn.net/download/gaoxiaoweiandy/11164333
1. boolean onInterceptTouchEvent(MotionEvent event)
这个函数通常返回false,表示不拦截事件,言外之意是ViewGroup接收到事件后,会继续传递给它嵌套包含的child视图,通常是一个View。相反,如果返回true,表示拦截,不会再将触摸事件传递给子View,而是由ViewGruop自己处理。这里透漏一下,通常这个函数总是返回false,不拦截。
1.1 返回false(默认总是返回false)
举个例子,有如下一个布局文件:
-
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-
android:layout_width="match_parent"
-
android:layout_height="match_parent"
-
android:id="@+id/layout"
-
>
-
-
<Button
-
android:onClick="buttonClick"
-
android:id="@+id/button1"
-
android:layout_width="wrap_content"
-
android:layout_height="wrap_content"
-
android:layout_marginTop="14dp"
-
android:text="Button" />
-
-
-
</RelativeLayout>
其中的RelativeLayout就是一个ViewGroup,它包含一个chlid子View:Button。 当我们点击Button的时候,DOWN事件先由RelativeLayout接收到,从ViewGroup类的dispatchTouchEvent函数开始,在ViewGroup.dispatchTouchEvent内部调用的onInterceptTouchEvent函数返回false(一般都返回false,后面分析源码再证实),所以RelativeLayout继续将Down事件传递给子View:Button,接下来开始传给Button的dispatchTouchEvent函数(实质是View类的),接下来的执行过程就和我们上一篇分析的View的分发机制一样了,Button最后依次执行了dispatchTouchEvent---->onTouchListener(onTouch返回false)------> onTouchEvent---->onClick。而
ViewGroup并没有执行后面的 onTouchListener(onTouch返回false)------> onTouchEvent---->onClick。 所以点击Button时,
ViewGroup不会响应单击函数onClick的,因为它将事件传递给了Button,同时Button的dispatchTouchEvent默认返回true,因此后续的UP事件也由Button消费
。
1.2 返回true
布局不变,但是事件被ViewGroup拦截了,当点击Button的时候,DOWN事件同样先由RelativeLayout接收到,从ViewGroup类的dispatchTouchEvent函数开始,让在ViewGroup.dispatchTouchEvent内部调用的onInterceptTouchEvent函数返回true(可以重写onInterceptTouchEvent强制让它返回true),所以RelativeLayout将事件拦截了下来,不再往下传递给子View:Button了。它自己消费这些事件,最后的结果是ViewGroup执行了dispatchTouchEvent---->onTouchListener(onTouch返回false)------> onTouchEvent---->onClick这个过程(Tip:这里和View的分发过程又一样了,是因为
ViewGroup自己处理事件时调用了super.dispatchTouchEvent,它的super正是View)
.产生了一个奇怪的效果:我们明明点击的是Button,并没有点击RelativeLayout,但最终RelativeLayout却响应了单击函数。
2. 证明上面的结论
我们还是通过写一个小Demo,通过做实验(改变onInterceptTouchEvent的返回值)且结合ViewGroup源码来得出上面的结论。
2.1 onInterceptTouchEvent返回false
ViewGroup会将事件传递给子View:MyButton。
Demo的布局文件:
-
<com.example.relativelayoutevent.MyLayout xmlns:android="http://schemas.android.com/apk/res/android"
-
android:layout_width="match_parent"
-
android:layout_height="match_parent"
-
android:id="@+id/layout"
-
>
-
-
<com.example.relativelayoutevent.MyButton
-
android:onClick="buttonClick"
-
android:id="@+id/button1"
-
android:layout_width="wrap_content"
-
android:layout_height="wrap_content"
-
android:layout_marginTop="14dp"
-
android:text="Button" />
-
-
-
</com.example.relativelayoutevent.MyLayout>
MainActivity.java
-
package com.example.relativelayoutevent;
-
-
import android.app.Activity;
-
import android.os.Bundle;
-
import android.util.Log;
-
import android.view.MotionEvent;
-
import android.view.View;
-
import android.view.View.OnClickListener;
-
import android.widget.Button;
-
import android.widget.ImageView;
-
import android.widget.RelativeLayout;
-
-
public class MainActivity extends Activity implements OnClickListener, View.OnTouchListener {
-
-
private MyButton myButton;
-
private MyLayout myLayout;
-
private ImageView imvTest;
-
-
@Override
-
protected void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.activity_main);
-
-
myButton = (MyButton)findViewById(R.id.button1);
-
myLayout = (MyLayout)findViewById(R.id.layout);
-
-
myLayout.setOnTouchListener(this);
-
myLayout.setOnClickListener(this);
-
-
myButton.setOnClickListener(this);
-
myButton.setOnTouchListener(this);
-
-
}
-
-
-
-
@Override
-
public boolean onTouch(View v, MotionEvent event) {
-
-
boolean returenOnTouch = false;
-
Log.i("eventTest", "onTouchListener:acton--"+event.getAction()+" ,result="+returenOnTouch+"----view:"+v);
-
return returenOnTouch;
-
}
-
-
@Override
-
public void onClick(View v) {
-
Log.i("eventTest", "OnClickListener----view:"+v);
-
}
-
-
}
这里分别为RelativeLayout与Button定义了单击响应函数和onTouchListener.onTouch并且返回false,以便能执行到onClick那里,至于为何,在上一篇已经总结过了:dispatchTouchEvent---->onTouchListener(onTouch返回false)------> onTouchEvent---->onClick。
MyLayout.java
-
package com.example.relativelayoutevent;
-
-
import android.content.Context;
-
import android.util.AttributeSet;
-
import android.util.Log;
-
import android.view.MotionEvent;
-
import android.widget.RelativeLayout;
-
-
public class MyLayout extends RelativeLayout {
-
public MyLayout(Context context) {
-
super(context);
-
}
-
-
public MyLayout(Context context, AttributeSet attrs) {
-
super(context, attrs);
-
}
-
-
public MyLayout(Context context, AttributeSet attrs, int defStyleAttr) {
-
super(context, attrs, defStyleAttr);
-
}
-
-
-
@Override
-
public boolean onTouchEvent(MotionEvent event) {
-
boolean onTouchEventResult = super.onTouchEvent(event);
-
-
Log.i("eventTest", "MyLayoutonTouchEvent:action--"+event.getAction()+
-
",result="+onTouchEventResult+",view:MyLayout");
-
return onTouchEventResult;//默認返回true
-
}
-
-
@Override
-
public boolean dispatchTouchEvent(MotionEvent event) {
-
boolean dispatchResult = super.dispatchTouchEvent(event); //默认返回true
-
Log.i("eventTest", "MyLayoutdispatchTouch:action--"+event.getAction()+
-
",result="+dispatchResult+",view:MyLayout");
-
return dispatchResult;
-
}
-
-
@Override
-
public boolean onInterceptTouchEvent(MotionEvent event) {
-
boolean onInterceptResult = super.onInterceptTouchEvent(event); //默认返回false
-
-
onInterceptResult = false;
-
Log.i("eventTest", "MyInterceptTouch:action--"+event.getAction()+
-
",result="+onInterceptResult+",view:MyLayout");
-
-
-
return onInterceptResult;
-
-
}
-
}
这里我们自定义了RelativieyLayout:MyLayout重写了
dispatchTouchEvent 默认返回true,就是说super.dispatchTouchEvent 返回true, Down与后续的UP事件可以顺利接收.
onTouchEvent 默认返回true,就是说super.onTouchEvent 返回true,从而dispatchTouchEvent 返回true.
-
onInterceptTouchEvent 默认返回false,就是说super.onInterceptTouchEvent 返回false.默认不拦截事件,将事件交给子View:MyButton。
-
-
这里我们显示的将onInterceptResult 指定为false,是为了直观的知道它默认返回false,并且后面做实验时可以随时将返回值改为true.
MyButton.java
-
package com.example.relativelayoutevent;
-
-
import android.content.Context;
-
import android.util.AttributeSet;
-
import android.util.Log;
-
import android.view.MotionEvent;
-
import android.widget.Button;
-
-
public class MyButton extends Button {
-
-
public MyButton(Context context, AttributeSet attrs) {
-
super(context, attrs);
-
// TODO Auto-generated constructor stub
-
}
-
-
@Override
-
public boolean dispatchTouchEvent(MotionEvent event) {
-
-
boolean dispatchResult = super.dispatchTouchEvent(event);//默认返回true
-
Log.i("eventTest", "MydispatchTouchEvent:action--"+event.getAction()+
-
",result="+dispatchResult+",view:MyButton");
-
-
return dispatchResult;
-
-
}
-
-
@Override
-
public boolean onTouchEvent(MotionEvent event) {
-
-
boolean onTouchEventResult = super.onTouchEvent(event); //这个默 认返回true
-
Log.i("eventTest", "MyonTouchEvent:action--"+event.getAction()+
-
",result="+onTouchEventResult+",view:MyButton");
-
return onTouchEventResult;
-
}
-
-
}
这里我们自定义了Button:MyButton重写了
dispatchTouchEvent 默认返回true,就是说super.dispatchTouchEvent 返回true, Down与后续的UP事件可以顺利接收.
onTouchEvent 默认返回true,就是说super.onTouchEvent 返回true,从而dispatchTouchEvent 返回true.
-
Tip: View是没有“onInterceptTouchEvent" 这个函数的
-
-
Ok,我们现在试试点击Button看一下会打印出什么日志:
-
eventTest: MyInterceptTouch:action--0,result=false,view:MyLayout
-
eventTest: onTouchListener:acton--0 ,result=false----view:com.example.relativelayoutevent.MyButton
-
eventTest: MyonTouchEvent:action--0,result=true,view:MyButton
-
eventTest: MydispatchTouchEvent:action--0,result=true,view:MyButton
-
eventTest: MyLayoutdispatchTouch:action--0,result=true,view:MyLayout
-
eventTest: MyInterceptTouch:action--1,result=false,view:MyLayout
-
eventTest: onTouchListener:acton--1 ,result=false,view:MyButton
-
eventTest: MyonTouchEvent:action--1,result=true,view:MyButton
-
eventTest: MydispatchTouchEvent:action--1,result=true,view:MyButton
-
eventTest: MyLayoutdispatchTouch:action--1,result=true,view:MyLayout
-
eventTest: OnClickListener----view:com.example.relativelayoutevent.MyButton
同时我们贴出ViewGroup的源码:
-
@Override
-
public boolean dispatchTouchEvent(MotionEvent ev) {
-
if (mInputEventConsistencyVerifier != null) {
-
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
-
}
-
-
// If the event targets the accessibility focused view and this is it, start
-
// normal event dispatch. Maybe a descendant is what will handle the click.
-
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
-
ev.setTargetAccessibilityFocus(false);
-
}
-
-
boolean handled = false;
-
if (onFilterTouchEventForSecurity(ev)) {
-
final int action = ev.getAction();
-
final int actionMasked = action & MotionEvent.ACTION_MASK;
-
-
// Handle an initial down.
-
if (actionMasked == MotionEvent.ACTION_DOWN) {
-
// Throw away all previous state when starting a new touch gesture.
-
// The framework may have dropped the up or cancel event for the previous gesture
-
// due to an app switch, ANR, or some other state change.
-
cancelAndClearTouchTargets(ev);
-
resetTouchState();
-
}
-
-
// Check for interception.
-
final boolean intercepted;
-
if (actionMasked == MotionEvent.ACTION_DOWN
-
|| mFirstTouchTarget != null) {
-
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
-
if (!disallowIntercept) {
-
intercepted = onInterceptTouchEvent(ev);
-
ev.setAction(action); // restore action in case it was changed
-
} else {
-
intercepted = false;
-
}
-
} else {
-
// There are no touch targets and this action is not an initial down
-
// so this view group continues to intercept touches.
-
intercepted = true;
-
}
-
-
// If intercepted, start normal event dispatch. Also if there is already
-
// a view that is handling the gesture, do normal event dispatch.
-
if (intercepted || mFirstTouchTarget != null) {
-
ev.setTargetAccessibilityFocus(false);
-
}
-
-
// Check for cancelation.
-
final boolean canceled = resetCancelNextUpFlag(this)
-
|| actionMasked == MotionEvent.ACTION_CANCEL;
-
-
// Update list of touch targets for pointer down, if needed.
-
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
-
TouchTarget newTouchTarget = null;
-
boolean alreadyDispatchedToNewTouchTarget = false;
-
if (!canceled && !intercepted) {
-
-
// If the event is targeting accessibility focus we give it to the
-
// view that has accessibility focus and if it does not handle it
-
// we clear the flag and dispatch the event to all children as usual.
-
// We are looking up the accessibility focused host to avoid keeping
-
// state since these events are very rare.
-
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
-
? findChildWithAccessibilityFocus() : null;
-
-
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;
-
if (newTouchTarget == null && childrenCount != 0) {
-
final float x = ev.getX(actionIndex);
-
final float y = ev.getY(actionIndex);
-
// Find a child that can receive the event.
-
// Scan children from front to back.
-
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
-
final boolean customOrder = preorderedList == null
-
&& isChildrenDrawingOrderEnabled();
-
final View[] children = mChildren;
-
for (int i = childrenCount - 1; i >= 0; i--) {
-
final int childIndex = getAndVerifyPreorderedIndex(
-
childrenCount, i, customOrder);
-
final View child = getAndVerifyPreorderedView(
-
preorderedList, children, childIndex);
-
-
// If there is a view that has accessibility focus we want it
-
// to get the event first and if not handled we will perform a
-
// normal dispatch. We may do a double iteration but this is
-
// safer given the timeframe.
-
if (childWithAccessibilityFocus != null) {
-
if (childWithAccessibilityFocus != child) {
-
continue;
-
}
-
childWithAccessibilityFocus = null;
-
i = childrenCount - 1;
-
}
-
-
if (!canViewReceivePointerEvents(child)
-
|| !isTransformedTouchPointInView(x, y, child, null)) {
-
ev.setTargetAccessibilityFocus(false);
-
continue;
-
}
-
-
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;
-
}
-
-
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();
-
newTouchTarget = addTouchTarget(child, idBitsToAssign);
-
alreadyDispatchedToNewTouchTarget = true;
-
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();
-
}
-
-
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;
-
}
-
}
-
}
-
-
// Dispatch to touch targets.
-
if (mFirstTouchTarget == null) {
-
// No touch targets so treat this as an ordinary view.
-
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;
-
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
-
handled = true;
-
} else {
-
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;
-
}
-
我们日志一行一行来解析:
第1行,DOWN事件(action = 0)进入ViewGroup的dispatchTouchEvent内部,先调用了onInterceptTouchEvent(关键信息已用红色标注),结果onInterceptTouchEvent返回false,不拦截事件。为什么说onInterceptTouchEvent默认会返回false,我们看一下onInterceptTouchEvent的代码:
-
public boolean onInterceptTouchEvent(MotionEvent ev) {
-
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
-
&& ev.getAction() == MotionEvent.ACTION_DOWN
-
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
-
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
-
return true;
-
}
-
return false;
-
}
首先if语句的第一个条件是事件来源于InputDevice.SOURCE_MOUSE(鼠标),因此onInterceptTouchEvent肯定不会返回true,在手机上用手指点肯定返回的是false。
为了阅读方便,还是把日志复制一份在这里。
-
eventTest: MyInterceptTouch:action--0,result=false,view:MyLayout
-
eventTest: onTouchListener:acton--0 ,result=false----view:com.example.relativelayoutevent.MyButton
-
eventTest: MyonTouchEvent:action--0,result=true,view:MyButton
-
eventTest: MydispatchTouchEvent:action--0,result=true,view:MyButton
-
eventTest: MyLayoutdispatchTouch:action--0,result=true,view:MyLayout
-
eventTest: MyInterceptTouch:action--1,result=false,view:MyLayout
-
eventTest: onTouchListener:acton--1 ,result=false,view:MyButton
-
eventTest: MyonTouchEvent:action--1,result=true,view:MyButton
-
eventTest: MydispatchTouchEvent:action--1,result=true,view:MyButton
-
eventTest: MyLayoutdispatchTouch:action--1,result=true,view:MyLayout
-
eventTest: OnClickListener----view:com.example.relativelayoutevent.MyButton
第2行日志:将事件传递给子View的dispatchTouchEvent:
第1行日志已表示ViewGroup不拦截事件,而是继续将DOWN事件(action= 0 )传递给子View(MyButton),于是从ViewGroup.dispatchTouchEvent 内部调用了 child.dispatchTouchEvent,即(MyButton extends View) view.dispatchTouchEvent,于是接下来的流程就和View事件分发一样了,先执行onTouchListener--onTouch,这里我们在MainActivity里已经强制返回false,以便能执行onTouchEvent,同时打印出了第2行日志。
第3行日志:执行MyButton的onTouchEvent,打印出日志。
第4行,MyButton的dispatchTouchEvent内部执行完onTouchEvent后,递归返回后打印出了日志。
第5行,MyLayout的dispatchTouchEvent,连续调用1,2,3,4步,递归返回后打印出了日志。
其实从第2行日志-----第4行日志 都是子view的dispatchTouchEvent在处理事件,我们来看一下MyLayout的dispatchTouchEvent是在哪里将事件传递给了子View的dispatchTouchEvent;请翻阅到ViewGroup源码,我已用红色标注:
for (int i = childrenCount - 1; i >= 0; i--) {
(dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)
........
}
这个for循环遍历ViewGroup内部的各个子View,判断哪一个被点击了,如果被点击了就执行dispatchTransformedTouchEvent,而这个函数里又调用了child.dispatchTouchEvent,即(MyButton)View的事件分发将要开始执行:dispatchTouchEvent---->onTouchListener(onTouch返回false)------> onTouchEvent---->onClick。
。dispatchTransformedTouchEvent函数的源码如下:
-
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;
-
}
-
-
。。。。。。
-
}
内部调用了handled = child.dispatchTouchEvent(event); 而handler会默认为true, 因此dispatchTransformedTouchEvent返回true,最终也会使ViewGroup.dispatchTouchEvent会默认返回true.
第6行至第10行日志:UP事件又将上述过程1-5重复了一遍,在此不再赘述。
当onInterceptTouchEvent返回false时,DOWN事件与UP事件最终都传递给了MyButton去处理,所以MyButton的onClick得以执行,而RelativeLayout的onClick不会执行。
2.2 onInterceptTouchEvent返回true
其它代码不变,只 修改MyLayout的onInterceptTouchEvent,让其返回true.表示拦截事件,这时我们再点击Button试试。
MyLayout代码及点击Button时的日志如下:
MyLayout.java:
-
package com.example.relativelayoutevent;
-
-
import android.content.Context;
-
import android.util.AttributeSet;
-
import android.util.Log;
-
import android.view.MotionEvent;
-
import android.widget.RelativeLayout;
-
-
public class MyLayout extends RelativeLayout {
-
public MyLayout(Context context) {
-
super(context);
-
}
-
-
public MyLayout(Context context, AttributeSet attrs) {
-
super(context, attrs);
-
}
-
-
public MyLayout(Context context, AttributeSet attrs, int defStyleAttr) {
-
super(context, attrs, defStyleAttr);
-
}
-
-
-
@Override
-
public boolean onTouchEvent(MotionEvent event) {
-
boolean onTouchEventResult = super.onTouchEvent(event);
-
-
Log.i("eventTest", "MyLayoutonTouchEvent:action--"+event.getAction()+
-
",result="+onTouchEventResult+",view:MyLayout");
-
return onTouchEventResult;//默認返回true
-
}
-
-
@Override
-
public boolean dispatchTouchEvent(MotionEvent event) {
-
boolean dispatchResult = super.dispatchTouchEvent(event); //默认返回true
-
Log.i("eventTest", "MyLayoutdispatchTouch:action--"+event.getAction()+
-
",result="+dispatchResult+",view:MyLayout");
-
return dispatchResult;
-
}
-
-
@Override
-
public boolean onInterceptTouchEvent(MotionEvent event) {
-
boolean onInterceptResult = super.onInterceptTouchEvent(event); //默认返回false
-
-
onInterceptResult = true;
-
Log.i("eventTest", "MyInterceptTouch:action--"+event.getAction()+
-
",result="+onInterceptResult+",view:MyLayout");
-
-
-
return onInterceptResult;
-
-
}
-
}
让ViewGroup的onInterceptTouchEvent返回false.
点击Button时的日志如下:
-
eventTest: MyInterceptTouch:action--0,result=true,view:MyLayout
-
eventTest: onTouchListener:acton--0 ,result=false----view:com.example.relativelayoutevent.MyLayout
-
eventTest: MyLayoutonTouchEvent:action--0,result=true,view:MyLayout
-
eventTest: MyLayoutdispatchTouch:action--0,result=true,view:MyLayout
-
eventTest: onTouchListener:acton--1 ,result=false----view:com.example.relativelayoutevent.MyLayout
-
eventTest: MyLayoutonTouchEvent:action--1,result=true,view:MyLayout
-
eventTest: MyLayoutdispatchTouch:action--1,result=true,view:MyLayout
-
eventTest: OnClickListener----view:com.example.relativelayoutevent.MyLayout
从日志上观察,点击MyButton时,竟然MyLayout响应了单击函数,也就是说界面空白处响应了onClick!
好,我们还是来一步一步分析日志:
第1行日志:
DOWN事件(action = 0)进入ViewGroup的dispatchTouchEvent内部,先调用了onInterceptTouchEvent(关键信息已用红色标注),结果onInterceptTouchEvent返回true,拦截截事件,MyButton将不处理DOWN事件,而是MyLayout自己处理。于是打印出了第2行日志。
第2行日志:DOWN事件由MyLayout自己处理,所以先执行了MyLayout的onTouchListener.onTouch,我们在这里强行在MainActivity中返回false.以便MyLayout能执行到onTouchEvent.。
第3行日志:执行MyLayout的onTouchEvent,打印出日志。
第4行,MyLayout的dispatchTouchEvent内部执行完onTouchEvent后,递归返回后打印出了日志。
我们发现从1-4,DOWN事件一直由MyLayout(ViewGroup)来处理,而且处理流程与View的事件分发一样:
dispatchTouchEvent---->onTouchListener(onTouch返回false)------> onTouchEvent
也就是说这时可以把MyLayout(ViewGroup)看作一个View了。为何会这样,我们还是翻上去看一下ViewGroup源,上面已经用红色标注:
-
// Dispatch to touch targets.
-
if (mFirstTouchTarget == null) {
-
// No touch targets so treat this as an ordinary view.
-
handled = dispatchTransformedTouchEvent(ev, canceled, null,
-
TouchTarget.ALL_POINTER_IDS);
-
}
这个就是子View没有处理DOWN事件,mFirstTouchTarget 就是null,然后执行
dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);这次第三个参数child被赋值了null,所以dispatchTransformedTouchEvent最终会执行super.dispatchTouchEvent, ViewGroup的super就是View。所以后续把ViewGroup的事件分发按照View的事件分发来执行,然后就执行了MyLayout的onTouchListener.onTouc与MyLayout的onTouchListener.onTouchEvent。
Tip:mFirstTouchTarget 是在这行代码 : newTouchTarget = addTouchTarget(child, idBitsToAssign); 中的addTouchTarget函数内部被赋值的,这行代码的执行恰好是在子View成功处理了Down事件的条件下执行的。
第5,6,7行日志是UP事件的日志,同上面的Down事件一样,都交由ViewGroup来执行,最终还是走的是View的事件分发流程。只是,只是UP事件少调用了一个onInterceptTouchEvent函数,因为在Down事件的时候已经return true表示拦截事件,那么UP事件就不用再调用了,随之也是被ViewGroup拦截。
到此,我们就分析完ViewGroup的事件分发机制了。配套Demo实验源码:https://download.csdn.net/download/gaoxiaoweiandy/11164333
文章来源: blog.csdn.net,作者:冉航--小虾米,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/gaoxiaoweiandy/article/details/89882161
- 点赞
- 收藏
- 关注作者
评论(0)