Android中事件处理机制之——ViewGroup的事件传递详解(一)

转载请注明出处:http://blog.csdn.net/xiaohao0724/article/details/54798908

通过对上篇 Android中事件处理机制之---View的事件分发详解(一) 的学习相信大家对Android事件处理机制都有了一定的了解。接下来今天我们继续来学习ViewGroup中的事件传递机制。

Android中的事件是从布局一层层向里面的布局或控件传递的,在传递过程中如果在哪层被拦(onInterceptTouchEvent返回true)则在这层被消费不会再向内层传递,如果传递到最内层事件没有完全响应将一层层向外传递,如果外层响应则消费掉事件,如果不响应将继续向外层传递直到最外层布局。

ViewGroup是根据onInterceptTouchEvent的返回值进行事件传递,当返回false时向内层传递,返回true是拦截事件不再向内层传递

    public boolean onInterceptTouchEvent(MotionEvent ev) {
	
        return false/true;
    }


我们先来新建一个工程EventViewGroup

自定义MyLinearlayout

public class MyLinearLayout extends LinearLayout {

	public MyLinearLayout(Context context) {
		super(context);
	}

	public MyLinearLayout(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public MyLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);

	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {

		return false;
	     // return super.onInterceptTouchEvent(ev);
	}

}

activity_main.xml

<com.havorld.eventviewgroup.view.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/myLinearLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:background="@android:color/holo_green_light"
        android:gravity="center"
        android:padding="10dp"
        android:text="TextView" />

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:padding="10dp"
        android:text="Button" />

</com.havorld.eventviewgroup.view.MyLinearLayout>


MainActivity.java

public class MainActivity extends Activity {

	protected static final String TAG = "TAG";

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		MyLinearLayout myLinearLayout = (MyLinearLayout) findViewById(R.id.myLinearLayout);
		TextView textView = (TextView) findViewById(R.id.textView);
		Button button = (Button) findViewById(R.id.button);

		myLinearLayout.setOnTouchListener(new OnTouchListener() {

			@Override
			public boolean onTouch(View v, MotionEvent event) {

				Log.e(TAG, "myLinearLayout---onTouch");
				return false;
			}
		});

		textView.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {

				Log.e(TAG, "textView---onClick");
			}
		});
		button.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {

				Log.e(TAG, "button---onClick");
			}
		});

	}

}

当点击某个控件时,首先会去调用该控件所在布局父类ViewGroup中的dispatchTouchEvent方法,然后在该方法中遍历子控件找到被点击的对应控件,再去调用该控件的父类View中的dispatchTouchEvent方法。

上一篇Android中事件处理机制之---View的事件分发详解(一) 我们已经对View中的dispatchTouchEvent进行过分析,现在我们来看一下ViewGroup中的dispatchTouchEvent源码:

public boolean dispatchTouchEvent(MotionEvent ev) {
    final int action = ev.getAction();
    final float xf = ev.getX();
    final float yf = ev.getY();
    final float scrolledXFloat = xf + mScrollX;
    final float scrolledYFloat = yf + mScrollY;
    final Rect frame = mTempRect;
    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (action == MotionEvent.ACTION_DOWN) {
        if (mMotionTarget != null) {
            mMotionTarget = null;
        }
		//标记①
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {
            ev.setAction(MotionEvent.ACTION_DOWN);
            final int scrolledXInt = (int) scrolledXFloat;
            final int scrolledYInt = (int) scrolledYFloat;
            final View[] children = mChildren;
            final int count = mChildrenCount;
			//标记②
            for (int i = count - 1; i >= 0; i--) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                        || child.getAnimation() != null) {
                    child.getHitRect(frame);
					//标记③
                    if (frame.contains(scrolledXInt, scrolledYInt)) {
                        final float xc = scrolledXFloat - child.mLeft;
                        final float yc = scrolledYFloat - child.mTop;
                        ev.setLocation(xc, yc);
                        child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
						//标记④
                        if (child.dispatchTouchEvent(ev))  {
                            mMotionTarget = child;
							//标记⑤
                            return true;
                        }
                    }
                }
            }
        }
    }
    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
            (action == MotionEvent.ACTION_CANCEL);
    if (isUpOrCancel) {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }
    final View target = mMotionTarget;
	//标记⑥
    if (target == null) {
        ev.setLocation(xf, yf);
        if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
            ev.setAction(MotionEvent.ACTION_CANCEL);
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
        }
		//标记⑦
        return super.dispatchTouchEvent(ev);
    }
    if (!disallowIntercept && onInterceptTouchEvent(ev)) {
        final float xc = scrolledXFloat - (float) target.mLeft;
        final float yc = scrolledYFloat - (float) target.mTop;
        mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
        ev.setAction(MotionEvent.ACTION_CANCEL);
        ev.setLocation(xc, yc);
        if (!target.dispatchTouchEvent(ev)) {
        }
        mMotionTarget = null;
        return true;
    }
    if (isUpOrCancel) {
        mMotionTarget = null;
    }
    final float xc = scrolledXFloat - (float) target.mLeft;
    final float yc = scrolledYFloat - (float) target.mTop;
    ev.setLocation(xc, yc);
    if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
        ev.setAction(MotionEvent.ACTION_CANCEL);
        target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
        mMotionTarget = null;
    }
    return target.dispatchTouchEvent(ev);
}

