目录
- 问题1: 为什么MyViewPager中的onInterceptTouchEvent返回为true,上下不能滑动
- 问题2: 为什么MyViewPager中的onInterceptTouchEvent返回为false,左右不能滑动
- 问题3: 为什么MyViewPager中的onInterceptTouchEvent返回为false,并且MyListView中实现dispatchTouchEvent返回为false,上下不能滑动,而左右可以滑动
- 问题4:为什么会产生冲突
- 问题5:为什么MyViewPager中的onInterceptTouchEvent 在处理冲突的时候,为什么一定要在down的时候返回false
ViewGroup.java
@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;
// 第一块代码:是否拦截子View
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;
// 第二块代码:遍历子View,选择分发给哪个View处理事件
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) { //childrenCount 表示子View的个数
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(); // buildTouchDispatchChildList()给子View进行布局
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;
}
// 判断点击是否在View上
if (!child.canReceivePointerEvents() //执行View.canReceivePointerEvents() 查看属性动画是否在点击切面
|| !isTransformedTouchPointInView(x, y, child, null)) { //isTransformedTouchPointInView() 点击位置是否在View上
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);
// dispatchTransformedTouchEvent方法如果返回false,遍历下一个view,如果返回为true,表示该子View响应该事件
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();
/**
* 执行addTouchTarget()这条语句可以得到
* newTouchTarget == mFirstTouchTarget != null
* target.next == null
* /
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.
// 第三块代码:如果没有子View处理事件,询问自己是否处理事件,否则就是子View处理
if (mFirstTouchTarget == null) { //自己处理
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else { //子View处理
// 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; //在执行addTouchTarget()这条语句target.next== null
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) { //询问子View是否处理事件
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;
}
ViewGroup类中dispatchTouchEvent()方法流程中可以分成三个模块进行理解:
- 第一块代码:是否拦截子View
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
... ...
} else {
... ...
}
- 第二块代码:遍历子View,选择分发给哪个View处理事件
if (!canceled && !intercepted) {
... ...
}
- 第三块代码:如果没有子View处理事件,询问自己是否处理事件,否则就是子View处理(down ---- 直接返回true,move ---- 询问子View是否处理事件)
if (mFirstTouchTarget == null) {
... ...
} else {
... ...(子View处理事件)
}
用例:在ViewPager(左右滑动)控件上包含一个MyListView(上下滑动)控件,两个控件嵌套在一起,左右滑动和上下滑动会出现什么情况呢?
问题1~4都是Down事件,问题5是Move事件
问题1: 为什么MyViewPager中的onInterceptTouchEvent返回为true,上下不能滑动
MyListView.java
public class MyListView extends ListView {
public MyListView(Context context) {
super(context);
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
MyViewPager.java
public class MyViewPager extends ViewPager {
private int mLastX, mLastY;
public MyViewPager(@NonNull Context context) {
super(context);
}
public MyViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return true;
}
}
答:因为Down事件---->执行到第一个代码模块中,disallowIntercept的值为false,则会执行onInterceptTouchEvent()拦截事件,因为MyViewPager中重写了onInterceptTouchEvent()方法,并返回true,导致第二个代码模块不会执行,即不会遍历子View,事件也就不会进行分发,从而执行到第三个代码模块,走到handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); 方法询问自己是否处理事件
View Group.java
/**
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
*/
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
if (child == null) {
handled = super.dispatchTouchEvent(event); //调用父类View的dispatchTouchEvent进行处理
} else {
}
}
问题2: 为什么MyViewPager中的onInterceptTouchEvent返回为false,左右不能滑动
MyListView.java
public class MyListView extends ListView {
public MyListView(Context context) {
super(context);
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
MyViewPager.java
public class MyViewPager extends ViewPager {
private int mLastX, mLastY;
public MyViewPager(@NonNull Context context) {
super(context);
}
public MyViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return false;
}
}
答:因为Down事件---->执行到第一个代码模块中,disallowIntercept的值为false,则会执行onInterceptTouchEvent()拦截事件,因为MyViewPager中重写了onInterceptTouchEvent()方法,并返回false,从而进入第二个代码模块执行,遍历子View,选择分发给哪个子View处理事件。
只有Down事件的时候才会分发事件给子View
第二块代码模块:dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign) 询问子View是否处理事件–>child.dispatchTouchEvent(transformedEvent); 子View处理事件
newTouchTarget = addTouchTarget(child, idBitsToAssign); -->执行这条语句可以得到:
newTouchTarget == mFirstTouchTarget != null
target.next == null
alreadyDispatchedToNewTouchTarget = true;
ViewGroup.java
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
由于mFirstTouchTarget != null,走第三个代码模块的else语句中
---->handled = true; 执行返回true
问题3: 为什么MyViewPager中的onInterceptTouchEvent返回为false,并且MyListView中实现dispatchTouchEvent返回为false,上下不能滑动,而左右可以滑动
MyListView.java
public class MyListView extends ListView {
public MyListView(Context context) {
super(context);
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return false;
}
}
MyViewPager.java
public class MyViewPager extends ViewPager {
private int mLastX, mLastY;
public MyViewPager(@NonNull Context context) {
super(context);
}
public MyViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return false;
}
}
答:在问题2的流程中
第一块代码模块:不拦截子View,会让第二块代码模块执行
第二块代码模块:dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign) 询问子View是否处理事件–> 子View不处理事件—这个模块什么都没做
第三个代码模块:handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); 方法询问自己是否处理事件
问题4:为什么会产生冲突
答:事件只有一个View处理
dispatchTransformedTouchEvent()
问题5:为什么MyViewPager中的onInterceptTouchEvent 在处理冲突的时候,为什么一定要在down的时候返回false
采用内部拦截法:这样上下、左右都可以滑动
MyListView.java
public class MyListView extends ListView {
public MyListView(Context context) {
super(context);
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event,getAction()) {
case MotionEvent.ACTION_DOWN: {
getParent().requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
}
MyViewPager.java
public class MyViewPager extends ViewPager {
private int mLastX, mLastY;
public MyViewPager(@NonNull Context context) {
super(context);
}
public MyViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
super.onInterceptTouchEvent(event);
return false;
}
return true;
}
}
答: 在MyListView调用MotionEvent.ACTION_DOWN事件中的getParent().requestDisallowInterceptTouchEvent(true);
以及在MyViewPager 中的onInterceptTouchEvent
if (event.getAction() == MotionEvent.ACTION_DOWN) {
super.onInterceptTouchEvent(event);
return false;
}
会使ViewGroup的dispatchTouchEvent中第一个代码模块的 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; disallowIntercept 这个值为true,从而onInterceptTouchEvent()拦截事件不会执行,intercepted = false;
内部拦截法 —> 由子View处理事件 -->要保证第二块代码模块必须执行
外部拦截法 —> 事件交由 ViewGroup 根据情况分发