您的位置:首页 > 其它

View 事件处理

2016-03-25 10:47 405 查看
让我自己看源码是看不懂的,还好有这么多大神。

1. dispatchTouchEvent

ViewGroup 在分发事件时会调用子 View 的 dispatchTouchEvent 方法。

对于 View (非 ViewGroup)来说,dispatchTouchEvent 方法被理解为 "handleEvent"  可能会更会合适。

该方法的返回值表示 Event 是否被 View 消费了,true 表示消费了。

public class MyView extends View {

public MyView(Context context) {
super(context);
}

public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}

public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.e("MyView", "dispatchTouchEvent start" );
boolean result = super.dispatchTouchEvent(event);
Log.e("MyView", "dispatchTouchEvent end " + result);
return result;
}

}

点击后日志如下:

03-25 10:48:00.296 18114-18114/com.unicorn.viewtry E/MyView: dispatchTouchEvent start

03-25 10:48:00.296 18114-18114/com.unicorn.viewtry E/MyView: dispatchTouchEvent end false

基本 View 并没有消费掉 Event。

2. dispatchTouchEvent 源码

来看看 View 是如何 handle event 的,重点的部分可能就这么点,源码是 6.0 版本的。

boolean result = false;
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;
}
return result;


3. OnTouchListener

添加 OnTouch 事件
findViewById(R.id.myView).setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.e("MyView", "onTouchListener onTouch start");
Log.e("MyView", "onTouchListener onTouch end " + false); // 不消费事件
return false;
}
});
点击后日志如下:
03-25 11:46:56.121 31150-31150/com.unicorn.viewtry E/MyView: dispatchTouchEvent start

03-25 11:46:56.121 31150-31150/com.unicorn.viewtry E/MyView: onTouchListener onTouch start

03-25 11:46:56.121 31150-31150/com.unicorn.viewtry E/MyView: onTouchListener onTouch end false

03-25 11:46:56.121 31150-31150/com.unicorn.viewtry E/MyView: dispatchTouchEvent end false
如果

findViewById(R.id.myView).setEnabled(false);
那么 OnTouch 事件不会被触发。

4. onTouchEvent

如果 OnTouchListener 没消费掉事件,那么就轮到 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);
}
注释上说,disabled 的 View 依旧会消费事件,即使它什么也没做。
只要 View 是 clickable 或者 long_clickable 的。
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
假如设置了 TouchDelegate ,那么由它来负责。
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {

// 之后分析
return true;
}

return false;
如果 View 是 clickable 或者 long_clickable 的,则消费事件。

点击日志如下:
03-25 12:24:17.964 20423-20423/com.unicorn.viewtry E/MyView: dispatchTouchEvent start

03-25 12:24:17.964 20423-20423/com.unicorn.viewtry E/MyView: onTouchListener onTouch start

03-25 12:24:17.964 20423-20423/com.unicorn.viewtry E/MyView: onTouchListener onTouch end false

03-25 12:24:17.964 20423-20423/com.unicorn.viewtry E/MyView: onTouchEvent start

03-25 12:24:17.964 20423-20423/com.unicorn.viewtry E/MyView: onTouchEvent end false

03-25 12:24:17.964 20423-20423/com.unicorn.viewtry E/MyView: dispatchTouchEvent end false
默认的 View 是不可点击的,所以事件还没被消费。

5. OnLongClickListener

case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;

if (performButtonActionOnTouchDown(event)) {
break;
}

// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();

// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0);
}
break;

看 checkForLongClick 函数:

private void checkForLongClick(int delayOffset) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
mHasPerformedLongPress = false;

if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.rememberWindowAttachCount();
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
}
这里 post 了一个 500 毫秒的延时事件。
private final class CheckForLongPress implements Runnable {
private int mOriginalWindowAttachCount;

@Override
public void run() {
if (isPressed() && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
if (performLongClick()) {
mHasPerformedLongPress = true;
}
}
}

public void rememberWindowAttachCount() {
mOriginalWindowAttachCount = mWindowAttachCount;
}
}
如果 500 毫秒后 View 依旧是 pressed 状态,则执行 performLongClick 函数,并将返回结果保存在 mHasPerformedLongPress 中。
public boolean performLongClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);

boolean handled = false;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLongClickListener != null) {
handled = li.mOnLongClickListener.onLongClick(View.this);
}
if (!handled) {
handled = showContextMenu();
}
if (handled) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
return handled;
}
在 performLongClick 函数中 OnLongClick 事件被触发。

长按日志如下:
03-25 12:35:54.199 25735-25735/com.unicorn.viewtry E/MyView 0: dispatchTouchEvent start

03-25 12:35:54.199 25735-25735/com.unicorn.viewtry E/MyView 0: onTouchListener onTouch start

03-25 12:35:54.199 25735-25735/com.unicorn.viewtry E/MyView 0: onTouchListener onTouch end false

03-25 12:35:54.199 25735-25735/com.unicorn.viewtry E/MyView 0: onTouchEvent start

03-25 12:35:54.199 25735-25735/com.unicorn.viewtry E/MyView 0: onTouchEvent end true

03-25 12:35:54.199 25735-25735/com.unicorn.viewtry E/MyView 0: dispatchTouchEvent end true

03-25 12:35:54.700 25735-25735/com.unicorn.viewtry E/MyView: onLongClickListener onLongClick start

03-25 12:35:54.700 25735-25735/com.unicorn.viewtry E/MyView: onLongClickListener onLongClick end false
长按事件触发距离事件分发结束相差约 500 毫秒。

因为设置了 OnLongClickListener,View 成为 long_clickable 的,所以 onTouchEvent 消费了事件。
onLongClick 的返回值只和是否触发 OnClickListener 有关。

6. OnClickListener

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;
如果 OnLongClickListener 被执行且返回 true,那么 perfermClick 函数不会执行。
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;
}


长按日志如下:

03-25 13:25:22.730 15131-15131/com.unicorn.viewtry E/MyView 0: dispatchTouchEvent start

03-25 13:25:22.730 15131-15131/com.unicorn.viewtry E/MyView 0: onTouchListener onTouch start

03-25 13:25:22.731 15131-15131/com.unicorn.viewtry E/MyView 0: onTouchListener onTouch end false

03-25 13:25:22.731 15131-15131/com.unicorn.viewtry E/MyView 0: onTouchEvent start

03-25 13:25:22.731 15131-15131/com.unicorn.viewtry E/MyView 0: onTouchEvent end true

03-25 13:25:22.731 15131-15131/com.unicorn.viewtry E/MyView 0: dispatchTouchEvent end true

03-25 13:25:23.230 15131-15131/com.unicorn.viewtry E/MyView: onLongClickListener onLongClick start

03-25 13:25:23.230 15131-15131/com.unicorn.viewtry E/MyView: onLongClickListener onLongClick end false

03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: dispatchTouchEvent start

03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: onTouchListener onTouch start

03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: onTouchListener onTouch end false

03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: onTouchEvent start

03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: onTouchEvent end true

03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: dispatchTouchEvent end true

03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView: onClickListener onClick start

03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView: onClickListener onClick end false

这里有个小细节,当 Down event(0 和 1 分别代表 Down 和 Up) 被 View 消费后。

View 才会收到对应的 Up event,这就涉及到 ViewGroup 是如何分发事件了。

推荐:

http://blog.csdn.net/dmk877/article/details/48781845
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: