首页 > 代码库 > android下ViewGroup的事件分发和处理

android下ViewGroup的事件分发和处理

先写个简单的demo:

布局文件中一个继承自ViewGroup的自定义控件MyLayout包含一个Button:

<RelativeLayout 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">

    <com.example.viewgroupdemo.MyLayout
        android:id="@+id/layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/hello_world" />
    </com.example.viewgroupdemo.MyLayout>

</RelativeLayout>


自定义控件中重写事件分发的两个重要方法:onInterceptTouchEvent 和 dispatchTouchEvent

public class MyLayout extends LinearLayout {

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

	//是否拦截事件的传递,true:拦截
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		//false:把touch事件传递到子控件
		return false;
	}
	
	//LinearLayout并没有重写dispatchTouchEvent
	//ViewGroup重写了View的dispatchTouchEvent方法
	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		return super.dispatchTouchEvent(ev);
	}
}
在MainActivity中设置两个控件的点击事件:

		layout.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				Log.i(tag, "click layout --------");
			}
		});
		
		button.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				Log.i(tag, "click button --------");
			}
		});

此时点击Button,由于在外层MyLayout没有阻止事件的传递,所以Button响应并处理了事件,打印"click button"的log

如果在MyLayout的onInterceptTouchEvent 中return true则表示MyLayout阻止了事件的传递,此时打印"click layout"

问题:当点击屏幕时系统如何确定是哪个view被点中呢?

实际上每个view对应屏幕上的一块矩形区域,当点击屏幕时系统通过判断该点属于哪块矩形区域来确定哪个view被选中

查看源码解释现象:

<pre name="code" class="java"> public boolean dispatchTouchEvent(MotionEvent ev) {
        ......
        //在按下时处理
        if (action == MotionEvent.ACTION_DOWN) {
        	//先将view对象置为null
            if (mMotionTarget != null) {
                mMotionTarget = null;
            }
            //如果事件传递没有被阻止,向内传递
            if (disallowIntercept || !onInterceptTouchEvent(ev)) {
            	/**
            	 * 步骤总结:
            	 * //1,找到当前控件子控件
					//2,判断当前点中点,所在的(x,y)属于哪个子控件的矩形区域内
					//3,判断当前子控件是viewgroup的子类对象,还是view的子类对象
					    //3.1  viewgroup 上述操作再来一遍
						//3.2  view 尝试让当前view去处理这个事件(
						true,dispatchTouchEvent方法结束,并且返回true
						false,当前没有返回值)
            	 */
                // reset this event's action (just to protect ourselves)
                ev.setAction(MotionEvent.ACTION_DOWN);
                // We know we want to dispatch the event down, find a child
                // who can handle it, start with the front-most child.
                final View[] children = mChildren;
                //获取当前ViewGroup子节点的个数
                final int count = mChildrenCount;
                //对当前ViewGroup子节点进行遍历循环,通过判断点中的点包含在哪个子节点确定哪个View被选中
                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)) {
                        	.......
                            //这里的事件分发即有可能是ViewGroup也可能是View,但最终调用的是view的dispatchTouchEvent方法
                            //注意:viewgroup是有重写过view的view的dispatchTouchEvent方法 	
                            //如果为true,说明这view个子节点处理了事件,事件响应完毕,可参考下一行goole工程师的注释~~
                            //如果是false,则继续走后续到的逻辑
                            if (child.dispatchTouchEvent(ev))  {
                                // Event handled, we have a target now.
                                mMotionTarget = child;
                                return true;
                            }
                        }
                    }
                }
            }
        }
        ......省略无关代码
        // The event wasn't an ACTION_DOWN, dispatch it to our target if
        // we have one.
        final View target = mMotionTarget;
        if (target == null) {
            // We don't have a target, this means we're handling the
            // event as a regular view.
            //调用当前对象父view的事件分发规则,注意当前对象为viewgroup对象,所以父view是View
            return super.dispatchTouchEvent(ev);
        }


android下ViewGroup的事件分发和处理