事件分发机制3连
Activity的事件分发机制(https://blog.csdn.net/u013728021/article/details/102826314)
ViewGroup事件分发机制(https://blog.csdn.net/u013728021/article/details/102831095)
View的事件分发机制(https://blog.csdn.net/u013728021/article/details/102826446)
自定义View的套路
- 自定义属性
- onMeasure用于测量控件的大小
- onDraw用于绘制内容
- onTouchEvent用于用户交互
自定义ViewGroup的套路
- 自定义属性
- onMeasure for循环测量子View的宽高,最后计算自己的宽高
- onLayout 用于摆放子View,前提是View不是Gone的情况
- onDraw用于绘制自己,默认是不调用,如果要绘制就复写dispatchDraw
- onTouchEvent用于用户交互
View的两个重要方法
- dispatchTouchEvent(MotionEvent event) 负责事件分发,事件是从这个方法开始分发的。
- onTouchEvent(MotionEvent event) 触摸事件。
dispatchTouchEvent(MotionEvent event)源码分析
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
if (onFilterTouchEventForSecurity(event)) {
// ListenerInfo 封装事件监听的对象,里面封装了一系统列的事件监听回调对象
// 只要是设置了监听事件,这个对象就不为空。
ListenerInfo li = mListenerInfo;
// 如果给View设置了OnTouchListener,
// 控件是ENABLED状态,
// 并且onTouch方法返回true,就不会调用onTouchEvent方法
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED // 控件是enabled
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// 如果没有设置OnTouchListener,或者onTouch返回false用户没有消费事件
// 那么就会走View的onTouchEvent方法
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
如果View没有设置OnTouchListener,或者设置OnTouchListener且onTouch返回false,用户没有消费事件,那么就会走View的 onTouchEvent(event)方法
onTouchEvent(MotionEvent event)源码分析
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
//此处说明view自己实现的onTouchEvent不支持多点,支持多点的会使用getActionMasked()
final int action = event.getAction();
//是否是点击事件 CLICKABLE:点击 LONG_CLICKABLE:长按 CONTEXT_CLICKABLE:android版本的上下文对象
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
//如果是一个可点击的View,被设置disable,可以把事件消费点,防止事件再传递,如条目布局中的View被设置disable后,点击该View不会把事件在传回item.
return clickable;
}
if (mTouchDelegate != null) {
//增大我们的点击范围,防止View过小,现在基本没啥用途,应该通过UI设计解决
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//8.0新加的TOOLTIP,解释当前的View是干什么的,可以在View中设置 android:tooltipText=""设置,长按View会出现提示""设置的提示信息,所以这里和clickable平级“或”的关系
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
//摸到标记位的flag置空,就是设置成不再摸到标记位
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
//设置一个延时器,多少时间之后TOOLTIP不再显示
handleTooltipUp();
}
if (!clickable) {
//不可点击进行各种状态移除
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
//预按下的处理
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;
//如果 可以获取焦点 并且 在触控模式下获取焦点(如EditText) 并且 没有焦点
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)) {
performClickInternal();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
//为了用户体验
//预按下的时候,64ms的延迟后再设置为未按下状态。
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
//主要在ACTION_DOWN拦截掉事件,后面的事件也会交给该控件
case MotionEvent.ACTION_DOWN:
//是不是摸到屏幕了(因为android有实体按键,按键会被模拟成手指按下了)
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
//如果不是clickable,检查长按
checkForLongClick(0, x, y);
break;
}
//检测鼠标右键点击
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);
//开始对长按就行检查,和上面滑动控件中处理的等待时间不同,这里是0
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
//滑动事件
case MotionEvent.ACTION_MOVE:
//如果可以点击
if (clickable) {
//5.0的波纹效果
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
// 如果手指没有移出点击范围
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
//移除点击的等待器
removeTapCallback();
//移除长按的等待器
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}
是否是鼠标右键点击
/**
* Performs button-related actions during a touch down event.
* 是否是鼠标右键点击
* @param event The event.
* @return True if the down was consumed.
*
* @hide
*/
protected boolean performButtonActionOnTouchDown(MotionEvent event) {
if (event.isFromSource(InputDevice.SOURCE_MOUSE) &&
(event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
showContextMenu(event.getX(), event.getY());
mPrivateFlags |= PFLAG_CANCEL_NEXT_UP_EVENT;
return true;
}
return false;
}
是否是滑动控件,这里在自定义ViewGroup控件的时候如果不需要滑动,在 shouldDelayChildPressedState()应该返回false,让控件响应的更快
/**
* @hide
* 是否是滑动控件
*/
public boolean isInScrollingContainer() {
ViewParent p = getParent();
while (p != null && p instanceof ViewGroup) {
//是否应该延迟子View的按下事件,ViewGroup的shouldDelayChildPressedState()默认返回为true,
//这里在自定义ViewGroup控件的时候如果不需要滑动,在 shouldDelayChildPressedState()应该返回false,让控件响应的更快
if (((ViewGroup) p).shouldDelayChildPressedState()) {
return true;
}
p = p.getParent();
}
return false;
}
预按下状态的处理
//预按下状态的处理
private final class CheckForTap implements Runnable {
public float x;
public float y;
@Override
public void run() {
//解除预按下状态
mPrivateFlags &= ~PFLAG_PREPRESSED;
//处理为按下的状态
setPressed(true, x, y);
//检测长按事件,此时等待时间变为:点击等待器的点击时间,getTapTimeout为100ms
checkForLongClick(ViewConfiguration.getTapTimeout(), x, y);
}
}
检测长按点击事件
//检测长按点击事件
// int delayOffset 延迟检测的时间
private void checkForLongClick(int delayOffset, float x, float y) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
mPendingCheckForLongPress.rememberPressedState();
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
}
检测手指是否移出点击范围,slop是一个溢出范围,在这个范围内不算溢出
/**
* Utility method to determine whether the given point, in local coordinates,
* is inside the view, where the area of the view is expanded by the slop factor.
* This method is called while processing touch-move events to determine if the event
* is still within the view.
*
* @hide
*/
public boolean pointInView(float localX, float localY, float slop) {
return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
localY < ((mBottom - mTop) + slop);
}
执行点击事件
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;
}
return result;
}
点击和长按事件都在 View 的 onTouchEvent 里面
点击事件的处理是在 onTouchEvent ACTION_UP里面
长按事件的处理是在 onTouchEvent ACTION_DOWN里面
OnTouchListener的 onTouch 方法返回 false,事件执行顺序:
OnTouchListener–> onTouchEvent --> onClickListener