Android View Touch的事件分发机制
2016-03-23 17:01
405 查看
开发一段时间的Android,或多或少对Android的事件有一些了解,对诸如dispatchTouchEvent、onTouchEvent方法有些了解。但真正在面试中被问起,整个机制,或者具体的分析ViewGroup+ViewGroup+View的具体回调顺序,就懵了。百度出的第一位博客讲解的很到位:
当一个Touch事件(触摸事件为例)到达根节点,即Acitivty的ViewGroup时,它会依次下发,下发的过程是调用子View(ViewGroup)的dispatchTouchEvent方法实现的。简单来说,就是ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。上述例子中的消息下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中如果⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件。
看了上面的讲解,其实心中已有一定的了解。但不能止于此。于是我使用代码验证并查看Android的源代码来看看到底是怎么回事,来找到最后的答案。
新建Activity,layout的结构为LinearLayout - RelativeLayout - Button,刚好是题目所示的结构。为了验证回调函数,我将这三个View都继承了,同时也在Activity中覆盖dispatchTouchEvent和onTouchEvent:
layout布局如下:
三个自定义的View/ViewGroup如下:
点击Button,可以看到回调的执行顺序是:
看到日志输出,更是一目了然了。去查看了下ViewGroup的源码,它的onInterceptTouchEvent方法未做任何处理,总是返回false的。
再看看ViewGroup中的dispatchTouchEvent方法:
那View里的dispatchTouchEvent方法执行的很明显,直接实现onTouch监听或执行onTouchEvent方法:
而ViewGroup中没有覆盖onTouchEvent方法,都是执行View中的onTouchEvent方法。
在View的onTouchEvent方法中,会在ACTION_UP事件中调用performCLick方法,从而响应Button.onClick事件:
所以这个题目的答案就是,点击事件从Activity传递到ViewGroup,最上层的ViewGroup调用dispatchTouchEvent进行事件分发,接着会调用下面一层ViewGroup的dispatchTouchEvent方法,然后就是Button的dispatchTouchEvent方法,而Button的dispatchTouchEvent方法会调用Button的onTouchEvent方法,在这个方法里,点击事件被消费,返回了true,接着Button的dispatchTouchEvent方法也返回true,然后中间ViewGroup的dispatchTouchEvent方法也返回true,然后是最上层的dispatchTouchEvent方法也返回true,最后是Activity的dispatchTouchEvent方法也返回true。事件一步一步向下传递,然后一步一步向上冒泡反馈。
参考文章:/article/5242433.html
当一个Touch事件(触摸事件为例)到达根节点,即Acitivty的ViewGroup时,它会依次下发,下发的过程是调用子View(ViewGroup)的dispatchTouchEvent方法实现的。简单来说,就是ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。上述例子中的消息下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中如果⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件。
ViewGroup+ViewGroup+View事件传递的具体分析
题目:比如最上面有一个ViewGroup,下面有一个ViewGroup,最下面有一个Button,那么点击Button,请问事件是如何传递的,这期间会回调哪些方法,顺序如何?看了上面的讲解,其实心中已有一定的了解。但不能止于此。于是我使用代码验证并查看Android的源代码来看看到底是怎么回事,来找到最后的答案。
新建Activity,layout的结构为LinearLayout - RelativeLayout - Button,刚好是题目所示的结构。为了验证回调函数,我将这三个View都继承了,同时也在Activity中覆盖dispatchTouchEvent和onTouchEvent:
layout布局如下:
<?xml version="1.0" encoding="utf-8"?> <com.idengpan.life100.viewtouch.TouchLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" android:orientation="vertical" > <com.idengpan.life100.viewtouch.TouchRelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" > <com.idengpan.life100.viewtouch.TouchButton android:id="@+id/btn_test" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="测试" /> </com.idengpan.life100.viewtouch.TouchRelativeLayout> </com.idengpan.life100.viewtouch.TouchLinearLayout>
三个自定义的View/ViewGroup如下:
public class TouchLinearLayout extends LinearLayout { public TouchLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { System.out.println("TouchLinearLayout onInterceptTouchEvent"); boolean result = super.onInterceptTouchEvent(ev); System.out.println("TouchLinearLayout onInterceptTouchEvent return " + result); return result; } @Override public boolean dispatchTouchEvent(MotionEvent ev) { System.out.println("TouchLinearLayout dispatchTouchEvent"); boolean result = super.dispatchTouchEvent(ev); System.out.println("TouchLinearLayout dispatchTouchEvent return " + result); return result; } @Override public boolean onTouchEvent(MotionEvent event) { System.out.println("TouchLinearLayout onTouchEvent"); boolean result = super.onTouchEvent(event); System.out.println("TouchLinearLayout onTouchEvent return " + result); return result; } } public class TouchRelativeLayout extends RelativeLayout{ public TouchRelativeLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { System.out.println("TouchRelativeLayout onInterceptTouchEvent"); boolean result = super.onInterceptTouchEvent(ev); System.out.println("TouchRelativeLayout onInterceptTouchEvent return " + result); return result; } @Override public boolean dispatchTouchEvent(MotionEvent ev) { System.out.println("TouchRelativeLayout dispatchTouchEvent"); boolean result = super.dispatchTouchEvent(ev); System.out.println("TouchRelativeLayout dispatchTouchEvent return " + result); return result; } @Override public boolean onTouchEvent(MotionEvent ev) { System.out.println("TouchRelativeLayout onTouchEvent"); boolean result = super.onTouchEvent(ev); System.out.println("TouchRelativeLayout onTouchEvent return " + result); return result; } } public class TouchButton extends Button { public TouchButton(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { System.out.println("TouchButton dispatchTouchEvent"); boolean result = super.dispatchTouchEvent(ev); System.out.println("TouchButton dispatchTouchEvent return " + result); return result; } @Override public boolean onTouchEvent(MotionEvent ev) { System.out.println("TouchButton onTouchEvent"); boolean result = super.onTouchEvent(ev); System.out.println("TouchButton onTouchEvent return " + result); return result; } } //Activity中覆盖: @Override public boolean dispatchTouchEvent(MotionEvent ev) { System.out.println("activity dispatchTouchEvent"); boolean result = super.dispatchTouchEvent(ev); System.out.println("activity dispatchTouchEvent return " + result); return result; } @Override public boolean onTouchEvent(MotionEvent ev) { System.out.println("activity onTouchEvent"); boolean result = super.onTouchEvent(ev); System.out.println("activity onTouchEvent return " + result); return result; }
点击Button,可以看到回调的执行顺序是:
I/System.out(7034): activity dispatchTouchEvent I/System.out(7034): TouchLinearLayout dispatchTouchEvent I/System.out(7034): TouchLinearLayout onInterceptTouchEvent I/System.out(7034): TouchLinearLayout onInterceptTouchEvent return false I/System.out(7034): TouchRelativeLayout dispatchTouchEvent I/System.out(7034): TouchRelativeLayout onInterceptTouchEvent I/System.out(7034): TouchRelativeLayout onInterceptTouchEvent return false I/System.out(7034): TouchButton dispatchTouchEvent I/System.out(7034): TouchButton onTouchEvent I/System.out(7034): TouchButton onTouchEvent return true I/System.out(7034): TouchButton dispatchTouchEvent return true I/System.out(7034): TouchRelativeLayout dispatchTouchEvent return true I/System.out(7034): TouchLinearLayout dispatchTouchEvent return true I/System.out(7034): activity dispatchTouchEvent return true
看到日志输出,更是一目了然了。去查看了下ViewGroup的源码,它的onInterceptTouchEvent方法未做任何处理,总是返回false的。
public boolean onInterceptTouchEvent(MotionEvent ev) { return false; }
再看看ViewGroup中的dispatchTouchEvent方法:
public boolean dispatchTouchEvent(MotionEvent ev) { .... // Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { 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; } ... //dispatchTransformedTouchEvent方法中 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; // Canceling motions is a special case. We don't need to perform any transformations // or filtering. The important part is the action, not the contents. final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } ... } }
那View里的dispatchTouchEvent方法执行的很明显,直接实现onTouch监听或执行onTouchEvent方法:
public boolean dispatchTouchEvent(MotionEvent event) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { return true; } if (onTouchEvent(event)) { return true; } } if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } return false; }
而ViewGroup中没有覆盖onTouchEvent方法,都是执行View中的onTouchEvent方法。
在View的onTouchEvent方法中,会在ACTION_UP事件中调用performCLick方法,从而响应Button.onClick事件:
public boolean onTouchEvent(MotionEvent event) { ... 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. setPressed(true); } 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(); } } } .... }
所以这个题目的答案就是,点击事件从Activity传递到ViewGroup,最上层的ViewGroup调用dispatchTouchEvent进行事件分发,接着会调用下面一层ViewGroup的dispatchTouchEvent方法,然后就是Button的dispatchTouchEvent方法,而Button的dispatchTouchEvent方法会调用Button的onTouchEvent方法,在这个方法里,点击事件被消费,返回了true,接着Button的dispatchTouchEvent方法也返回true,然后中间ViewGroup的dispatchTouchEvent方法也返回true,然后是最上层的dispatchTouchEvent方法也返回true,最后是Activity的dispatchTouchEvent方法也返回true。事件一步一步向下传递,然后一步一步向上冒泡反馈。
参考文章:/article/5242433.html
相关文章推荐
- android提供的json解析类
- Android开发库VUtils之农历计算
- android中实现返回首页功能
- Android使用xml旋转图片,个人记录
- Android官方开发文档Training系列课程中文版:添加ActionBar之自定义ActionBar样式
- android——混淆打包
- 3月23日学习记录(showAsAction,android:orderInCategory="100")
- 【Android】【事件】onInterceptTouchEvent(),onTouchEvent()
- Android源代码的获取
- android解压zip格式的包
- 理解android:targetSdkVersion
- 关于Android中XML解析方式
- Android 6.0 运行时权限处理
- Android学习笔记(32):通知推送Notification
- Android入门——Activity生命周期和横竖屏切换总结
- Android事件分发
- Android学习笔记(31):滚动视图ScrollView
- Android学习笔记(30):选项卡TabHost
- Android NDK编程
- android提供的json解析类