一、对MyLinearLayoutTextView进行事件分析

分别点击TextView和空白处

1、onInterceptTouchEvent返回false



分析:

当点击了TextView时会先调用MyLinearLayout父控件ViewGroup中的dispatchTouchEvent,然后看源码的标记①处disallowIntercept || !onInterceptTouchEvent(ev),当两个条件有一个成立时进入判断中。disallowIntercept是指是否禁用掉事件拦截的功能,默认是false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改(不常用),第二个条件的值就是自定义MyLinearLayout复写onInterceptTouchEvent方法的返回值取反,此处我们返回的是false所以!onInterceptTouchEvent(ev) = true进入判断方法中,看标记②对布局中的子View进行遍历,在标记③处进行判断哪个View是所点击的View,在标记④处调用子View的事件分发机制child.dispatchTouchEvent(ev)并返回true执行TextView的onClick方法(注意:此处子View是TextView由于设置了onCick事件clickable 为真才返回true, 此处不明白的同学可参考上篇Android中事件处理机制之---View的事件分发详解(一)  ),此时进入方法内在标记⑤处也就整个dispatchTouchEvent方法直接返回true后面的不再执行。

当点击空白处时先调用MyLinearLayout父控件ViewGroup中的onInterceptTouchEvent,标记③处进行判断没有子View被点击,因为点击的是父布局,所以跳出判断继续向下执行到标记⑥处,由于没有运行标记④处所以没有跟mMotionTarget赋值,此时target = mMotionTarget = null进入判断方法中,在MyLinearLayout父控件ViewGroup内标记⑦处调用super.dispatchTouchEvent(ev)也就是ViewGroup的父控件View的事件分发机制(具体可参考Android中事件处理机制之---View的事件分发详解(一)  ) 所以执行MyLinearLayout的onTouch方法

2、onInterceptTouchEvent返回true

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {

		return true;
	// return super.onInterceptTouchEvent(ev);
	}



分析:

因为onInterceptTouchEvent返回true所以不管点击TextView还是MyLinearLayout在标记①处的判断都为false,所以不进入判断方法内,在标记⑥处进行判断进入方法内在标记⑦处把事件分发给MyLinearLayout父控件ViewGroup内标记⑦处调用super.dispatchTouchEvent(ev)也就是ViewGroup的父控件View,所以两次点击都执行MyLinearLayout的onTouch方法


下面我们来做个拓展:

将TextView的onClick事件代码替换成onTouch事件(onInterceptTouchEvent返回false)

		textView.setOnTouchListener(new OnTouchListener() {
			@Override
			public boolean onTouch(View v, MotionEvent event) {
				Log.e(TAG, "textView---onTouch---" + event.getAction());
				return false;
			}
		});


点击TextView


分析:

点击TextView调用MyLinearLayout父控件ViewGroup中的dispatchTouchEvent,标记① 处  !onInterceptTouchEvent(ev) = true进入方法中,在标记④调用的child.dispatchTouchEvent(ev)正是TextView的事件分发处理,根据上篇我们对事件分发的学习不难得出此处会只调用一次onTouch事件TextView执行按下事件并且child.dispatchTouchEvent(ev)的返回值为false,所以不会执行到标记⑤处返回而是直接执行到标记⑦处调用MyLinearLayout的父控件ViewGroup的super.dispatchTouchEvent(ev)事件分发,所以会执行MyLinearLayout的触摸事件

这是父类布局和子控件结合在一块时的情况,各种组合多种多样但万变不离其宗,比如

当TextView的onTouch事件返回true时(onInterceptTouchEvent返回false)点击TextView:



MyLinearLayout的触摸事件返回true时(T(onInterceptTouchEvent返回false,textView的onTouch返回false)点击一次TextView:



添加Activity的触摸事件

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		
		Log.e(TAG, "activity---onTouch");
		return super.onTouchEvent(event);
	}
MyLinearLayout和textView的触摸事件都 返回false时( T(onInterceptTouchEvent返回false )点击一次TextView



总结一下:


二、对MyLinearLayout和Button进行事件分析

需要注意的是默认情况下Button按钮的clickable = true,其他分析过程跟TextView是一样的,大家可以自己试着分析一下哈!通过这两篇的学习大家多Android事件处理机制是不是有了比较深刻的了解了呢?


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值