一、MotionEvent——手指触摸屏幕时产生的事件
事件 | 含义 |
ACTION_DOWN | 手指初次碰到屏幕时触发 |
ACTION_MOVE | 手指在屏幕上滑动时触发(ps:会多次触发,看源码时同一块代码应该看多次去理解) |
ACTION_UP | 手指离开屏幕时触发 |
ACTION_CANCEL | 事件被上层拦截时触发 |
关于ACTION_MOVE事件,手指在屏幕上滑动时会触发多次,对于这个点,看源码时同一块代码应该看多次结合去理解。
关于ACTION_CANCEL,这个事件并不是由用户手指触发的,而是在事件分发过程中,MOVE事件和UP事件被上层拦截而产生的。(关于ACTION_CANCEL是如何产生的,又有什么作用,需要在后面了解事件分发流程及源码之后~)
二、View的事件处理
当手指点击屏幕时,一般来说,事件都会经过Activity,然后由Activity往下传递到ViewGroup,然后ViewGroup再分发给它的子view,即
Activity——ViewGroup1——ViewGroup2——....—— View
我们通常会对一个View做事件监听,那么其中有什么机制呢?
【View的onTouch、onTouchEvent、onClick、onLongClick之间的关系】
事件优先级: onTouch > onLongClick > onClick
当事件一层层分发下来时,最后会分发给目标View,执行目标View的dispatchTouchEvent方法(ps:View的dispatchTouchEvent方法 与ViewGroup中的是不同的)
View的dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent event) {
// TODO: 2023/7/22 看主要代码,无关省略
// ... 省略
// 【1】result ,dispatchTouchEvent最后的返回值,true代表消费,false代表未消费
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
// ... 省略
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
// 【2】if判断
// && 短路与, 当前面条件成立时,不会执行后面的条件
// 条件1 li != null —— 当View有设置事件监听时,mListenerInfo不为空
// 条件2 li.mOnTouchListener != null —— 当view设置了setOnTouchListener时li.mOnTouchListener不为空
// 条件3 (mViewFlags & ENABLED_MASK) == ENABLED —— 当前点击的控件是否为 enable
// 条件4 li.mOnTouchListener.onTouch(this, event) —— view设置的touch监听事件回调
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// 【3】当result为false,才会执行onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
}
// ... 省略
return result;
}
(1)由以上代码分析可知,一般情况下,若要事件被消费,即result 为ture,要么onTouch返回true,要么onTouchEvent返回true。
(2)当我们对View设置了setOnTouchListener监听,onTouch方法返回true时,那么当MotionEvent事件分发到此处时,代码【3】处 if判断中,reslut为true,!result条件不成立,那么onTouchEvent不会执行,setOnClickListener无效(ps:onClick方法的调用在onTouchEvent里面)。同理,当我们对VIew设置了 setEnable(false)时,代码【2】处(mViewFlags & ENABLED_MASK) == ENABLED,li.mOnTouchListener.onTouch(this, event)不会执行,所以setOnTouchListener监听无效。
由此可得出,onTouch优先级高于onTouchEvent,接下来看看onTouchEvent中做了什么。
View的onTouchEvent方法
public boolean onTouchEvent(MotionEvent event) {
// ...省略
// 重点
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
// 【1】处理Up、Down、Move、Cancel事件
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
// 【2】clickable,可通过setClickable方法控制,为false时,跳出
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
// ...省略
// 【3】mHasPerformedLongPress跟长按有关,默认为false,只有onLongClick回调方法返回true时,该值才为true
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();
}
// TODO: 2023/7/22 【4】onCLick在这里
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
break;
case MotionEvent.ACTION_DOWN:
// ...省略
mHasPerformedLongPress = false;
// 【5】开始长按处理,包括计时等操作
if (!clickable) {
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
break;
}
// ...省略
break;
case MotionEvent.ACTION_CANCEL:
// ...省略
break;
case MotionEvent.ACTION_MOVE:
// ...省略
break;
}
return true;
}
return false;
}
private boolean performClickInternal() {
// Must notify autofill manager before performing the click actions to avoid scenarios where
// the app has a click listener that changes the state of views the autofill service might
// be interested on.
notifyAutofillManagerOnClick();
return performClick();
}
public boolean performClick() {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick();
final boolean result;
final ListenerInfo li = mListenerInfo;
// 重点
if (li != null && li.mOnClickListener != null) {
// 顺便提一嘴, click的播放音效,public
playSoundEffect(SoundEffectConstants.CLICK);
// onCLick找到了 并且result = true, onClick没有返回值,一旦执行到onClick,系统认为事件已经处理了
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
(1)onClick点击事件是在UP事件中,也就是手指离开屏幕时处理的,其完整调用流程是dispatchTouchEvent ——onTouchEvent ——performClickInternal——performClick——li.mOnClickListener.onClick(this)
(2)若对VIew设置了setClickable(false), 代码【2】处可知,onClick监听将会无效,但切记setClickable(false)要放在setOnClickListener之前,因为该方法会重置clickable参数
public void setOnClickListener(@Nullable OnClickListener l) { if (!isClickable()) { setClickable(true); // 重置 } getListenerInfo().mOnClickListener = l; }
(3)onClick方法没有返回值, onLongClick方法由返回值,当onLongClick方法返回true时, mHasPerformedLongPress会置为true,看代码【3】处的判断,!mHasPerformedLongPress不成立,后面的代码不会执行,onClick无效。
(4)对于长按监听的逻辑, 简单说说(ps:本人还没有细看),长按事件是手指长按0.5s以上会触发,不需要松开。看代码【5】,是在onTouchEvent——down事件——checkForLongClick方法开始的长按事件处理,当手指松开屏幕时(UP事件),代码【3】处的if判断中,会移除这个长按事件,若此时长按事件还没有触发,则再也不会触发。
checkForLongClick方法
private void checkForLongClick(long delay, float x, float y, int classification) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
// 【1】长按任务
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
mPendingCheckForLongPress.rememberPressedState();
mPendingCheckForLongPress.setClassification(classification);
// 【2】延迟执行长按任务
postDelayed(mPendingCheckForLongPress, delay);
}
}
代码【1】处里的mPendingCheckForLongPress 又是什么呢,来看看
CheckForLongPress对象
private final class CheckForLongPress implements Runnable {
// ...省略其他成员及方法
@Override
public void run() {
if ((mOriginalPressedState == isPressed()) && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
recordGestureClassification(mClassification);
// performLongClick, 是不是很熟悉,跟前面的performClick一样
if (performLongClick(mX, mY)) {
// mHasPerformedLongPress唯一置为true的地方,代表事件由longClick消费了,则优先级在其之后的Click不会收到事件
mHasPerformedLongPress = true;
}
}
}
}
在按下屏幕时,就开始长按事件的计时任务了,当长按不松开一段时间后,便会执行该任务,回调onLongClick方法。
到此, View的事件分析先告一段落。再来看看我之前有的一些疑惑,
三、自问自答加深一下理解~
1、当对按钮设置了setOnTouchListener监听之后,点击该按钮(松开),onTouch方法调用几次?
button.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // 打印一下日志 return false; } });
点击按钮,会产生DOWN和UP两个事件,所以onTouch会调用两次
2、 当对view设置了setOnTouchListener监听之后,按住该view滑动,onTouch方法调用几次?
当在view上滑动时,会调用多次onTouch,因为会一直产生MOVE事件
3、设置了setOnTouchListener之后,为什么setOnClickListener的onClick没有反应了?
当onTouch方法返回true时,认为该事件已经处理了 ,就不会有onCLick的调用了,修改onTouch返回false即可
4、同时设置点击监听和长按监听,当用户长按时只响应长按事件应该怎么做?
onLongClick返回true即可
5、setClickable(false)失效的原因?
setClickable应该在setOnClickListener之后调用,因为setOnClickListener方法会重置该参数为true(setLongClickable同理)
6、onTouch、onClick、onLongClick的优先级?
优先级: onTouch > onLongClick > onClick文章来源:https://www.toymoban.com/news/detail-819807.html
四、后面学习整理一下ViewGroup的处理机制
Android 关于View事件分发(ViewGroup的事件分发流程解析,结合Down事件、Move事件各种情况下的分发流程加深对事件分发的理解)(二)~_半摆的博客-CSDN博客文章来源地址https://www.toymoban.com/news/detail-819807.html
到了这里,关于Android 关于View事件分发(onTouch、onTouchEvent、onClick、onLongClick的关系及原理)(一)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!