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

android 事件分发

2015-10-11 20:51 465 查看
1.背景

在写android程序时,由于设计需要,经常要自定义控件,那么假设:有一个控件,内部可以填充子控件,满足控件的滑动且子控件可点击;是不是很常见?这个假设的实现:nice的遇见频道。你想象的,它应该是本来就可以滑动,然后子控件又可以点击,但事实不是你理解的那样。我们面临两个问题 :1.这个事件到底是如何分发和相应的2.自定义控件时,如何处理,可以完成,控件滑动,子控件可点击。

2.准备知识

Android 是怎么捕获屏幕事件的?追到源码里面window这个类,负责屏幕上事件的处理,window是怎么处理的,framework的这部分代码不开放。源码(Android 2.3)中是我们关键看这几个方法,然后再总结:

a.Activity

1.dispatchTouchEvent(MotionEvent ev);

2.onTouchEvent(MotionEvent event);

public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();//这个是处理活动状态,关,帮助活动管理状态栏通知;
}
if (getWindow().superDispatchTouchEvent(ev){    //window实质上来完成事件的分发
return true;
}

return onTouchEvent(ev);
}
public boolean onTouchEvent(MotionEvent event) {
// 默认是不消费事件的
return false;
}


b.ViewGroup

1.dispatchTouchEvent(MotionEvent ev);//viewGroup分发事件,有些长

2.onTouchEvent(MotionEvent event);//真正处理事件的

3.onInterceptTouchEvent(MotionEvent ev);//截断事件传递

/**
* {@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
// intercep 两个参数控制
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;
}
//交给子控件的分发处理
//注意整个过程没有看到调用ViewGroup自己的ontouchEvent方法

return target.dispatchTouchEvent(ev);
}

//默认是不拦截事件分发的
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}


c.View

1.dispatchTouchEvent(MotionEvent event);

2.onTouchEvent(MotionEvent event);

public boolean dispatchTouchEvent(MotionEvent event) {
if (!onFilterTouchEventForSecurity(event)) {
return false;
}
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}


3查看源码结论:



4自定义控件中,如何处理,滑动和子控件的点击?下篇
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: