前一段时间,做一个app的时候,想在Touch屏幕的时候判断一下位置坐标,进行一些其他的修改,于是重写了FragmentLayout,在其中的dispatchTouchEvent函数中进行判断修改,但是发现效果并不理想,它只能截获属于它自己的区域内的一些Touch事件,而我想要的是任意一块区域的布局能知道其他区域的Touch事件,于是好好看了下Android源码,理顺了下Touch事件的分发顺序。
首先Activity有这样几个函数:
public boolean dispatchTrackballEvent(MotionEvent ev);
public boolean onTouchEvent(MotionEvent event);
....
当Touch事件到达的时候,首先会传给Activity的dispatchTrackballEvent函数,为什么?我们看看Activity的源码:
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
这个函数的注释大概是说:你可以在这个touch事件分配给各窗口之前截获这个事件,也就是说事件由它流向各个窗口。
而且分析源码也知道,它首先是获取该Activity的Window---getWindow()函数,该函数返回的是个Window的类。
而继续追究,Window是一个抽象类,源码位置: frameworks\base\core\java\android\view\Window.java
里面有这些抽象方法:
/**
* Used by custom windows, such as Dialog, to pass the key press event
* further down the view hierarchy. Application developers should
* not need to implement or call this.
*
*/
public abstract boolean superDispatchKeyEvent(KeyEvent event);
/**
* Used by custom windows, such as Dialog, to pass the key shortcut press event
* further down the view hierarchy. Application developers should
* not need to implement or call this.
*
*/
public abstract boolean superDispatchKeyShortcutEvent(KeyEvent event);
/**
* Used by custom windows, such as Dialog, to pass the touch screen event
* further down the view hierarchy. Application developers should
* not need to implement or call this.
*
*/
public abstract boolean superDispatchTouchEvent(MotionEvent event);
/**
* Used by custom windows, such as Dialog, to pass the trackball event
* further down the view hierarchy. Application developers should
* not need to implement or call this.
*
*/
public abstract boolean superDispatchTrackballEvent(MotionEvent event);
/**
* Used by custom windows, such as Dialog, to pass the generic motion event
* further down the view hierarchy. Application developers should
* not need to implement or call this.
*
*/
public abstract boolean superDispatchGenericMotionEvent(MotionEvent event);
/**
* Retrieve the top-level window decor view (containing the standard
* window frame/decorations and the client's content inside of that), which
* can be added as a window to the window manager.
*
* <p><em>Note that calling this function for the first time "locks in"
* various window characteristics as described in
* {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}.</em></p>
*
* @return Returns the top-level window decor view.
*/
public abstract View getDecorView();
这些方法正是Activity调用的,找到了这些还得去查找Window的实现类---PhoneWindow
源码位置:frameworks\base\policy\src\com\android\internal\policy\impl\PhoneWindow.java
PhoneWindow是所有Activity的窗口体,在它里面有个子类叫DecorView就是所有布局的顶层布局:
public class PhoneWindow extends Window implements MenuBuilder.Callback
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker
PhoneWindow实现了Window抽象类的那些方法,代码如下:
@Override
public boolean superDispatchKeyEvent(KeyEvent event) {
return mDecor.superDispatchKeyEvent(event);
}
@Override
public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
return mDecor.superDispatchKeyShortcutEvent(event);
}
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
@Override
public boolean superDispatchTrackballEvent(MotionEvent event) {
return mDecor.superDispatchTrackballEvent(event);
}
@Override
public boolean superDispatchGenericMotionEvent(MotionEvent event) {
return mDecor.superDispatchGenericMotionEvent(event);
}
PhoneWindow有个内部成员DecorView
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
它也就是封装在PhoneWindow类里面的那个内部类,顶层布局。
稍微总结下,当Activity调用dispatchTouchEvent(MotionEvent ev)派发事件时,是获取这个Activity的PhoneWindow,然后通过它的superDispatchTouchEvent(MotionEvent event)来下发,通过源码可以看到,PhoneWindow其实是调用了顶层布局DecorView的superDispatchTouchEvent(event);
也就是说Activity通过Window将事件派发给顶层布局DecorView,下面给出DecorView的部分源码:
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
final int keyCode = event.getKeyCode();
final int action = event.getAction();
final boolean isDown = action == KeyEvent.ACTION_DOWN;
if (isDown && (event.getRepeatCount() == 0)) {
// First handle chording of panel key: if a panel key is held
// but not released, try to execute a shortcut in it.
if ((mPanelChordingKey > 0) && (mPanelChordingKey != keyCode)) {
boolean handled = dispatchKeyShortcutEvent(event);
if (handled) {
return true;
}
}
// If a panel is open, perform a shortcut on it without the
// chorded panel key
if ((mPreparedPanel != null) && mPreparedPanel.isOpen) {
if (performPanelShortcut(mPreparedPanel, keyCode, event, 0)) {
return true;
}
}
}
if (!isDestroyed()) {
final Callback cb = getCallback();
final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
: super.dispatchKeyEvent(event);
if (handled) {
return true;
}
}
return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)
: PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);
}
@Override
public boolean dispatchKeyShortcutEvent(KeyEvent ev) {
// If the panel is already prepared, then perform the shortcut using it.
boolean handled;
if (mPreparedPanel != null) {
handled = performPanelShortcut(mPreparedPanel, ev.getKeyCode(), ev,
Menu.FLAG_PERFORM_NO_CLOSE);
if (handled) {
if (mPreparedPanel != null) {
mPreparedPanel.isHandled = true;
}
return true;
}
}
// Shortcut not handled by the panel. Dispatch to the view hierarchy.
final Callback cb = getCallback();
handled = cb != null && !isDestroyed() && mFeatureId < 0
? cb.dispatchKeyShortcutEvent(ev) : super.dispatchKeyShortcutEvent(ev);
if (handled) {
return true;
}
// If the panel is not prepared, then we may be trying to handle a shortcut key
// combination such as Control+C. Temporarily prepare the panel then mark it
// unprepared again when finished to ensure that the panel will again be prepared
// the next time it is shown for real.
if (mPreparedPanel == null) {
PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true);
preparePanel(st, ev);
handled = performPanelShortcut(st, ev.getKeyCode(), ev,
Menu.FLAG_PERFORM_NO_CLOSE);
st.isPrepared = false;
if (handled) {
return true;
}
}
return false;
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Callback cb = getCallback();
return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
: super.dispatchTouchEvent(ev);
}
@Override
public boolean dispatchTrackballEvent(MotionEvent ev) {
final Callback cb = getCallback();
return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTrackballEvent(ev)
: super.dispatchTrackballEvent(ev);
}
@Override
public boolean dispatchGenericMotionEvent(MotionEvent ev) {
final Callback cb = getCallback();
return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchGenericMotionEvent(ev)
: super.dispatchGenericMotionEvent(ev);
}
public boolean superDispatchKeyEvent(KeyEvent event) {
if (super.dispatchKeyEvent(event)) {
return true;
}
// Not handled by the view hierarchy, does the action bar want it
// to cancel out of something special?
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
final int action = event.getAction();
// Back cancels action modes first.
if (mActionMode != null) {
if (action == KeyEvent.ACTION_UP) {
mActionMode.finish();
}
return true;
}
// Next collapse any expanded action views.
if (mActionBar != null && mActionBar.hasExpandedActionView()) {
if (action == KeyEvent.ACTION_UP) {
mActionBar.collapseActionView();
}
return true;
}
}
return false;
}
public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
return super.dispatchKeyShortcutEvent(event);
}
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
public boolean superDispatchTrackballEvent(MotionEvent event) {
return super.dispatchTrackballEvent(event);
}
public boolean superDispatchGenericMotionEvent(MotionEvent event) {
return super.dispatchGenericMotionEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return onInterceptTouchEvent(event);
}
private boolean isOutOfBounds(int x, int y) {
return x < -5 || y < -5 || x > (getWidth() + 5)
|| y > (getHeight() + 5);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
if (mFeatureId >= 0) {
if (action == MotionEvent.ACTION_DOWN) {
int x = (int)event.getX();
int y = (int)event.getY();
if (isOutOfBounds(x, y)) {
closePanel(mFeatureId);
return true;
}
}
}
if (!SWEEP_OPEN_MENU) {
return false;
}
if (mFeatureId >= 0) {
if (action == MotionEvent.ACTION_DOWN) {
Log.i(TAG, "Watchiing!");
mWatchingForMenu = true;
mDownY = (int) event.getY();
return false;
}
if (!mWatchingForMenu) {
return false;
}
int y = (int)event.getY();
if (action == MotionEvent.ACTION_MOVE) {
if (y > (mDownY+30)) {
Log.i(TAG, "Closing!");
closePanel(mFeatureId);
mWatchingForMenu = false;
return true;
}
} else if (action == MotionEvent.ACTION_UP) {
mWatchingForMenu = false;
}
return false;
}
//Log.i(TAG, "Intercept: action=" + action + " y=" + event.getY()
// + " (in " + getHeight() + ")");
if (action == MotionEvent.ACTION_DOWN) {
int y = (int)event.getY();
if (y >= (getHeight()-5) && !hasChildren()) {
Log.i(TAG, "Watchiing!");
mWatchingForMenu = true;
}
return false;
}
if (!mWatchingForMenu) {
return false;
}
int y = (int)event.getY();
if (action == MotionEvent.ACTION_MOVE) {
if (y < (getHeight()-30)) {
Log.i(TAG, "Opening!");
openPanel(FEATURE_OPTIONS_PANEL, new KeyEvent(
KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU));
mWatchingForMenu = false;
return true;
}
} else if (action == MotionEvent.ACTION_UP) {
mWatchingForMenu = false;
}
return false;
}
通过这种方式将事件派发给各个布局。
其他的关于:
public boolean dispatchTouchEvent(MotionEvent event); // 事件派发
public boolean onTouchEvent(MotionEvent event); // 事件处理
public boolean onInterceptTouchEvent(MotionEvent ev); // 事件拦截
我也简单的总结下以上几个函数,首先Touch事件需要注意的几点:
- 不在触摸范围内的View或ViewGroup不派发事件;
- 某次事件中,down事件谁处理了,move、up等事件就交给谁处理;
- View和Activity是没有onInterceptTouchEvent函数的。
事件分发:public boolean dispatchTouchEvent(MotionEvent ev)
Touch 事件发生时 Activity 的 dispatchTouchEvent(MotionEvent ev) 方法会以隧道方式(从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递)将事件传递给最外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法,并由该 View 的 dispatchTouchEvent(MotionEvent ev) 方法对事件进行分发。
dispatchTouchEvent 的事件分发逻辑如下:
如果 返回 true,事件会分发给当前 View 并由 dispatchTouchEvent 方法进行消费,同时事件会停止向下传递;
如果 返回 false,事件分发分为两种情况:
如果当前 View 获取的事件直接来自 Activity,则会将事件返回给 Activity 的 onTouchEvent 进行消费;
如果当前 View 获取的事件来自外层父控件,则会将事件返回给父 View 的 onTouchEvent 进行消费。
如果返回系统默认的 super.dispatchTouchEvent(ev),分两种情况:
1,当前视图是ViewGroup,事件会自动的分发给当前 ViewGroup 的 onInterceptTouchEvent 方法。
2,当前视图是View,件会自动的分发给当前 View 的 onTouchEvent 方法。
事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)
在外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法返回系统默认的 super.dispatchTouchEvent(ev) 情况下,事件会自动的分发给当前 ViewGroup 的 onInterceptTouchEvent 方法。
onInterceptTouchEvent 的事件拦截逻辑如下:
如果 onInterceptTouchEvent 返回 true,则表示将事件进行拦截,并将拦截到的事件交由当前ViewGroup的onTouchEvent进行处理;
如果 onInterceptTouchEvent 返回 false,同下
如果 onInterceptTouchEvent 返回 super.onInterceptTouchEvent(ev),分两种情况:
1,如果当前ViewGroup没有子View,事件默认会被拦截,并将拦截到的事件交由当前ViewGroup的onTouchEvent进行处理。
2,如果当前ViewGroup有子View,事件会放行,传递到子View 上,再由子View的dispatchTouchEvent来开始这个事件的分发;
事件响应:public boolean onTouchEvent(MotionEvent ev)
在 dispatchTouchEvent 返回 super.dispatchTouchEvent(ev) 并且 onInterceptTouchEvent 返回 true或返回 super.onInterceptTouchEvent(ev) 的情况下 onTouchEvent 会被调用。
onTouchEvent 的事件响应逻辑如下:
如果返回 false,那么这个事件会从当前 View 向上传递,并且都是由上层 View 的 onTouchEvent 来接收,如果传递到上面的 onTouchEvent 也返回 false,这个事件就会交由Activity的onTouchEvent 处理,而且在下次down事件之前当前View都接收不到事件。
如果返回 true ,则当前View会接收并消费该事件。
如果返回 super.onTouchEvent(ev) 默认处理事件的逻辑和返回 false 时相同。
所以总体来讲事件分发顺序:Touch事件-->Activity-->Window-->DecorView->FrameLayout-->ViewGroup
当没有一个View来派发事件的时候,那么Activity.dispatchTouchEvent(MotionEvent ev)源码中的getWindow().superDispatchTouchEvent(ev)会返回false,事件此时重新会回到Activity,调用Activity的onTouchEvent(ev)来处理事件。
补充:其实KeyEvent也是这种分发机制,由Activity先行发放到Window,再到DecorView,然后再到其他View