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

关于android事件分发之ViewGroup

2015-06-04 21:56 483 查看
感觉现在对touch事件的分发运用的越来越熟练,赶紧记录下来,免得以后忘记了。上一篇博客已经详细的讲解了关于View的touch事件的,感觉还不过瘾,赶紧把ViewGroup事件的传递也总结一下吧。

还是通过具体的实例进行总结吧。

实例中自定义MyLayout继承自LinearLayout。请看布局代码。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<com.example.toucheventdemo.MyLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/ll"
>

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="click 1"
android:id="@+id/btn1"
/>

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="click 2"
android:id="@+id/btn2"
/>

</com.example.toucheventdemo.MyLayout>

</LinearLayout>


有两个button。我们分别注册click和touch事件。请看代码。

btn1.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
Log.d(tag, "button 1 click");
}
});

btn2.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
Log.d(tag, "button 1 click");
}
});

layout.setOnTouchListener(new OnTouchListener() {

@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d(tag, "touch");
return false;
}
});


请两个button注册了单击事件,和布局注册了touch事件。

我现在分别点击button1和button2,并且触摸了button以外的区域。输出结果如下:

06-04 23:34:48.096: D/MainActivity(19416): button 1 click
06-04 23:34:50.646: D/MainActivity(19416): button 1 click
06-04 23:34:51.496: D/MainActivity(19416): touch


点击button1和button2时仅仅是执行了onclick,却没有执行ontouch。那我们就有一个疑问了,这个touch事件到底是从ViewGroup传递给View呢,还是从View传递到ViewGroup。关于这个问题,我们可以先放一放。先看看ViewGroup中的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;
}


这个方法的作用就是判断是否拦截touch事件的分发,如果返回true,表示拦截,这时候就不会传递给子View,如果返回false,表示不拦截,正常传递给View即可。所有说touch事件的传递应该是从ViewGroup传递给View,需要层层传递的。那我们不妨在MyLayout方法中重写onInterceptTouchEvent方法,让它返回true,拦截一下试试,拦截以后,button应该是不能响应onclick事件才对,这时候MyLayout就不会讲touch事件传递给Button了,点击时只会执行MyLayout的touch事件才对。不妨拭目以待吧。

public class MyLayout extends LinearLayout {

public MyLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}

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

}


输出结果为:

06-04 23:50:28.736: D/MainActivity(23257): touch
06-04 23:50:30.306: D/MainActivity(23257): touch
06-04 23:50:31.916: D/MainActivity(23257): touch


我点击了button1和button1,和其他的区域。这说明MyLayout拦截了事件,没有传递到Button。上一篇我们就说过不管是View还是ViewGroup事件的分发都是从dispatchTouchEvent开始的,我们不妨看看ViewGroup代码吧。

/**
* {@inheritDoc}
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (!onFilterTouchEventForSecurity(ev)) {
return false;
}

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);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
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);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
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;
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
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);

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);
}
代码比较长,我们重点看29行, if (disallowIntercept || !onInterceptTouchEvent(ev)),这个if是或的关系,只要有一个满足条件即可。其中disallowIntercept,表示是否禁止事件的分发,默认为false,我们可以通过getParent().requestDisallowInterceptTouchEvent(true)修改这个值,这个方法非常有用,我会在下一篇博客中进行介绍。而第二个onInterceptTouchEvent方法就行我们刚刚重写的方法了,默认返回false,那么这个表达式就为true,就会执行下边的for循环了,找到你触摸区域的View,然后调用View的dispatchTouchEvent方法,这时候流程就我们上一篇介绍的View的事件分发过程一样了。当我们重写onInterceptTouchEvent方法,返回true时,就不会执行for循环了,就会执行76行的代码,最终调用super.dispatchTouchEvent(ev)的方法,那就View的分发了,所有最终我们看到只执行touch事件,而没有执行onclick。

今天的总结就要告一段落,我们再回顾一下ViewGroup事件分发的知识点吧。

1. touch事件的传递是从ViewGroup经过层层传递到View的。

2. 对于ViewGroup事件的传递也是从dispatchTouchEvent方法开发分发。

3.如果某一个ViewGroup想要拦截事件分发到子view上去,可以重写ViewGroup中的onInterceptTouchEvent方法。

4. 如果想要让ViewGroup不拦截事件,让touch事件顺利的传递到View,让View来处理这个手势的话,有两个方法,requestDisallowInterceptTouchEvent(true),而另一个就是重写方法onInterceptTouchEvent,即可解决。

下一篇将要总结touch事件在我工作中遇到的问题,以及是如何解决的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: