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

Android中Activity触摸事件传递源码学习

2017-10-30 20:07 453 查看
Activity中的触摸事件传递对应两个方法:dispatchTouchEvent和onTouchEvent。分别是分发和消费。

先来看分发。

/**
* 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);
}


第一个就有点看懵了。不过还是一步步看下去吧。先看注释!我英语不是很好,谷歌翻译了一下,自己也理解了一些,注释的翻译大概是:这个方法的是用来处理屏幕的触摸事件的。你可以重写这个方法在分发到window之前去拦截屏幕的触摸事件。最好要用正常的方式来处理这些触摸事件。我的理解就是在触摸我们的应用时,首先是触摸事件先到达Activity中,重写这个方法可以控制触摸事件的是否分发到Activity界面中的ViewGroup、View中。

这里说下Android中Window的概念。先来google翻译下Window这个抽象类注释中的说明:抽象基类的顶级窗口外观和行为策略。 应该使用此类的实例作为添加到窗口管理器的顶级视图。 它提供标准的UI策略,如背景,标题区域,默认密钥处理等。

我的理解是Window是一种容器,用来装Activity、Dialog等视图。我们设置一个应用的主题就相当于给Window设置了一些基础的背景、标题区域等因此能看到一个应用中的Activity等视图有一致的基础样式。

接着看代码,一开始就是一个判断,当触摸事件是ACTION_DOWN时,调用了onUserInteraction()方法。找到方法的位置,发现是一个空方法,上面有一大段注释。代码如下:

/**
* Called whenever a key, touch, or trackball event is dispatched to the
* activity.  Implement this method if you wish to know that the user has
* interacted with the device in some way while your activity is running.
* This callback and {@link #onUserLeaveHint} are intended to help
* activities manage status bar notifications intelligently; specifically,
* for helping activities determine the proper time to cancel a notfication.
*
* <p>All calls to your activity's {@link #onUserLeaveHint} callback will
* be accompanied by calls to {@link #onUserInteraction}.  This
* ensures that your activity will be told of relevant user activity such
* as pulling down the notification pane and touching an item there.
*
* <p>Note that this callback will be invoked for the touch down action
* that begins a touch gesture, but may not be invoked for the touch-moved
* and touch-up actions that follow.
*
* @see #onUserLeaveHint()
*/
public void onUserInteraction() {
}


这里贴上google翻译的结果:

每当将关键,触摸或轨迹球事件发送到活动时,都会调用该对象。 如果您希望知道用户在您的活动正在运行时以某种方式与设备进行了互动,则可以实现此方法。 此回调和{@link #onUserLeaveHint}旨在帮助智能地管理状态栏通知的活动; 具体来说,帮助活动确定正确的时间取消通知。所有调用活动的{@link #onUserLeaveHint}回调将伴随着对{@link #onUserInteraction}的调用。 这将确保您的活动将被告知相关的用户活动,例如拉下通知窗格并在其中触摸一个项目。请注意,这个回调将被调用,以触发动作开始触摸手势,但可能不会被调用 触摸移动和接下来的动作。

我觉得大概意思就是这个方法在我们有触摸事件到达我们的应用时都会调用,这个方法可以配合onUserLeaveHint()方法管理状态栏通知的取消。以前我都没有注意到管理状态栏通知这方面知识,找个时间可以看看,这里先不深入。

再下来是一个判断。点开判断条件的源码可以看到如下代码:

/**
* 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);


谷歌翻译如下:

由自定义窗口(如Dialog)使用,可以在视图层次结构下进一步传递触摸屏事件。 应用程序开发人员不需要实现或调用这个。

我的理解是当有dailog之类的置于Activity之上时,此时dialog会消费掉触摸事件,正如我们平常看到的一样,触摸dialog不会触发Activity、View、ViewGroup的触摸事件,因此这里直接return true,不在Activity中对触摸事件进行处理。

最后一句则是默认Activity是向下分发触摸事件。

Activity中没有拦截事件,我们来看看消费事件。消费事件对应的是onTouchEvent方法。

/**
* Called when a touch screen event was not handled by any of the views
* under it.  This is most useful to process touch events that happen
* outside of your window bounds, where there is no view to receive it.
*
* @param event The touch screen event being processed.
*
* @return Return true if you have consumed the event, false if you haven't.
* The default implementation always returns false.
*/
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}

return false;
}


注释的意思大概是一些触摸事件当你的Activity中没有View消耗掉时,该方法用来接受并处理掉这些触摸事件。

接着我们看代码。如果Activity消费掉触摸事件返回true,如果没有则返回false。

到Window类中看判断语句中的方法的源码。

/** @hide */
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
&& isOutOfBounds(context, event) && peekDecorView() != null) {
return true;
}
return false;
}


注释中的@hide的意思可以看我另外一篇博客

http://blog.csdn.net/flyyyyyyyy_/article/details/78340949

第一个判断条件没看出来是什么意思,第二个意思是触摸事件为DOWN时,即触摸事件刚开始的时候就要判断然后进行处理,第三个和第四个都与Android中的DecorView有关系,这里来说说DecorView的概念。

private boolean isOutOfBounds(Context context, MotionEvent event) {
final int x = (int) event.getX();
final int y = (int) event.getY();
final int slop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
final View decorView = getDecorView();
return (x < -slop) || (y < -slop)
|| (x > (decorView.getWidth()+slop))
|| (y > (decorView.getHeight()+slop));
}
/**
* Retrieve the current decor view, but only if it has already been created;
* otherwise returns null.
*
* @return Returns the top-level window decor or null.
* @see #getDecorView
*/
public abstract View peekDecorView();

/**
* 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();


这里有关于DecorView的英文介绍,

google翻译是:检索顶级窗口装饰视图(包含标准窗口框架/装饰和客户端的内容),可以作为窗口添加到窗口管理器。

我的理解是最顶级的Window中的View,也就是说触摸事件先从DecorView开始传递,一层一层往下传。

第三个判断条件就是是否超出应用显示的最顶层,第四个就是判断最顶层视图是否被创建。这里判断的意思是如果触摸超出Activity并且顶层视图存在则关闭Activity。(还是不太懂这里的意思。)

总结:触摸事件在Activity中的传播是这样的,首先dispatchTouchEvent分发事件判断是否有如Dialog的Window覆盖在上面并且判断是否消耗掉触摸事件了,如果消耗掉则返回true事件不分发,如果没有则交由消费者onTouchEvent处理。消费者onTouchEvent如果根据相关规则处理了触摸事件则返回true,触摸事件不分发,返回false则向下分发。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息