Android事件分发机制二

来源:互联网 发布:山东双轨直销软件 编辑:程序博客网 时间:2024/05/22 12:13

创建Demo

MainActivity.java

public class MainActivity extends Activity {  @Override  protected void onCreate(Bundle savedInstanceState) {      super.onCreate(savedInstanceState);      setContentView(R.layout.activity_main);      //初始化控件      MyLinearLayout mgLayout = (MyLinearLayout) findViewById(R.id.mylayout);      Button btn = (Button) findViewById(R.id.btn);      //添加onClick事件      mgLayout.setOnClickListener(new View.OnClickListener() {          @Override          public void onClick(View view) {              Log.i("chuyibo","点击了自定义线性布局MyLinearLayout");          }      });      //添加onClick事件      btn.setOnClickListener(new View.OnClickListener() {          @Override          public void onClick(View view) {              Log.i("chuyibo","点击了btn");          }      });  }}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?><bo.yi.chu.chuyibo2.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/mylayout"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:gravity="center"    android:background="#fff">    <Button        android:layout_width="150dp"        android:layout_height="50dp"        android:id="@+id/btn"        android:background="#684132"        android:text="点击"        android:textSize="16sp"        android:textColor="#fff"/></bo.yi.chu.chuyibo2.MyLinearLayout>

MyLinearLayout.java

public class MyLinearLayout extends LinearLayout {  public MyLinearLayout(Context context, AttributeSet attrs) {      super(context, attrs);  }  @Override  public boolean onInterceptTouchEvent(MotionEvent ev) {      return false;  }}

运行

这里写图片描述

点击Button查看Log日志

这里写图片描述

点击空白区域查看Log日志

这里写图片描述

查看日志得出结论,myLayout为btn的父类,两者同时添加onClick监听,点击btn,只有btn的onClick()方法执行。点击myLayout(空白区域)只有myLayout的onClick()方法执行,Demo中MyLinearLayout继承LinearLayout,并重写了onInterceptTouchEvent()。

onInterceptTouchEvent()方法中有一个返回值,Demo中的返回值为false。把该方法的返回值改为true,重新点击Button和空白区域查看Log日志。

public boolean onInterceptTouchEvent(MotionEvent ev) {    return true;//返回值改为true}

点击Button查看Log日志

这里写图片描述

点击空白区域查看Log日志

这里写图片描述

查看日志得出结论,当onInterceptTouchEvent()方法返回true时,无论点击btn还是myLayout,都只执行myLayout的onClick()方法。

android中的布局直接或者间接的继承了ViewGroup,ViewGroup又是View的子类,但是ViewGroup重写了dispatchTouchEvent()方法。ViewGroup中可以存放多个View,当点击一个ViewGroup中的子View时触摸事件是先传递给了ViewGroup,然后有ViewGroup的dispatchTouchEvent()分发给子View,最后再有子View的dispatchTouchEvent()来处理触摸事件。同时ViewGroup也可以拦截事件从而使触摸事件不传递给子View,onInterceptTouchEvent()方法就是事件传递的拦截器。

源码分析

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) {                // this is weird, we got a pen down, but we thought it was                // already down!                // XXX: We should probably send an ACTION_UP to the current                // target.                mMotionTarget = null;            }            // If we're disallowing intercept or if we're allowing and we didn't            // intercept            if (disallowIntercept || !onInterceptTouchEvent(ev)) {                // 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 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)) {                            // offset the event to the view's coordinate system                            final float xc = scrolledXFloat - child.mLeft;                            final float yc = scrolledYFloat - child.mTop;                            ev.setLocation(xc, yc);                            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.                        }                    }                }            }        }        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||                (action == MotionEvent.ACTION_CANCEL);        if (isUpOrCancel) {            // Note, we've already copied the previous state to our local            // variable, so this takes effect on the next event            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;        }        // 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.            ev.setLocation(xf, yf);            return super.dispatchTouchEvent(ev);        }        // if have a target, see if we're allowed to and want to intercept its        // events        if (!disallowIntercept && onInterceptTouchEvent(ev)) {            final float xc = scrolledXFloat - (float) target.mLeft;            final float yc = scrolledYFloat - (float) target.mTop;            ev.setAction(MotionEvent.ACTION_CANCEL);            ev.setLocation(xc, yc);            if (!target.dispatchTouchEvent(ev)) {                // target didn't handle ACTION_CANCEL. not much we can do                // but they should have.            }            // clear the target            mMotionTarget = null;            // Don't dispatch this event to our own view, because we already            // saw it when intercepting; we just want to give the following            // event to the normal onTouchEvent().            return true;        }        if (isUpOrCancel) {            mMotionTarget = null;        }        // finally offset the event to the target's coordinate system and        // dispatch the event.        final float xc = scrolledXFloat - (float) target.mLeft;        final float yc = scrolledYFloat - (float) target.mTop;        ev.setLocation(xc, yc);        return target.dispatchTouchEvent(ev);    }

第21行是一个if判断,disallowIntercept表示是否禁止掉拦截事件的功能,默认为false,因此if判断为true或false取决于onInterceptTouchEvent()方法的返回值。

onInterceptTouchEvent()方法返回false表示不拦截事件。if条件判断成立,在第30行对ViewGroup中的子View进行遍历,并在第35行进行判断当前子View是否是正在点击的View。如果是,执行View的dispatchTouchEvent()。因为View的dispatchTouchEvent()方法始终返回true,所以40行if条件判断成立,执行return,后面的代码不执行。

onInterceptTouchEvent()方法返回true表示拦截事件,if条件判断不成立。或ViewGroup中没有子View,以及ViewGroup中的所有子View都不是当前正在点击的View时执行后面的语句。第66行判断target是否为null。target一般不会为null,执行第70行super.dispatchTouchEvent()就是调用父类View中的dispatchTouchEvent()方法。

流程图

这里写图片描述

原创粉丝点击