Android——View、ViewGroup事件(Touch事件)处理机制总结

Android中的事件

Touch事件,四种状态:

ACTION_DOWN     ——>   表示按下了屏幕,一个事件必然从ACTION_DOWN开始

ACTION_MOVE      ——>   表示移动手势

ACTION_UP            ——>  表示离开屏幕

ACTION_CANCEL  ——>   表示取消手势,一般由程序产生,不会由用户产生

一个ACTION_DOWN, n个ACTION_MOVE,1个ACTION_UP,就构成了Android中众多的事件。

Android中的事件onClick, onScroll, onFling等等,都是由许多个Touch组成的。

一个原则,所有的touch事件都是从父容器开始向下传递的,呈U字形。

View事件处理机制核心代码

Android中诸如ImageView、textView、Button等控件都没有重写View的dispatchTouchEvent方法,所以View的事件处理机制对这些控件都有效。

View.java(基于android2.3.3):

    public boolean dispatchTouchEvent(MotionEvent event) {//返回true,表示该View内部消化掉了所有事件。返回false,表示View内部只处理了ACTION_DOWN事件,事件继续传递,向上级View(ViewGroup)传递。
	...
            if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {//此处的onTouch方式就是回调的我们注册OnTouchListener时重写的onTouch()方法
                return true;
            }

            if (onTouchEvent(event)) {// onTouchEvent参考下面源码
                return true;
            }
        ...
    }
    public boolean onTouchEvent(MotionEvent event) {
        ...

        // 当前onTouch的组件必须是可点击的比如Button,ImageButton等等,此处CLICKABLE为true,才会进入if方法,最后返回true。
	// 如果是ImageView、TexitView这些默认为不可点击的View,此处CLICKABLE为false,最后返回false。当然会有特殊情况,如果给这些View设置了onClick监听器,此处CLICKABLE也将为true,参考下面源码
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    ...
                                if (!post(mPerformClick)) {
                                    performClick();// 实际就是回调了我们注册的OnClickListener中重新的onClick()方法,源码下面源码
                                }
                     ...
                    break;

                case MotionEvent.ACTION_DOWN:
                   ...
                    break;

                case MotionEvent.ACTION_CANCEL:
                    ...
                    break;

                case MotionEvent.ACTION_MOVE:
                   ...
                    break;
            }
            return true;
        }

        return false;
    }
    public void setOnClickListener(OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }
    public boolean performClick() {
        ...

        if (li != null && li.mOnClickListener != null) {
            ...
            li.mOnClickListener.onClick(this);
            return true;
        }

        return false;
    }

总结:

只有我们注册OnTouchListener时重写的onTouch()方法中返回false  ——> 执行onTouchEvent方法 ——>  导致onClick()回调方法执行

onTouch()方法返回true ——> onTouchEvent方法不执行 ——>  导致onClick()回调方法不会执行

ViewGroup事件处理机制核心代码

Android中诸如LinearLayout等的五大布局控件,都是继承自ViewGroup,而ViewGroup本身是继承自View,所以ViewGroup的事件处理机制对这些控件都有效。

ViewGroup.java(基于android2.3.3):

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

	...

	if (action == MotionEvent.ACTION_DOWN) {
		if (mMotionTarget != null) {
			mMotionTarget = null;
		}
		//onInterceptTouchEvent返回false,说明向下传递
		//onInterceptTouchEvent返回true,说明拦截
		if (disallowIntercept || !onInterceptTouchEvent(ev)) {

			...
			// 伪代码如下:
			//1,找到当前控件子控件
			//2,判断当前touch的点的坐标(x,y)在哪个子控件的矩形区域内
			//3,判断当前子控件是viewgroup的子类对象,还是view的子类对象
				//3.1  如果是viewgroup的子类: 调用其dispatchTouchEvent方法,上述操作再来一遍
				//3.2  view 尝试让当前view去处理这个事件(
				             true,dispatchTouchEvent方法结束,并且返回true
				             false,dispatchTouchEvent继续向下执行)

			...

		}
	}

	...

	 target = mMotionTarget
	//target一定是null
	if (target == null) {

		...

		//调用当前viewgroup的父View的处理事件的方法
		return super.dispatchTouchEvent(ev);
	}

   ...

}
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;// 默认返回false
    }

总结:

1、dispatchTouchEvent作用:决定事件是否由onInterceptTouchEvent来拦截处理。

返回super.dispatchTouchEvent时,由onInterceptTouchEvent来决定事件的流向

返回false时,会继续分发事件,自己内部只处理了ACTION_DOWN

返回true时,不会继续分发事件,自己内部处理了所有事件(ACTION_DOWN,ACTION_MOVE,ACTION_UP)

2、onInterceptTouchEvent作用:拦截事件,用来决定事件是否传向子View

返回true时,拦截后交给自己的onTouchEvent处理

返回false时,拦截后交给子View来处理

3、onTouchEvent作用:事件最终到达这个方法

返回true时,内部处理所有的事件,换句话说,后续事件将继续传递给该view的onTouchEvent()处理

返回false时,事件会向上传递,由onToucEvent来接受,如果最上面View中的onTouchEvent也返回false的话,那么事件就会消失

综合案例分析

以下摘自:http://www.longdw.com/android-onintercepttouchevent-ontouchevent/

源码:

public class MainActivity extends Activity {
	Group1 group1;
	Group2 group2;
	MyTextView myTv;

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		//--group1
		//----|
		//-------group2
		//---------|
		//------------myTv

		group1 = new Group1(this);
		group2 = new Group2(this);
		myTv = new MyTextView(this);
		group2.addView(myTv, new LayoutParams(LayoutParams.FILL_PARENT,
				LayoutParams.FILL_PARENT));
		group1.addView(group2, new LayoutParams(LayoutParams.FILL_PARENT,
				LayoutParams.FILL_PARENT));
		setContentView(group1);
	}
}
public class Group1 extends FrameLayout {

	public Group1(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		// TODO Auto-generated method stub
		Log.d(Constant.LOGCAT, "Group1 onInterceptTouchEvent触发事件:"+Constant.getActionTAG(ev.getAction()));
		return false;
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub
		Log.d(Constant.LOGCAT, "Group1 onTouchEvent触发事件:"+Constant.getActionTAG(event.getAction()));
		return false;
	}
}
public class Group2 extends FrameLayout {

	public Group2(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		// TODO Auto-generated method stub
		Log.d(Constant.LOGCAT, "Group2 onInterceptTouchEvent触发事件:"+Constant.getActionTAG(ev.getAction()));
		return false;
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub
		Log.d(Constant.LOGCAT, "Group2 onTouchEvent触发事件:"+Constant.getActionTAG(event.getAction()));
		return false;
	}
}
public class MyTextView extends TextView {

	public MyTextView(Context context) {
		super(context);
		this.setGravity(Gravity.CENTER);
		this.setText("点击我!");
		// TODO Auto-generated constructor stub
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub
		Log.d(Constant.LOGCAT, "MyTextView onTouchEvent触发事件:"+Constant.getActionTAG(event.getAction()));
		return false;
	}
}
public class Constant {
	public static final String LOGCAT = "logcat";

	public static String getActionTAG(int action) {
		switch (action) {
		case 0:
			return "ACTION_DOWN";
		case 1:
			return "ACTION_UP";
		case 2:
			return "ACTION_MOVE";
		default:
			return "NULL";
		}
	}
}

分别重写Group1和Group2的onInterceptTouchEvent和onTouchEvent方法,重写MyTextView的onTouchEvent方法,最终得到的控件层次结构如下:

1.在默认返回值情况下logcat输出如下:

测试后可知默认情况下和所有方法返回值为false的结果一致,down事件的捕获顺序onInterceptTouchEvent先于onTouchEvent,由于onTouchEvent返回值为false,down事件没被消化,后续的move和up事件没有出现,同时逆序返回到父控件的onTouchEvent方法来捕获,如下图所示:

2.所有onTouchEvent返回值为true情况下logcat输出如下:

输出结果可以看出子控件MyTextView消化了down事件,后续的move和up事件正常捕获,由于down事件被消化,上层的onTouchEvent方法不执行,如下图所示:(三箭头分别指down、move、up事件)

既然如此,如果MyTextView中onTouchEvent方法返回为false,而group1和group2的onTouchEvent方法返回true的结果自然也就如下图的顺序了:

测试输出结果证明了这一猜测顺序:

注意:可能有人对这种情况比较疑惑,ACTION_DOWN还好理解,但是ACTION_MOVE为什么没有经历myTv,而且ACTION_MOVE只经历了group1的onInterceptTouchEvent和group2的onTouchEvent而没有经历group2的onInterceptTouchEvent?我开始也费解,后来想想也是,大家对比第1条,由于onTouchEvent返回了false而没有消耗down事件导致后续的move和up都没有出现,这里也是一样由于myTv中onTouchEvent返回了false也就是说没有消耗down事件,那么后面的move和up也都不会出在这个view里面,但是group2截获到了down事件,但后来的move为什么group2中的onInterceptTouchEvent没有执行到呢,原因大家不要忘记了onInterceptTouchEvent的初衷是什么,返回false是让它的子view或viewgroup类处理,而group2的子控件显然是myTv而myTv的onTouchEvent返回了false也就是接收不到后续的move和up事件,也就没必要经过onInterceptTouchEvent来继续分发了(因为分发了也还是接收不到),经过group2的onTouchEvent因为它返回的是true,截获了事件并且消耗了事件。

3.当某个GroupView中的onInterceptTouchEvent方法返回值为true情况下logcat输出如下(如group2):

如果在该方法返回值中返回true,那么子控件将获取不到任何点击事件,转而向自身的onTouchEvent方法转发,如下图所示:

如果onTouchEvent方法返回值都为true,那么根据规律结果就如下图顺序触发:

最后logcat的结果证实了这一猜测:

还有一篇文章也比较好,可作为这个案例的补充,http://orgcent.com/android-touch-event-mechanism/



