在Android中给我们提供了形形色色的控件供我们来呈现一个完美的UI,但是我们在开发中总得自己写或者借鉴开源库来表现一个复杂的UI特别在需要涉及滑动的时候就要遇到手势分发的问题我们都知道在dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent的方法中处理,可是我们自己需要问问自己真的知道这三个方法的意思和执行流程么,认真阅读了http://blog.csdn.net/yanbober 工匠若水的文章,特地记录一下自己在源码角度的理解。
View的手势分发流程
首先在View中处理手势我们提前是这仅仅只是一个View而不是ViewGroup。
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
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;
}
这些方法的源码都是很长很长的一大截代码我阅读代码的时候没有一行一字的阅读,首先需要先了解ListenerInfo这个类的作用是存放OnTouchListener,OnClickListener等点击事件,每个方法的源码都类似下面的:
public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l;
}
所以在源码中li.mOnTouchListener.onTouch(this, event)这句话在前提mOnTouchListener不为空且改View是enable的时候会调用OnTouchListener的onTouch方法从而导致result为true所以系统onTouchEvent就无法被调用。然后你就会发onCLickListener居然没有回调,我心里也是一万只草泥马,所以我继续点onTouchEvent进去看看为什么呢?
public boolean onTouchEvent(MotionEvent event) {
...
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
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();
}
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;
...
}
源码太长了,所以不想都拷贝出来,就看这个比较重要的地方就好了。当这个View是CLICKABLE或者LONG_CLICKABLE的时候就会触发然后就会执行perforClick方法
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
}
可以看出调用了OnclickListener中的onClick方法。
所以总结一下View的手势分发,如果一个View设置了onTouchListener方法且返回true那么就不会调用onTOuchEvent方法了,且onClickListener是在onTouchEvent中的ACTION_UP中被触发不过前提是View是CLICKABLE的。
View的整体流程是:dispathTouchEvent-onTouch-onTouchEvent-onClick 当然要每个方法都触发每个条件都得满足才行。
ViewGroup的手势分发
ViewGroup的手势分发可比View要麻烦的多,且多了个onInterceptTouchEvent方法源码又特别长看起来真的好累好难懂。首先仍然是先从dispatchTouchEvent方法出发首先会看到
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;
}
代码里面的disallowIntercept来自requestDisallowInterceptTouchEvent方法设置的,intercepted代表是不是要拦截这个手势且intercepted来自onInterceptTouchEvent的返回值,如果onInterceptTouchEvent返回true的话表示拦截那么mFirstTouchTarget就会为null从而代码走向了dispatchTransformedTouchEvent这个方法
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
注意第三个参数为null就会执行handled = super.dispatchTouchEvent(event)从而执行上一层的dispathTouchEvent方法,其实代码非常的长只是截取其中调用的那些代码。
第一种,如果在onInterceptTouchEvent的ACTION_DOWN返回true的话其实就是上面的流程,最后没有执行改ViewGroup的onTouchevent方法而是把手势往父类的ViewGroup或者Activity层分发过去。
第二种,如果在onInterceptTouchEvent的ACTION_MOVE返回true的话,因为在ACTION_DOWN时候mFirstTouchTarget不为空了,就会执行下面那段代码
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;
}
其实最后还是跳入dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)这个方法只是参数为空了所以会执行handled = child.dispatchTouchEvent(event)就等于跟View的执行过程类似在满足条件的时候就会执行onTouEvent方法并且把ACTION_MOVE传递进来,这种情况也是最常见的:即在onInterceptTouchEvent的ACTION_MOVE返回true然后在onTouchEvent做手势滑动处理。
第三种,在ACTION_UP中返回true的话如果mFirstTouchTarget不为null的话就会执行触摸View的dispathTouchEvent方法从而不执行onTouchEvent方法,如果mFirstTouchTarget为空的话就相反且会讲ACTION_UPa分发到onTouchEvent中。
当做笔记一样记录一下流程让自己对手势分发更懂得原理了。