您的位置:首页 > 移动开发 > Android开发

Android事件分发05——View的onTouchEvent

2017-04-20 13:17 411 查看
Android事件分发05View的onTouchEvent
一onTouchEvent的源码

二动作处理之前

三按下的判断

四MotionEventACTION_DOWN

五MotionEventACTION_MOVE

六MotionEventACTION_UP

七MotionEventACTION_CANCEL

八流程图

Android事件分发05——View的onTouchEvent

上次我们分析了view的dispatchTouchEventAndroid事件分发04——View的dispatchTouchEvent ),说到事件分发到view的dispatchTouchEvent后,会先到触摸监听的onTouch中,然后再到view的onTouchEvent中。现在我们一起来看看onTouchEvent

一、onTouchEvent的源码

public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();

//判断控件是否不可用
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);
}

//如果设置了Touch的代理,那么直接调用代理的 onTouchEvent,如下消费了,那么返回true
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}

//控件可点击的(包括点击、长按等)才操作,操作完成,直接返回true表示消费了
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
//获取是否具有 短暂延反馈的情况,就是down中包含在滚动控件中的情况,这个就是去除了滚动的情况
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);
}

//没有长按并且mIgnoreNextUpEvent==false
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) {//如果按下了
//添加一个延时任务,这个任务把按下标记设为false
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
//任务调用失败,直接执行这个任务
mUnsetPressedState.run();
}
//移除敲击事件
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;

case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;

if (performButtonActionOnTouchDown(event)) {
break;
}

// Walk up the hierarchy to determine if we're inside a scrolling container.
//获取控件是否包含在可滚动的view中
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;

case MotionEvent.ACTION_CANCEL:
setPressed(false);//按下设为false
removeTapCallback();//取消敲击
removeLongPressCallback();//取消长按
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;

case MotionEvent.ACTION_MOVE:
//状态改变的处理和分发
drawableHotspotChanged(x, y);

// Be lenient about moving outside of buttons
//移动的点没在控件内
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();//移除敲击回调
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();//移除长按

setPressed(false);//按下设置为空
}
}
break;
}

return true;
}

return false;
}


二、动作处理之前

动作处理之前 : 2—25行

final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();

//判断控件是否不可用
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);
}

//如果设置了Touch的代理,那么直接调用代理的 onTouchEvent,如下消费了,那么返回true
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}


这里面首先判断空寂是否不可用,不可用的情况下,我们还是会消费消费事件的,只是不响应而已。

如果不满足上面的情况,我们接着判断这个控件有没有添加了触摸的代理,如果添加了,那么我们直接调用代理的onTouchEvent方法,如果这个方法消费了事件,那么直接放回true。负责继续往下走,走入到action处理的核心。

三、按下的判断

对应:28–151行

//控件可点击的(包括点击、长按等)才操作,操作完成,直接返回true表示消费了
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {

....

return true;
}


这里面判断是否可以点击,只有可以点击才处理,进入后,处理完成直接返回true,代表消费了。

四、MotionEvent.ACTION_DOWN

case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;

if (performButtonActionOnTouchDown(event)) {
break;
}

// Walk up the hierarchy to determine if we're inside a scrolling container.
//获取控件是否包含在可滚动的view中
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;


4-6行:这里面根据performButtonActionOnTouchDown方法的返回值判断是否对down动作还要处理,那么我们看看performButtonActionOnTouchDown方法

protected boolean performButtonActionOnTouchDown(MotionEvent event) {
if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE &&
(event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
showContextMenu(event.getX(), event.getY(), event.getMetaState());
mPrivateFlags |= PFLAG_CANCEL_NEXT_UP_EVENT;
return true;
}
return false;
}


这个方法处理的是,鼠标的动作。是鼠标,并且点击了右键,才返回true。因此我们可以知道我们这里面放回的false。

第10行:
boolean isInScrollingContainer = isInScrollingContainer();
调用了 isInScrollingContainer()方法

public boolean isInScrollingContainer() {
ViewParent p = getParent();
while (p != null && p instanceof ViewGroup) {
if (((ViewGroup) p).shouldDelayChildPressedState()) {
return true;
}
p = p.getParent();
}
return false;
}


这里面的处理,其实就是获取不断的获取父控件,判断父控件是否具有滚动的功能。

/**
* Return true if the pressed state should be delayed for children or descendants of this
* ViewGroup. Generally, this should be done for containers that can scroll, such as a List.
* This prevents the pressed state from appearing when the user is actually trying to scroll
* the content.
*
* The default implementation returns true for compatibility reasons. Subclasses that do
* not scroll should generally override this method and return false.
*/
public boolean shouldDelayChildPressedState() {
return true;
}


15–23行:如果这个控件包含在可滚动的控件内,我们需要延迟反馈。延迟100毫秒,后执行 mPendingCheckForTap 任务。

ViewConfiguration

private static final int TAP_TIMEOUT = 100;//毫秒
public static int getTapTimeout() {
return TAP_TIMEOUT;
}


View

private final class CheckForTap implements Runnable {
public float x;
public float y;

@Override
public void run() {
mPrivateFlags &= ~PFLAG_PREPRESSED;
setPressed(true, x, y);
checkForLongClick(ViewConfiguration.getTapTimeout());
}
}


这里面我们来看看checkForLongClick

private void checkForLongClick(int delayOffset) {
//是否具有长按标记
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
mHasPerformedLongPress = false;

if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
//编辑window添加的次数
mPendingCheckForLongPress.rememberWindowAttachCount();
//延时执行任务  500-100=400ms
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
}


这里面我们看看

CheckForLongPress

private final class CheckForLongPress implements Runnable {
private int mOriginalWindowAttachCount;

@Override
public void run() {
//是按下的,父控件部位空,自己标记的和view的添加的次数相同
if (isPressed() && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
if (performLongClick()) {
mHasPerformedLongPress = true;
}
}
}

public void rememberWindowAttachCount() {
mOriginalWindowAttachCount = mWindowAttachCount;
}
}


现在到了 performLongClick

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);
}
//如果没有消费事件,使用showContextMenu来处理
if (!handled) {
handled = showContextMenu();
}
if (handled) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
return handled;
}


23–28行:如果没有被包含在可滚动的控件中,那么直接显示反馈

总结按下的处理:其实就是被包含在滚动控件中,那么延时按下的返回,反之,立即反馈。

五、MotionEvent.ACTION_MOVE

case MotionEvent.ACTION_MOVE:
//状态改变的处理和分发
drawableHotspotChanged(x, y);

// Be lenient about moving outside of buttons
//移动的点没在控件内
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();//移除敲击回调
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();//移除长按

setPressed(false);//按下设置为空
}
}
break;


这里面的操作,其实就是,如果点没有落在view上,那么取消我们的处理。负责不操作。

pointInView在以后的文章中分析

六、MotionEvent.ACTION_UP

case MotionEvent.ACTION_UP:
//获取是否具有 短暂延反馈的情况,就是down中包含在滚动控件中的情况,这个就是去除了滚动的情况
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);
}

//没有长按并且mIgnoreNextUpEvent==false
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) {//如果按下了
//添加一个延时任务,这个任务把按下标记设为false
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
//任务调用失败,直接执行这个任务
mUnsetPressedState.run();
}
//移除敲击事件
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;


这里面注释已经很详细了。主要看看一哈22–40行:

22行:mHasPerformedLongPress 这个参数,我们在LongPress的延迟处理中赋过值(CheckForLongPress ),也即是说,如果发生了长按这个值为true。

33–35行:这里面涉及到了PerformClick这个类,我们来看看

PerformClick

private final class PerformClick implements Runnable {
@Override
public void run() {
performClick();
}
}


这个看到这个里面直接执行performClick()

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;
}


performClick方法里面我们可以看到,主要就是判断我们有没有添加监听,如果添加了监听,调用监听的onClick
li.mOnClickListener.onClick(this);
)并且返回true,代表消费了事件,否则返回false。

36–38行:

这里if判断,主要是判断这个任务有没有添加到消息队列中去了,如果没有添加成功,那么直接调用performClick。我们可以看看 post方法

/**
* <p>Causes the Runnable to be added to the message queue.
* The runnable will be run on the user interface thread.</p>
*
* @param action The Runnable that will be executed.
*
* @return Returns true if the Runnable was successfully placed in to the
*         message queue.  Returns false on failure, usually because the
*         looper processing the message queue is exiting.
*
* @see #postDelayed
* @see #removeCallbacks
*/
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Assume that post will succeed later
ViewRootImpl.getRunQueue().post(action);
return true;
}


方法上说的很清楚了:如果Runnable成功添加到消息队列中返回true。失败返回false。

小小的总结:我们对up动作小小的总结一下,主要就是判断有没有按下,然后判断要不要执行onClick方法。

七、MotionEvent.ACTION_CANCEL

case MotionEvent.ACTION_CANCEL:
setPressed(false);//按下设为false
removeTapCallback();//取消敲击
removeLongPressCallback();//取消长按
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;


这个动作中,其实就是恢复一些初始化状态。

八、流程图

通过上面的分析,我们来个流程图总结一下。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