
为了更好的研究VIEw的事件转发,我们自定以一个Mybutton继承button,然后把跟事件传播有关的方法进行复写,然后添加上日志:
然后把我们自定义的按钮加到主布局文件中:
<?xml version="1.0" enCoding="utf-8"?><linearLayout xmlns:androID="http://schemas.androID.com/apk/res/androID" androID:layout_wIDth="match_parent" androID:layout_height="match_parent" androID:orIEntation="vertical"> <myvIEw.MylinerLayout androID:ID="@+ID/ll" androID:layout_wIDth="match_parent" androID:layout_height="match_parent" androID:gravity="center"> <myvIEw.Mybutton androID:ID="@+ID/btn" androID:layout_wIDth="100dp" androID:layout_height="100dp" androID:text="button" /> </myvIEw.MylinerLayout></linearLayout>
在Activity中设置button的touch事件:
public class MainActivity extends Activity{ protected static final String TAG = "Mybutton"; private button mbutton ; @OverrIDe protected voID onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentVIEw(R.layout.activity_main); mbutton = (button) findVIEwByID(R.ID.ID_btn); mbutton.setontouchListener(new OntouchListener() { @OverrIDe public boolean ontouch(VIEw v, MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: Log.e(TAG, "ontouch ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(TAG, "ontouch ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(TAG, "ontouch ACTION_UP"); break; default: break; } return false; } }); } }点击按钮之后查看打印结果:
E/Mybutton(879): dispatchtouchEvent ACTION_DOWN
E/Mybutton(879): ontouch ACTION_DOWN
E/Mybutton(879): ontouchEvent ACTION_DOWN
E/Mybutton(879): dispatchtouchEvent ACTION_MOVE
E/Mybutton(879): ontouch ACTION_MOVE
E/Mybutton(879): ontouchEvent ACTION_MOVE
E/Mybutton(879): dispatchtouchEvent ACTION_UP
E/Mybutton(879): ontouch ACTION_UP
E/Mybutton(879): ontouchEvent ACTION_UP
可以看到,不管是DOWN,MOVE,UP都会按照下面的顺序执行
1、dispatchtouchEvent
2、 setontouchListener的ontouch
3、ontouchEvent
先来看触摸事件的入口函数:
public boolean dispatchtouchEvent(MotionEvent event) { if (!onFiltertouchEventForSecurity(event)) { return false; } if (mOntouchListener != null && (mVIEwFlags & ENABLED_MASK) == ENABLED && mOntouchListener.ontouch(this, event)) { return true; } return ontouchEvent(event); }
可以看出如果mOntouchListener不为空,并且vIEw是ENABLED的,并且mOntouchListener.ontouch(this, event)的返回值==true,那么dispatchtouchEvent的返回值就是true,ontouchEvent方法就得不到执行。
public voID setontouchListener(OntouchListener l) { mOntouchListener = l; }
如果vIEw设置了setontouchListener的监听,那么mOntouchListener!=null就成立,一般vIEw是ENABLED的,所以ontouch函数的返回值就决定了ontouchEvent函数能否得到执行。来看下ontouchEvent函数的源码:
public boolean ontouchEvent(MotionEvent event) { final int vIEwFlags = mVIEwFlags; //如果当前VIEw是Disabled状态且是可点击则会消费掉事件(return true);可以忽略,不是我们的重点 if ((vIEwFlags & ENABLED_MASK) == Disabled) { 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 (!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(); } } } if (mUnsetpressedState == null) { mUnsetpressedState = new UnsetpressedState(); } if (prepressed) { mPrivateFlags |= pressed; refreshDrawableState(); postDelayed(mUnsetpressedState, VIEwConfiguration.getpressedStateDuration()); } else if (!post(mUnsetpressedState)) { // If the post Failed, unpress right Now mUnsetpressedState.run(); } removeTapCallback(); } break; case MotionEvent.ACTION_DOWN: if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPrivateFlags |= PREpressed; mHasPerformedLongPress = false; postDelayed(mPendingCheckForTap, VIEwConfiguration.getTapTimeout()); 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 int slop = mtouchSlop; if ((x < 0 - slop) || (x >= getWIDth() + slop) || (y < 0 - slop) || (y >= getHeight() + slop)) { // 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; }
首先分析down事件:
可以看到down的时候会将mPrivateFlags设置一个PREpressed的表示,设置mHasPerformedLongPress=false;表示长按事件还未触发
这是一个延迟115ms的异步任务,也就是115ms之后就会执行CheckForTap类中的run()方法。
postDelayed(mPendingCheckForTap, VIEwConfiguration.getTapTimeout());private final class CheckForTap implements Runnable { public voID run() { mPrivateFlags &= ~PREpressed; mPrivateFlags |= pressed; refreshDrawableState(); if ((mVIEwFlags & LONG_CliCKABLE) == LONG_CliCKABLE) { postCheckForLongClick(VIEwConfiguration.getTapTimeout()); } } }
将mPrivateFlags设置为pressed里面还包含了一个postCheckForLongClick()的方法,来看下postCheckForLongClick()方法: private voID postCheckForLongClick(int delayOffset) { mHasPerformedLongPress = false; if (mPendingCheckForLongPress == null) { mPendingCheckForLongPress = new CheckForLongPress(); } mPendingCheckForLongPress.rememberWindowAttachCount();
//执行一个延迟500-115ms异步任务 postDelayed(mPendingCheckForLongPress, VIEwConfiguration.getLongPresstimeout() - delayOffset); }
class CheckForLongPress implements Runnable { private int mOriginalWindowAttachCount; public voID run() { if (ispressed() && (mParent != null) && mOriginalWindowAttachCount == mWindowAttachCount) { if (performlongClick()) { mHasPerformedLongPress = true; } } }
如果performlongClick()返回true那么mHasPerformedLongPress=true。
public boolean performlongClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CliCKED); boolean handled = false; if (mOnLongClickListener != null) { // 回调用户实现的长按 *** 作监听函数(OnLongClickListener) handled = mOnLongClickListener.onLongClick(VIEw.this); } if (!handled) { // 如果OnLongClickListener的onLongClick返回false // 则需要继续处理该长按事件,这里是显示上下文菜单 handled = showContextMenu(); } if (handled) { // 长按 *** 作事件被处理了,此时应该给用户触觉上的反馈 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } return handled; }
可以看到用户的长按事件是在这个方法里面实现的回调,并且返回一个boolean值。总结下就是:
当用户按下,首先会设置标识为PREpressed,如果115后,没有抬起,会将VIEw的标识设置为pressed且去掉PREpressed标识,然后发出一个检测长按的延迟任务,延时为:VIEwConfiguration.getLongPresstimeout() - delayOffset(500ms -115ms),这个115ms刚好是检测到PREpressed时间;也就是用户从DOWN触发开始算起,如果500ms内没有抬起则认为触发了长按事件。
在来看下up事件:
如果包含pressed或者PREpressed则进入执行体,也就是无论是115ms内或者之后抬起都会进入执行体,如果mHasPerformedLongPress==false,进入IF,否则直接跳出。
如果mPerformClick为null,初始化一个实例,然后立即通过handler添加到消息队列尾部,如果添加失败则直接执行 performClick();添加成功,在mPerformClick的run方法中就是执行performClick()方法。
public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CliCKED); if (mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CliCK); mOnClickListener.onClick(this); return true; } return false; }
可以看到这个方法里面会执行用户的onClick()的回调,并且返回true,表示消费了这个事件。总结下up事件:如果用户设置了长按回调并且返回了true,那么performClick()方法就不会执行,那么我们的onClick()方法也就不会执行。
总结下整个的ontouchEvent方法,只要vIEw具有onClick或者onLongClick那么都会返回true,否则返回false。这也就是button可以不用设置点击或者长按的回调,而TextVIEw需要设置回调之后
才会进入里面的if()体的原因:
public voID setonClickListener(@Nullable OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l; }
调用这个方法之后,如果不可点击就会设置成可以点击。整个VIEw的事件转发流程是:
VIEw.dispatchEvent->VIEw.setontouchListener->VIEw.ontouchEvent
在dispatchtouchEvent中会进行OntouchListener的判断,如果OntouchListener不为null且返回true,则表示事件被消费,ontouchEvent不会被执行;否则执行ontouchEvent。
总结
以上是内存溢出为你收集整理的Android View的事件分发机制-源码解析全部内容,希望文章能够帮你解决Android View的事件分发机制-源码解析所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)