[Android进阶笔记]Android触摸事件的拦截机制
第一句话总是最重要的:
Android的拦截机制是一个自顶向下的事件分发与自底向上的事件响应机制
自顶向下的分发,就是我从View树的顶部开始向下分发事件
自底向上的响应,就是当事件传递到View树的底层,那么他就开始往上层层响应
View
View有2个方法 dispatchTouchEvent和onTouchEvent,源码如下:
3363 public boolean dispatchTouchEvent(MotionEvent event) {3364 if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&3365 mOnTouchListener.onTouch(this, event)) {3366 return true;3367 }3368 return onTouchEvent(event);3369 }
根据我上边的话,可以这么理解
dispatchTouchEvent 是用来 事件分发的
onTouchEvent 是用来事件响应的
View的onTouchEvent源码
3792 public boolean onTouchEvent(MotionEvent event) {3793 final int viewFlags = mViewFlags;3794 3795 if ((viewFlags & ENABLED_MASK) == DISABLED) {3796 // A disabled view that is clickable still consumes the touch3797 // events, it just doesn't respond to them.3798 return (((viewFlags & CLICKABLE) == CLICKABLE ||3799 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));3800 }3801 3802 if (mTouchDelegate != null) {3803 if (mTouchDelegate.onTouchEvent(event)) {3804 return true;3805 }3806 }3807 3808 if (((viewFlags & CLICKABLE) == CLICKABLE ||3809 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {3810 switch (event.getAction()) {3811 case MotionEvent.ACTION_UP:3812 if ((mPrivateFlags & PRESSED) != 0) {3813 // take focus if we don't have it already and we should in3814 // touch mode.3815 boolean focusTaken = false;3816 if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {3817 focusTaken = requestFocus();3818 }3819 3820 if (!mHasPerformedLongPress) {3821 // This is a tap, so remove the longpress check3822 if (mPendingCheckForLongPress != null) {3823 removeCallbacks(mPendingCheckForLongPress);3824 }3825 3826 // Only perform take click actions if we were in the pressed state3827 if (!focusTaken) {3828 performClick();3829 }3830 }3831 3832 if (mUnsetPressedState == null) {3833 mUnsetPressedState = new UnsetPressedState();3834 }3835 3836 if (!post(mUnsetPressedState)) {3837 // If the post failed, unpress right now3838 mUnsetPressedState.run();3839 }3840 }3841 break;3842 3843 case MotionEvent.ACTION_DOWN:3844 mPrivateFlags |= PRESSED;3845 refreshDrawableState();3846 if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {3847 postCheckForLongClick();3848 }3849 break;3850 3851 case MotionEvent.ACTION_CANCEL:3852 mPrivateFlags &= ~PRESSED;3853 refreshDrawableState();3854 break;3855 3856 case MotionEvent.ACTION_MOVE:3857 final int x = (int) event.getX();3858 final int y = (int) event.getY();3859 3860 // Be lenient about moving outside of buttons3861 int slop = ViewConfiguration.get(mContext).getScaledTouchSlop();3862 if ((x < 0 - slop) || (x >= getWidth() + slop) ||3863 (y < 0 - slop) || (y >= getHeight() + slop)) {3864 // Outside button3865 if ((mPrivateFlags & PRESSED) != 0) {3866 // Remove any future long press checks3867 if (mPendingCheckForLongPress != null) {3868 removeCallbacks(mPendingCheckForLongPress);3869 }3870 3871 // Need to switch from pressed to not pressed3872 mPrivateFlags &= ~PRESSED;3873 refreshDrawableState();3874 }3875 } else {3876 // Inside button3877 if ((mPrivateFlags & PRESSED) == 0) {3878 // Need to switch from not pressed to pressed3879 mPrivateFlags |= PRESSED;3880 refreshDrawableState();3881 }3882 }3883 break;3884 }3885 return true;3886 }3887 3888 return false;
如果消化了点击事件的Action_Down,则返回true。若不消耗则返回false,返回false则对应的dispatchTouchEvent也返回fasle。 则表示不拦截,继续向下分发事件。至于怎么继续向下分发呢,这个放在ViewGroup的事件分发机制来说。返回True 则表示这个View拦截这个事件,这样这个完整的点击事件(Action_Down到Up)都交给这个View来处理。
ViewGroup:
ViewGroup有三个方法dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent
dispatchTouchEvent 先看看源码里的一小段,就能马上理解ViewGroup的分发机制了
if (child.dispatchTouchEvent(ev)) {// Event handled, we have a target now. mMotionTarget = child; return true;}// The event didn't get handled, try the next view.// Don't reset the event's location, it's not// necessary here.
依次调用ViewGroup里所包含的childView的dispatchTouchEvent,倘若该ViewGroup里的childView,有一个选择了拦截,那么这个ViewGroup的dispatchTouchEvent就返回True,而事件的传递也就会在拦截的childView的onTouchEvent 里停止。如果ViewGroup里所有的childView的dispatchTouchEvent都返回false,即子View都不拦截,那么就相当于这个点击事件已经传递到底了,只能逐层向上响应,即调用ViewGroup的onTouchEvent,直到某个ViewGroup的onTouchEvent返回True,否则一直向上响应,直至rootView。
废话一堆不如代码来的实在。请看例子!
先写2个自定义的ViewGroup,ViewGroupB代码同ViewGroupA,只是在三个事件拦截的方法里加了打印信息
package com.dongua.toucheventtest;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.widget.RelativeLayout;public class ViewGroupA extends RelativeLayout { public ViewGroupA(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public ViewGroupA(Context context) { super(context); } public ViewGroupA(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.i("click", "dispatchTouchEvent: ViewGroupA"); return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.i("click", "onInterceptTouchEvent: ViewGroupA"); return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { Log.i("click", "onTouchEvent: ViewGroupA"); return super.onTouchEvent(event); }}
和一个自定义的View,这里先继承自TextView,因为TextView对点击事件是默认不响应,当然如果熟悉的话你继承一个Button,然后手动的在dispatchTouchEvent里返回fasle,使button不拦截也是ok 的~
package com.dongua.toucheventtest;import android.content.Context;import android.graphics.Canvas;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.widget.TextView;/** * Created by dongua on 2016/11/21. */public class ViewC extends TextView { public ViewC(Context context) { super(context); } public ViewC(Context context, AttributeSet attrs) { super(context, attrs); } public ViewC(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.i("click", "dispatchTouchEvent: ViewC"); return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { Log.i("click", "onTouchEvent: ViewC"); return super.onTouchEvent(event); }}
然后写一下布局
<?xml version="1.0" encoding="utf-8"?><LinearLayout 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="com.dongua.toucheventtest.MainActivity"> <com.dongua.toucheventtest.ViewGroupA android:id="@+id/A" android:background="#0000ff" android:layout_width="200dp" android:layout_height="200dp"> <com.dongua.toucheventtest.ViewGroupB android:id="@+id/B" android:background="#00ff00" android:layout_width="100dp" android:layout_height="100dp"> <com.dongua.toucheventtest.ViewC android:id="@+id/C" android:background="#ff0000" android:layout_width="60dp" android:layout_height="60dp" android:text="View C 文本"/> </com.dongua.toucheventtest.ViewGroupB> </com.dongua.toucheventtest.ViewGroupA></LinearLayout>
布局如图:
然后试着点击一下TextView,看看打印消息
11-22 18:11:36.770 7412-7412/com.dongua.toucheventtest I/click: dispatchTouchEvent: ViewGroupA11-22 18:11:36.770 7412-7412/com.dongua.toucheventtest I/click: onInterceptTouchEvent: ViewGroupA11-22 18:11:36.770 7412-7412/com.dongua.toucheventtest I/click: dispatchTouchEvent: ViewGroupB11-22 18:11:36.770 7412-7412/com.dongua.toucheventtest I/click: onInterceptTouchEvent: ViewGroupB11-22 18:11:36.770 7412-7412/com.dongua.toucheventtest I/click: dispatchTouchEvent: ViewC11-22 18:11:36.771 7412-7412/com.dongua.toucheventtest I/click: onTouchEvent: ViewC11-22 18:11:36.771 7412-7412/com.dongua.toucheventtest I/click: onTouchEvent: ViewGroupB11-22 18:11:36.771 7412-7412/com.dongua.toucheventtest I/click: onTouchEvent: ViewGroupA
这就很好理解了吧 这次我们让TextView强行拦截,即ViewC的dispatchTouchEvent返回一个true,我们在看看打印的消息
@Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.i("click", "dispatchTouchEvent: ViewC"); return true; }
11-22 18:17:55.295 15435-15435/com.dongua.toucheventtest I/click: dispatchTouchEvent: ViewGroupA11-22 18:17:55.295 15435-15435/com.dongua.toucheventtest I/click: onInterceptTouchEvent: ViewGroupA11-22 18:17:55.296 15435-15435/com.dongua.toucheventtest I/click: dispatchTouchEvent: ViewGroupB11-22 18:17:55.296 15435-15435/com.dongua.toucheventtest I/click: onInterceptTouchEvent: ViewGroupB11-22 18:17:55.296 15435-15435/com.dongua.toucheventtest I/click: dispatchTouchEvent: ViewC
可以看到它只到ViewC的dispatchTouchEvent就结束了整个事件的拦截。因为ViewC我们手动设置了拦截,但ViewC继承自TextView,如果我们对onTouchEvent也强行返回true,那么就会执行到ViewC的onTouchEvent方法。
同理的大家可以试试在ViewGroupB的dispatchTouchEvent直接返回true,这就使得事件向下的传递到此为止。对onTouchEvent直接返回true,这就使得事件向上的响应到此为止。
例子就这些,大家想要熟悉的话肯定要自己去手动实现一遍,我觉得一定要在反复的看几遍文字的描述之后,再去写代码才能理解其中深意,盲目的堆代码只会让你的了解停留在表面。之前看完了老是不自己写一遍,结果一阵子之后又忘了,如果多做笔记,在做笔记的过程中无疑又是一次学习的过程。
以上为个人学习笔记,如有错误,请不吝赐教~
更多相关文章
- [置顶] 【小超_Android】2014年框架类源码年度精品汇总
- 从源码角度理解HandlerThread和IntentService
- emulator启动编译源码
- 收藏android源码项目
- Ubuntu 12.04(64位)下载并编译 Android(安卓)4.1 源码
- 开源直播系统源码Android中activity跳转动画效果
- ANDROID关于亮屏和暗屏还有解锁的监听事件http://blog.csdn.net/
- Android应用程序键盘(Keyboard)消息处理机制分析(6)
- Android(安卓)4.0源码下载