您的位置:首页 > 移动开发 > Android开发

Android事件分发机制(二)

2016-04-21 10:18 435 查看
上篇Android事件分发机制(一)我们已经讲述了View的dispatchTouchEvent方法,今天我们来看一下ViewGroup的事件传递。先来看下什么是ViewGroup,ViewGroup是一组View的集合,它可以包含子View或者子ViewGroup,我们常用的一些布局都是继承自ViewGroup,当然他也是View的子类,只不过是可以包含子View和子ViewGroup。下面我们自定一个布局来看一下ViewGroup的传递机制。

public class MyLayout extends LinearLayout {

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

}

然后引入我们的布局:

<com.example.viewgrouptouchevent.MyLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/my_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button1" />

<Button
android:id="@+id/button2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button2" />

</com.example.viewgrouptouchevent.MyLayout>

在activity中我们给两个button和布局添加监听:

myLayout.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("TAG", "myLayout on touch");
return false;
}
});
button1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TAG", "You clicked button1");
}
});
button2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TAG", "You clicked button2");
}
});

我们来分别点击btn1,btn2,空白区域打印结果如下:

通过结果看到,点击按钮的时候不会触发父布局的touch方法,难道是从btn分发到父布局的?查阅文档,看到ViewGroup除了dispatchTouchEvent方法还有一个onInterceptTouchEvent方法,那我们先来看下它的源码

/**
* Implement this method to intercept all touch screen motion events.  This
* allows you to watch events as they are dispatched to your children, and
* take ownership of the current gesture at any point.
*
* <p>Using this function takes some care, as it has a fairly complicated
* interaction with {@link View#onTouchEvent(MotionEvent)
* View.onTouchEvent(MotionEvent)}, and using it requires implementing
* that method as well as this one in the correct way.  Events will be
* received in the following order:
*
* <ol>
* <li> You will receive the down event here.
* <li> The down event will be handled either by a child of this view
* group, or given to your own onTouchEvent() method to handle; this means
* you should implement onTouchEvent() to return true, so you will
* continue to see the rest of the gesture (instead of looking for
* a parent view to handle it).  Also, by returning true from
* onTouchEvent(), you will not receive any following
* events in onInterceptTouchEvent() and all touch processing must
* happen in onTouchEvent() like normal.
* <li> For as long as you return false from this function, each following
* event (up to and including the final up) will be delivered first here
* and then to the target's onTouchEvent().
* <li> If you return true from here, you will not receive any
* following events: the target view will receive the same event but
* with the action {@link MotionEvent#ACTION_CANCEL}, and all further
* events will be delivered to your onTouchEvent() method and no longer
* appear here.
* </ol>
*
* @param ev The motion event being dispatched down the hierarchy.
* @return Return true to steal motion events from the children and have
* them dispatched to this ViewGroup through onTouchEvent().
* The current target will receive an ACTION_CANCEL event, and no further
* messages will be delivered here.
*/
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}

注释比较多,方法却很简单,直接返回了一个false。那我们在自定义布局里面重写一下这个方法,让他返回true试一下

public class MyLayout extends LinearLayout {

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

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
}

}

打印结果:



点击btn不会触发点击动作了,只会触发父布局的touch方法,那如果是从View到ViewGroup,那么父布局是怎么拦截的呢?我们来看一下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);
}

ViewGroup的dispatchTouchEvent方法要比View的dispatchTouchEvent方法复杂一些,我们还是来看重点,13行的判断条件,第一个是disallowIntercept,这个意思是禁止事件拦截,默认是false,可以通过requestDisallowInterceptTouchEvent方法对这个值进行修改,那么只要是onInterceptTouchEvent返回true就会进入下面的方法,我们上面说道onInterceptTouchEvent默认是返回false的呀,仔细看下你会发现,判断条件里是取反的!也就是返回false正好能进入。也就是可以理解为返回true代表拦截事件,false返回不拦截。

再来看进入以后的方法,进入以后呢就是对ViewGroup内的View进行遍历,直到找到被点击的对象,然后调用他的dispatchTouchEvent方法。接着就是我们上一篇的内容了,返回true就不会执行后面的代码了,可见我们重写onInterceptTouchEvent返回true就不会进入13行的判断了。如果点击空白区域,就不会在31行返回true,看44行判断target,一般都会返回null,那么继续执行super.dispatchTouchEvent(ev); 也就是调用父类View的dispatchTouchEvent(ev)了,因此我们重写的父布局的touch方法得到执行。

我们借用一张流程图来看下整个事件的传递过程:



总结一下:

Android 的事件传递机制是从ViewGroup往下传到View的,如果ViewGroup对事件进行了拦截,也就是onInterceptTouchEvent方法返回了true,事件就不会往下传递,由ViewGroup本身消费。

子View如果消费了事件,那么ViewGroup不会接收到任何事件。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Android事件分发