看了许多文章,自己也把源码跟着翻了一次,写了两篇文章,感觉事件分发到了View这边几乎是类似的,但是,这篇文章的重点不仅仅是事件分发,还有几个我一直不太了解的函数,所以,解决掉这几个函数才是本文的重点。
之前写的两篇文章分别是:
需要解决的函数: public boolean dispatchTouchEvent(MotionEvent event){...}
public boolean performClick() {...}
public boolean onTouchEvent(MotionEvent event) {...}
public interface OnTouchListener { boolean onTouch(View v, MotionEvent event);}
public interface OnClickListener {void onClick(View v);}
直接看dispatchTouchEvent()的源码,现在可以直接看重点,去掉那些繁文缛节。
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//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;
}
}
onFilterTouchEventForSecurity之前已经提过,这段代码有三个重点需要关注下:ListenerInfo类和先调用的onTouchListener.onTouch()后调用的view.onTouchEvent()
ListenerInfo是View里面的一个内部类,定义了一系列的Listener
static class ListenerInfo {
/**
* Listener used to dispatch focus change events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnFocusChangeListener mOnFocusChangeListener;
/**
* Listeners for layout change events.
*/
private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
protected OnScrollChangeListener mOnScrollChangeListener;
/**
* Listener used to dispatch click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
public OnClickListener mOnClickListener;
/**
* Listener used to dispatch long click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnLongClickListener mOnLongClickListener;
private OnKeyListener mOnKeyListener;
private OnTouchListener mOnTouchListener;
private OnHoverListener mOnHoverListener;
}
接下来再来过下onTouchEvent()函数的实现,分段摘录源码:
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
在return上面有一个注释:即使不可用的视图但是能点击(包括长按),依然会消费掉事件,只是不做什么响应处理罢了。
接下来涉及返回(包含return 语句)的是代理,即:如果有代理,便会执行代理的onTouchEvent()方法。所以忽略它继续看后面的源码,便是一个onTouchEvent()函数的核心:switch-case语句,对各类事件类型进行分别处理。
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
...
case MotionEvent.ACTION_DOWN:
...
case MotionEvent.ACTION_CANCEL:
...
case MotionEvent.ACTION_MOVE:
...
return true;
}
return false;
通过源码可知,If判断成立时(View可以点击或者长按之类的),那么View肯定会消费掉事件,并返回true;如果If判断不成立,则不会消费掉事件,返回false;
接下来我们重点,关注ACTION_UP类型的事件,为什么呢?因为里面涉及我之前感到困惑的performClick()函数。拉下源码:
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是否是pressed状态,即按下状态,如果是按下状态就会触发performClick()方法。
//
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
判断是否有onClickListener的回调,有则调用。显然,performClick()的执行在要onClickListener的onClick()函数之前。
是时候下个结论了:
首先会执行dispatchTouchEvent(); 然后是onTouchListener中的onTouch()函数;接着是onTouchEvent()函数,其次是performClick()函数和onClickListener的onClick()函数。
程序验证:
第一步:自定义视图
public class CrshButton extends View{
public CrshButton(Context context) {
super(context);
}
public CrshButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CrshButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.e("crsh","dispatchTouchEvent - event = " + event.getAction());
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("crsh","onTouchEvent - event = " + event.getAction());
return super.onTouchEvent(event);
}
@Override
public boolean performClick() {
Log.e("crsh","performClick()");
return super.performClick();
}
}
第二步:引入,并设置监听
crshBtn.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.e("crsh","setOnTouchListener -- onTouch event = " + event.getAction());
return false;
}
});
crshBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("crsh","setOnClickListener -- onClick");
}
});
第三步:打印Log结果
01-07 15:37:17.140 9811-9811/com.crsh.test.learnviewpager E/crsh: dispatchTouchEvent - event = 0
01-07 15:37:17.140 9811-9811/com.crsh.test.learnviewpager E/crsh: setOnTouchListener -- onTouch event = 0
01-07 15:37:17.140 9811-9811/com.crsh.test.learnviewpager E/crsh: onTouchEvent - event = 0
01-07 15:37:17.154 9811-9811/com.crsh.test.learnviewpager E/crsh: dispatchTouchEvent - event = 2
01-07 15:37:17.154 9811-9811/com.crsh.test.learnviewpager E/crsh: setOnTouchListener -- onTouch event = 2
01-07 15:37:17.154 9811-9811/com.crsh.test.learnviewpager E/crsh: onTouchEvent - event = 2
01-07 15:37:17.171 9811-9811/com.crsh.test.learnviewpager E/crsh: dispatchTouchEvent - event = 2
01-07 15:37:17.171 9811-9811/com.crsh.test.learnviewpager E/crsh: setOnTouchListener -- onTouch event = 2
01-07 15:37:17.171 9811-9811/com.crsh.test.learnviewpager E/crsh: onTouchEvent - event = 2
01-07 15:37:19.117 9811-9811/com.crsh.test.learnviewpager E/crsh: dispatchTouchEvent - event = 2
01-07 15:37:19.117 9811-9811/com.crsh.test.learnviewpager E/crsh: setOnTouchListener -- onTouch event = 2
01-07 15:37:19.118 9811-9811/com.crsh.test.learnviewpager E/crsh: onTouchEvent - event = 2
01-07 15:37:19.120 9811-9811/com.crsh.test.learnviewpager E/crsh: dispatchTouchEvent - event = 1
01-07 15:37:19.120 9811-9811/com.crsh.test.learnviewpager E/crsh: setOnTouchListener -- onTouch event = 1
01-07 15:37:19.120 9811-9811/com.crsh.test.learnviewpager E/crsh: onTouchEvent - event = 1
01-07 15:37:19.121 9811-9811/com.crsh.test.learnviewpager E/crsh: performClick()
01-07 15:37:19.121 9811-9811/com.crsh.test.learnviewpager E/crsh: setOnClickListener -- onClick
MotionEvent的值看一下:
/**
* Constant for {@link #getActionMasked}: A pressed gesture has started, the
* motion contains the initial starting location.
* <p>
* This is also a good time to check the button state to distinguish
* secondary and tertiary button clicks and handle them appropriately.
* Use {@link #getButtonState} to retrieve the button state.
* </p>
*/
public static final int ACTION_DOWN = 0;
/**
* Constant for {@link #getActionMasked}: A pressed gesture has finished, the
* motion contains the final release location as well as any intermediate
* points since the last down or move event.
*/
public static final int ACTION_UP = 1;
/**
* Constant for {@link #getActionMasked}: A change has happened during a
* press gesture (between {@link #ACTION_DOWN} and {@link #ACTION_UP}).
* The motion contains the most recent point, as well as any intermediate
* points since the last down or move event.
*/
public static final int ACTION_MOVE = 2;
所以,完全符合我们之前下的结论:
先影响Down事件,中间影响多次Move事件,最后是Up事件。当是Up事件时触发performClick()函数,又因为重写了onClickListener监听,所以回调onClick()函数。
完美!