时间: 2024-10-12 08:36:48

Android——View、ViewGroup事件(Touch事件)处理机制总结的相关文章

【Android 1.6】View和ViewGroup的touch事件分析和总结

ENV: android 1.6 目前Android版本已经到了7.0(nougat)了,Android 随着版本升级,touch事件的源码也在跟随着系统的升级而写得越来越复杂,加入了很多旁枝末节,这些旁枝末节,对于分析流程是一种干扰:由于Android的版本升级是向下兼容的,万变不离其宗,研究Android早期的版本,可以更容易理解touch事件的分发,本篇以Android1.6版本的源码进行讲解,由简及繁,理解了早期的源码,再进入高版本的研究也会更容易许多. 前言: View事件的派发其实非

自定义View系列教程07--详解ViewGroup分发Touch事件

自定义View系列教程01–常用工具介绍 自定义View系列教程02–onMeasure源码详尽分析 自定义View系列教程03–onLayout源码详尽分析 自定义View系列教程04–Draw源码分析及其实践 自定义View系列教程05–示例分析 自定义View系列教程06–详解View的Touch事件处理 自定义View系列教程07–详解ViewGroup分发Touch事件 PS:如果觉得文章太长,那就直接看视频吧 在上一篇中已经分析完了View对于Touch事件的处理,在此基础上分析和理

解决 android.view.ViewGroup$LayoutParams cannot be cast to android.widget.AbsListView$LayoutParams

错误日志1: 06-13 10:55:50.410: E/KVLog(1129): Error info:java.lang.ClassCastException: android.widget.LinearLayout$LayoutParams06-13 10:55:50.423: E/KVLog(1129): Cause Result:java.lang.ClassCastException: android.widget.LinearLayout$LayoutParams06-13 10:

Android java.lang.StackOverflowError at android.view.ViewGroup.drawChild(ViewGroup.java:2666)

做这样一个页面,下面有五个页签,滑动,有数据显示listview,无数据显示动画,开始我看到他们好多东西是相同的,就提取出来,用include包含布局文件: 结果报一下错误: 01-28 11:27:36.593: E/AndroidRuntime(16182): FATAL EXCEPTION: main 01-28 11:27:36.593: E/AndroidRuntime(16182): java.lang.StackOverflowError 01-28 11:27:36.593: E

android.view.ViewGroup$LayoutParams cannot be cast to android.widget.AbsListView$LayoutParams

LinearLayout layout = (LinearLayout) mInflater.inflate( R.layout.cell_check_item, null); LinearLayout.LayoutParams param = new LinearLayout.LayoutParams( UPDeviceInfo.getDeviceWidth(), LayoutParams.WRAP_CONTENT); rootLayout.addView(convertView, param

java.lang.ClassCastException: android.view.ViewGroup$LayoutParams cannot be cast to android.widget.L(转)

09-09 10:19:59.979: E/AndroidRuntime(2767): FATAL EXCEPTION: main09-09 10:19:59.979: E/AndroidRuntime(2767): java.lang.ClassCastException: android.view.ViewGroup$LayoutParams cannot be cast to android.widget.LinearLayout$LayoutParams09-09 10:19:59.97

NullPointerException: Attempt to invoke virtual method 'android.view.ViewGroup$LayoutParam

最近在做和图片相关显示的出现了一个问题,整理一下思路,分享出来给大家参考一下: 下面是一个空指针的异常,是在GalleryAdapter中出现的. // Short Msg: java.lang.NullPointerException // Long Msg: java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.ViewGroup$LayoutParams android.view.Vie

android自定义控件系列教程-----touch事件的传递

前沿: 很久没有写过博客了,因为工作的原因很少有时间写东西了,最近想写一个UI系列的博客,因为我发现这一系列的都很少,而且没有那么系统,这里我想以我自己的观点来阐述一下如何自定义android 控件系列. 自定义控件阐述: 在我的理解里面自定义控件,需要了解到touch事件的传递.分发.拦截机制,Scroller类的运用,andorid 视图的理解,ViewGroup的熟悉,因为我们绝大多的控件都是继承自ViewGroup,还有就是要学会布局测量等. Touch事件的传递 首先我们要了解在and

View源码-Touch事件

在Android-27中查看源码: 首先我们来查看单个View的触摸事件的处理,在View的dispatchTouchEvent方法中看看源码是如何处理的. public boolean dispatchTouchEvent(MotionEvent event) { // If the event should be handled by accessibility focus first. if (event.isTargetAccessibilityFocus()) { // We don'

手机端html5触屏事件(touch事件)

touchstart:触摸开始的时候触发 touchmove:手指在屏幕上滑动的时候触发 touchend:触摸结束的时候触发 而每个触摸事件都包括了三个触摸列表,每个列表里包含了对应的一系列触摸点(用来实现多点触控): touches:当前位于屏幕上的所有手指的列表. targetTouches:位于当前DOM元素上手指的列表. changedTouches:涉及当前事件手指的列表. 每个触摸点由包含了如下触摸信息(常用): identifier:一个数值,唯一标识触摸会话(touch ses