已经有一段事件没有写博客了,由于最近一段时间比较忙,在赶项目,而且也有一些迷茫,不知道该写些什么好,正好最近遇到了一个自定义控件的事件冲突问题,所以就再网上找了很多关于事件传递和事件冲突的相关解决方案再结合Android系统源代码自己了解了一下,觉得有必要写一些东西来记录一下,以免过一段时间忘记了而又要到网上找很多资料自己重新总结。
我遇到的项目问题是这样的。一个自定义的下拉刷新控件,里面包含一个ScrollView,在Scrollview的顶部有一个viewpager,下面还有其他内容,在对viewpager进行左右滑动的时候,如果是水平的滑动不会有任何问题,但是如果倾斜(就是水平有滑动距离,垂直也有滑动距离的话),下拉刷新控件的下拉头会出现 而导致给人一种viewpager滑动不灵敏的感觉。开始没有想象到在下拉刷新里面做水平滑动距离和垂直滑动距离的比较的判断,来确定是应该由子控件(viewpager)响应滑动事件还是由下拉刷新控件响应滑动事件,在网上找了很多都尝试了例如这篇文章里面提到的问题和解决方案和我的项目场景非常相似,可是尝试过还是不好使,因为我们的下拉刷新控件的滑动事件的处理逻辑全部写在方法dispatchTouchEvent中,他是触摸事件最开始走的地方,因此他始终会执行的,才会根据具体情况决定是否传递给子控件。好了不扯这么长了,下面来讲讲今天的要将的主要内容吧。
在网上关于Android的事件传递的文章真的可以说是泛滥成灾了,千篇一律的都是什么返回true自己消费,返回false传递给子view去消费,如果子view消费不了,又再回传给父view处理,最后父view到底是如何处理能不能够处理的了,这就跟我子view没得半毛钱的关系了。呵呵,其中有一些文章可能是作者真正尝试做过实验而得出的结论,而有一些就是看了别人的文章觉得很吊,自己有改改,没试验,就成自己的东西了。说道这里,你们的记忆中关于事件传递的东西是不是也都是这样的。这些东西有时候可能解决我们的问题,但是有时候你按照他们的思路去写你的代码,并不能解决问题,这说明他们也可能存在一些问题,至少我是这么认为的,哈哈,我的东西我也不能保证全对。所以有些问题还是得自己去发现问题,解决问题,回归根本(Android源代码)。这样才会做到有理有据。在开发中才能游刃有余。同时,事件传递在我们的开发中也非常重要,当你看到别人一个很nb,非常炫酷的自定义控件,他们绝逼处理过相关的事件传递或者事件冲突的问题。
先看一段很简单的xml文件,不做多余的解释:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="${relativePackage}.${activityClass}" >
<TextView
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="30sp"
android:background="#ff0000"
android:gravity="center"
android:text="@string/hello_world" />
</RelativeLayout>在MainActivity中我们重写了Activity的dispatchTouchEvent和onTouchEvent
public class MainActivity extends Activity {
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.tv);
// tv.setOnTouchListener(new OnTouchListener() {
//
// @Override
// public boolean onTouch(View v, MotionEvent event) {
// boolean flag = true;
// switch (event.getAction()) {
// case MotionEvent.ACTION_DOWN:
// flag = false;
// System.out.println("downdowndowndowndowndowndowndowndowndowndowndowndown");
// break;
// case MotionEvent.ACTION_MOVE:
// flag = false;
// System.out.println("movemovemovemovemovemovemovemovemovemovemovemovemove");
// break;
// case MotionEvent.ACTION_UP:
// flag = false;
// System.out.println("upupupupupupupupupupupupupupupupupupupupupupupupupupup");
// break;
// default:
// flag = false;
//
// break;
// }
// return false;
// }
// });
// tv.setOnClickListener(new OnClickListener() {
//
// @Override
// public void onClick(View v) {
// // TODO Auto-generated method stub
// Toast.makeText(MainActivity.this, "onClick点击事件触发", 1000).show();
// }
// });
// tv.setOnLongClickListener(new OnLongClickListener() {
//
// @Override
// public boolean onLongClick(View v) {
// System.out.println("onLongClick点击事件触发");
// return false;
// }
// });
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean flag = false;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
flag = false;
System.out.println("dispatchTouchEvent===down");
break;
case MotionEvent.ACTION_MOVE:
flag = false;
System.out.println("dispatchTouchEvent==move");
break;
case MotionEvent.ACTION_UP:
flag = false;
System.out.println("dispatchTouchEvent==up");
break;
default:
flag = false;
break;
}
// super.dispatchTouchEvent(ev);
// return super.dispatchTouchEvent(ev);
return flag;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean flag = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
flag = false;
System.out.println("onTouchEventt===down");
break;
case MotionEvent.ACTION_MOVE:
flag = true;
System.out.println("onTouchEvent==move");
break;
case MotionEvent.ACTION_UP:
flag = false;
System.out.println("onTouchEvent==up");
break;
default:
flag = false;
break;
}
return super.onTouchEvent(event);
}
}分别看看下面三张截图的运行结果再说话:
其实 并不如大家所说的return true或者return false;会怎么样,当我们重写了dispatchTouchEvent的时候,我们会发现无论他是return true或者return false本身的onTouchEvent都不会执行,只有我们执行了super.dispatchTouchEvent(在return之前调用将事件传递给父控件)或者 return dispatchTouchEvent(ev);才会执行到onTouchEvent方法中,这是为什么呢,当我们执行返回true或者false的时候就不会调用父类或者父控件的dispatchTouchEvent方法了,而在Android系统中关于Activity中dispatchTouchEvent的方法是这样的:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}看源码的执行顺序 ,如果我们重写了dispatchTouchEvent但是没有执行父类的dispatchTouchEvent,所有我们重写的onTouchEvent方法也不会执行,所有会有我们的图二的执行结果,如果我们仿照Android源码的写法直接写return onTouchEvent(ev);也会达到我们想要的执行我们自己重写的onTouchEvent方法,或者在我们return之前直接调用父类的也即super.dispatchTouchEvent方法也会达到我们即在此消费了事件但是我也传递给子控件了额。我们的下拉刷新就是这么干的。至此再也不要有那种惯性思维return true消费不传递,return false不消费 往下传递。我们可以追根溯源找到我么自己想要的答案,还可以得到多种可执行onTouchEvent方法。
说了这么久,感觉只是纠正了大家的一种思维的惯性错误。我现在就通过ViewGroup和View源码中关于dispatchTouchEvent , onIntecptTouchEvent ,onTouchEvent方法看看事件的传递顺序以及onLongClick ,onClick ,onTouch() ,onTouchEvent方法的执行顺序 。
先看看ViewGroup中关于dispatchTouchEvent的源码是怎么写的:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
//ev 是否设置 FLAG_TARGET_ACCESSIBILITY_FOCUS这个
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
/*
* ACTION_DOWN重新设置状态,给mFirstTouchTarget中的target发生cancel事件,
* 清空mFirstTouchTarget列表
* 清空mGroupFlags的FLAG_DISALLOW_INTERCEPT标志
* mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
*/
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
/*在 ACTION_DOWN 和 mFirstTouchTarget不为空的时候就会去intercept
* 在ACTION_MOVE事件的时候,虽然有子view处理事件序列了,
* 但是viewgroup还是有机会插手事件序列
* */
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//是否不允许intercept 觉得参数和requestDisallowInterceptTouchEvent(true);是否非常之相似啊
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//允许intercept
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
//这种情况比较少
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
//取消FLAG_TARGET_ACCESSIBILITY_FOCUS
ev.setTargetAccessibilityFocus(false);
}
// Check for cancelation.
//取消PFLAG_CANCEL_NEXT_UP_EVENT标记,返回之前是否已经 PFLAG_CANCEL_NEXT_UP_EVENT
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//遍历子view
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = children[i];
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
//如果child的dispatchTouchEvent返回true,dispatchTransformedTouchEvent也会返回true,这时候条件就会成立,
//newTouchTarget和alreadyDispatchedToNewTouchTarget就会被修改。这里传进去child,方法里就会调用child.dispatchTouchEvent,
//如果传null就会调用super.dispatchTouchEvent
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = i;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//将处理了down事件的view的target添加到target list的头部,此时newTouchTarget和mFirstTouchTarget是相等的
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//标记事件已经分发给子view
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
//如果都没有没找到,那么用mFirstTouchTarget队列中尾巴上的那个target来处理
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// Dispatch to touch targets.
//如果是ACTION_MOVE事件,上面的这个if 是没有不会执行的,直接到这个if了
mFirstTouchTarget是一个全局变量,指向target list的第一个元素。
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
//进入这里,说明前面down事件处理的子view返回false,导致没有target被添加到list中,也就是这个事件没有view认领。
// No touch targets so treat this as an ordinary view.
//这里有个参数传了null,方法里面会判断这个参数,如果为null就调用super.dispatchTouchEvent,也就是自己来处理event。
//由于down后面的事件都没法修改mFirstTouchTarget,所以之后的事件都在这里执行了,该子view就没法接收到后面的事件了
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//能进入这里,说明子view在处理down事件后返回了true。后面的move和up事件会直接进入这里
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
//这里遍历这个target list,挨个分发事件,为了方便理解,可以暂时认为list里面此时就只包含一个target的,也就是当前处理事件的view的target
final TouchTarget next = target.next;
//如果是down事件的话,就会进入这个if里面,由于down事件在前面已经处理了,所以直接handled = true。
//因为如果是move和up的话alreadyDispatchedToNewTouchTarget是false,newTouchTarget是null。
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
//handled会被返回到当前Activity的dispatchTouchEvent中,具体在Activity中怎么使用可以查看其源码
handled = true;
} else {
//move和up事件会进入这里
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//看这里,由此可知在move的时候返回true或者false只会影响到layout返回给Activity的值,由于不是down事件所以不会影响up事件的获取。
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//取消或者所有手指都起开
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
//值起开一个手指
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
在代码71-72行,
就是看这两个变量newTouchTarget和alreadyDispatchedToNewTouchTarget在过了这段代码后的值有没有改变,在down的时候可以进入这里面,所以如果子view的dispatchTouchEvent如果返回false的话该子view的target就不会被添加到target list中了,两个变量的值没有被改变。但在move和up的时候就进不去这里,所以在move和up事件时newTouchTarget是null的,alreadyDispatchedToNewTouchTarget是false。
代码134行即以后解释了具体的事件处理逻辑,事件就此也分发到子View当中去了。下面再来看View中的dispatchTouchEvent是怎么处理的
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
//首先检查一些相关条件 mOnTouchListener是否为空 也就是我们是否给view设置onTouchListener
//在检查listener是否给我们返回true如果三者条件都符合 函数直接返回
//不会再执行下面if条件了 更不会执行view的onTouchEvent方法了
//也即你写在onTouchEvent中的代码逻辑毫无意义了
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
if (onTouchEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}从上面的代码和解释我们可以看出,当我们给View设置了onTouchListener的时候,重写的onTouch要比onTouchEvent先执行,当然onTouchEvent还有可能不执行,它是否执行还得取决于onTouch是否返回false。如果返回false,onTouchEvent能够执行,返回true,onTouchEvent就不能执行。
/**
* Implement this method to handle touch screen motion events.
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
// 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));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
if ((mPrivateFlags & 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.
mPrivateFlags |= PRESSED;
refreshDrawableState();
}
if (!mHasPerformedLongPress) {
// 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(); //执行click方法
}
}
}
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();
}
break;
//首先接收到ACTION_DOWN事件他是move,up事件的前提
//并且在Action_DOWN时候检查onLongClick是否可执行是否可执行
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false; //默认onLongClick标记为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) { //判断控件view是否可以滚动
mPrivateFlags |= PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
mPrivateFlags |= PRESSED;
refreshDrawableState();
checkForLongClick(0); //检查控件的onLongClick是否可执行
}
break;
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
return true;
}
return false;
}我们首先看ACTION_DOWN事件,解释上也说明,最后会检查onLongClick事件是否可执行 我们看看checkForLongClick(0)方法是怎么写的:
private void checkForLongClick(int delayOffset) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { //检查是否可长按
mHasPerformedLongPress = false; //是否执行长按设置为false
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.rememberWindowAttachCount();
//通过handler延时来确定长按是否发生
//事件阈值ViewConfiguration.getLongPressTimeout() - delayOffset
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
}我们可以看出最后是通过handler来处理的,我们看看CheckFoeLongPress这个runnable是怎么实现的以及他的run方法里面的内容
class CheckForLongPress implements Runnable {
private int mOriginalWindowAttachCount;
public void run() {
if (isPressed() && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
if (performLongClick()) { //既做出了判断 也执行了LongClick的事件
mHasPerformedLongPress = true;
}
}
}
public void rememberWindowAttachCount() {
mOriginalWindowAttachCount = mWindowAttachCount;
}
}
//performLongClick()源代码
public boolean performLongClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
if (mOnLongClickListener != null) {
//这里就是为什么onLongClick方法会有返回值的原因 而这个值就决定这onClick方法是否执行了
//返回true的时候,事件就此消费完了,onClick方法就不再执行 了
//返回false的时候,onClick方法还可以继续执行
handled = mOnLongClickListener.onLongClick(View.this);
}
if (!handled) {
handled = showContextMenu();
}
if (handled) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
return handled;
}onLongClick的返回值直接决定着onClick方法是否能够执行 继续回到CheckForLongPress的run方法中 当if(pefromLongClick()) 为true的时候 也就是事件消费之后,mHasPerformedLongPress = true;这个
mHasPerformedLongPress 我们在view的onTouchEvent的ACTION_UP事件之后会用到,再回到ACTION_UP分支当中,也即上面onTouchEvent代码中的49行判断mHasPerformedLongPress 只有当mHasPerformedLongPress值为false的时候 我们才会去移除longClick事件去执行performclick事件。这就是为什么会在判断完performLongClick()之后,如果返回true,就将mHasPerformedLongPress = true ,返回false的话mHasPerformedLongPress 就为默认值false了。这里可以看出onLongClick在ACTION_DOWN事件之后判断执行,而onClick方法在ACTION_UP方法之后 并且还需要判断onLongClick的返回值才能决定是否能够执行onClick方法了
。
而此时我们如果细心的话还会发现在onTouchEvent方法中if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) 代码块执行完成后 一直是return true的,这也可以说明此时的onTouchEvent方法返回true这时候可以用惯性思维来考虑了,事件在此时此刻以及完全被消费的渣渣都不剩下了,所以,你在View(控件)响应了点击事件之后,它不会将Touch这一类的事件再回传递给父类或者父控件了。也就是所有的事件在你响应手指点击事件,手指ACTION_UP的时候全部宣告结束了额。
尼玛,自己终于感觉把自己想要记录的东西差不多写完了额。好累额。不过感觉还不错。还是来总结一下吧(父控件->子控件->父控件)
1.摒弃父控件(布局容器)的dispatchTouchEvent中return true 消费事件 return false 事件传递给父控件的onInterceptTouchEvent中进行拦截,由于此方法默认一直返回false,所以return false就传递给子控件的观念。实际问题实际分析。
2.在触摸事件当中ACTION_DOWN事件是重中之重,他是MOVE事件和UP事件的前提
3.同一个控件的onTouchListeren中的onTouch()方法要先于onTouchEvent()方法执行,只有当onTouchListeren中的onTouch()最后返回false的时候次控件的onTouchEvent()方法才会执行。
4.onTouchEvent先于onLongClick事件执行 ,而onLongClick事件先于onClick事件执行,onClick取决于onLongClick事件执行的返回值,当返回false的时候onClik方法才会执行,返回true的时候onClick事件不会执行。
5.所有的触摸类事件在onLongClick或者onClick方法就执行结束了,不会在传递 了。
本文深入探讨Android中的事件传递机制,包括ACTION_DOWN事件的重要性、onTouch()与onTouchEvent()的执行顺序,以及onLongClick与onClick事件的关系。通过解析源代码揭示了事件处理的内在逻辑。
1117

被折叠的 条评论
为什么被折叠?



