android的 View和 ViewGroup的事件分发机制

Android时间分发

View的时间分发过程dispatchTouchEvent —> onTouch –-> onTouchEvent

/**

* Pass the touch screen motion event down to the target view, or this

* view if it is the target.

*

* @param event The motion event to be dispatched.

* @return True if the event was handled by the view, false otherwise.

*/

public boolean dispatchTouchEvent(MotionEvent event) {

if (mInputEventConsistencyVerifier != null) {

mInputEventConsistencyVerifier.onTouchEvent(event, 0);

}

if (onFilterTouchEventForSecurity(event)) {

//noinspection SimplifiableIfStatement

ListenerInfo li = mListenerInfo;

if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED

&& li.mOnTouchListener.onTouch(this, event)) {

return true;

}

if (onTouchEvent(event)) {

return true;

}

}

if (mInputEventConsistencyVerifier != null) {

mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);

}

return false;

}

dispatchTouchEvent的返回值由是否设置触摸回调的返回值或者onTouchEvent的返回值决定。

一个触摸事件发生后,首先如果设置了触摸事件的侦听,并且返回了true,表示该事件已经被消费了,dispatchTouchEvent方法会直接返回true。如果回调返回了false。那么会执行onTouchEvent方法,dispatchTouchEvent的返回值由onTouchEvent的返回值决定。默认情况下,只要可以点击并且是enable的,都会返回true。下面是onTouchEvent方法

/**

* Implement this method to handle touch screen motion events.

*

* @param event The motion event.

* @return True if the event was handled, false otherwise.

*/

public boolean onTouchEvent(MotionEvent event) {

final int viewFlags = mViewFlags;

if ((viewFlags & ENABLED_MASK) == DISABLED) {

if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {

setPressed(false);

}

// A disabled view that is clickable still consumes the touch

// events, it just doesn‘t respond to them.

return (((viewFlags & CLICKABLE) == CLICKABLE ||

(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));

}

if (mTouchDelegate != null) {

if (mTouchDelegate.onTouchEvent(event)) {

return true;

}

}

if (((viewFlags & CLICKABLE) == CLICKABLE ||

(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {

switch (event.getAction()) {

case MotionEvent.ACTION_UP:

boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;

if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {

// take focus if we don‘t have it already and we should in

// touch mode.

boolean focusTaken = false;

if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {

focusTaken = requestFocus();

}

if (prepressed) {

// The button is being released before we actually

// showed it as pressed.  Make it show the pressed

// state now (before scheduling the click) to ensure

// the user sees it.

setPressed(true);

}

if (!mHasPerformedLongPress) {

// This is a tap, so remove the longpress check

removeLongPressCallback();

// Only perform take click actions if we were in the pressed state

if (!focusTaken) {

// Use a Runnable and post this rather than calling

// performClick directly. This lets other visual state

// of the view update before click actions start.

if (mPerformClick == null) {

mPerformClick = new PerformClick();

}

if (!post(mPerformClick)) {

performClick();

}

}

}

if (mUnsetPressedState == null) {

mUnsetPressedState = new UnsetPressedState();

}

if (prepressed) {

postDelayed(mUnsetPressedState,

ViewConfiguration.getPressedStateDuration());

} else if (!post(mUnsetPressedState)) {

// If the post failed, unpress right now

mUnsetPressedState.run();

}

removeTapCallback();

}

break;

case MotionEvent.ACTION_DOWN:

mHasPerformedLongPress = false;

if (performButtonActionOnTouchDown(event)) {

break;

}

// Walk up the hierarchy to determine if we‘re inside a scrolling container.

boolean isInScrollingContainer = isInScrollingContainer();

// For views inside a scrolling container, delay the pressed feedback for

// a short period in case this is a scroll.

if (isInScrollingContainer) {

mPrivateFlags |= PFLAG_PREPRESSED;

if (mPendingCheckForTap == null) {

mPendingCheckForTap = new CheckForTap();

}

postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());

} else {

// Not inside a scrolling container, so show the feedback right away

setPressed(true);

checkForLongClick(0);

}

break;

case MotionEvent.ACTION_CANCEL:

setPressed(false);

removeTapCallback();

removeLongPressCallback();

break;

case MotionEvent.ACTION_MOVE:

final int x = (int) event.getX();

final int y = (int) event.getY();

// Be lenient about moving outside of buttons

if (!pointInView(x, y, mTouchSlop)) {

// Outside button

removeTapCallback();

if ((mPrivateFlags & PFLAG_PRESSED) != 0) {

// Remove any future long press/tap checks

removeLongPressCallback();

setPressed(false);

}

}

break;

}

return true;

}

return false;

}

长按事件是在ACTION_DOWN 里面执行的,点击事件是在ACTION_UP 里面执行的。长按事件的执行逻辑如下,

class CheckForLongPress implements Runnable {

private int mOriginalWindowAttachCount;

public void run() {

if (isPressed() && (mParent != null)

&& mOriginalWindowAttachCount == mWindowAttachCount) {

if (performLongClick()) {

mHasPerformedLongPress = true;

}

}

}

public boolean performLongClick() {

sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);

boolean handled = false;

ListenerInfo li = mListenerInfo;

if (li != null && li.mOnLongClickListener != null) {

handled = li.mOnLongClickListener.onLongClick(View.this);

}

if (!handled) {

handled = showContextMenu();

}

if (handled) {

performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);

}

return handled;

}

如果执行了长按事件,并且长按的回调返回了true,那么mHasPerformedLongPress = true ,就不会再去执行performClick();

因此想要长按事件和单击事件同时发生,就要在长按的回调函数返回false。

单击事件和长按事件其实都是封装在触摸事件里面的。

ViewGroup的事件分发过程dispatchTouchEvent ---> onInterceptTouchEvent

public boolean onInterceptTouchEvent(MotionEvent ev) {

return false;

}

默认情况下onInterceptTouchEvent返回false,即不拦截。

/**

* {@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;

// 是否禁用拦截,如果为true表示不能拦截事件;反之,则为可以拦截事件

boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

// ACTION_DOWN事件,即按下事件

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))         // 1、是否禁用拦截、是否拦截事件的判断

// 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--)        // 2、迭代所有子view,查找触摸事件在哪个子view的坐标范围内

final View child = children[i];

// 该child是可见的

if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE

|| child.getAnimation() != null) {

// 3、获取child的坐标范围

child.getHitRect(frame);

// 4、判断发生该事件坐标是否在该child坐标范围内

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;

// 5、child处理该事件,如果返回true,那么mMotionTarget为该child。正常情况下,

// dispatchTouchEvent(ev)的返回值即onTouchEcent的返回值。因此onTouchEcent如果返回为true,

// 那么mMotionTarget为触摸事件所在位置的child。

if (child.dispatchTouchEvent(ev))

//默认的实现下View.dispatchTouchEvent(ev)返回值一定为true

// Event handled, we have a target now.

mMotionTarget = child;

return true;

//表示子view已经能将触摸时间消费掉

}

}

}

}

}

}// end if

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;

// 6、如果mMotionTarget为空,那么执行super.dispatchTouchEvent(ev),

// 即View.dispatchTouchEvent(ev),就是该View Group自己处理该touch事件,只是又走了一遍View的分发过程而已. (指没有找到view,也可能是下面两种情况)

// 1,拦截事件 或者2.在不拦截事件target view的onTouchEvent返回false的情况都会执行到这一步.  这种情况下

//执行super.dispatchTouchEvent(ev);也就是当成view来分发事件,过程同 view的时间分发过程一致

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

// 调用super.dispatchTouchEvent(ev); 表示子view没能将触摸时间消费掉,就会将触摸事件传递给父view

}

// if have a target, see if we‘re allowed to and want to intercept its

// events

// 7、如果没有禁用事件拦截,并且onInterceptTouchEvent(ev)返回为true,即进行事件拦截.

//-----似乎只有target!=null也就是子view处理down还返回true,然后拦截事件发生了才会执行下面的if

//也就是对move 和 up 事件的拦截会执行到这里。

//由于在down时child.dispatchTouchEvent(ev)返回了true,所以target有了值。下面的代码是让子view执行ACTION_CANCEL事件

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)) {

//拦截move事件,会让子view分发cancel事件

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

}

// 事件不拦截,且target view在ACTION_DOWN时返回true,那么后续事件由target来处理事件

// 执行到这里的条件是子view 在ACTION_DOWN时返回true,这样target不为null,并且

//还不会执行target == null 的判断才会执行到这里

return target.dispatchTouchEvent(ev);

}

拦截的使用方法

@Override

public boolean onInterceptTouchEvent(MotionEvent ev)

{

int action = ev.getAction();

switch (action)

{

case MotionEvent.ACTION_DOWN:

return true ;

case MotionEvent.ACTION_MOVE:

return true ;

case MotionEvent.ACTION_UP:

return true ;

}

return false;

}

如果你在DOWN retrun true ,则DOWN,MOVE,UP子View都不会捕获事件;onInterceptTouchEvent(ev) return true的时候,mMotionTarget 为null ;

如果你在MOVE return true , 则子View在MOVE和UP都不会捕获事件。onInterceptTouchEvent(ev) return true的时候,此时target还不为null,会执行target.dispatchTouchEvent(ev)来分发cancel事件,接着会把mMotionTarget 置为null ;

拦截down事件,会执行到target==null,然后调用super.dispatchTouchEvent(ev);即父view来处理。

拦截move事件,子view会处理cancel事件,父view也不会处理,

requestDisallowInterceptTouchEvent(boolean) 用于设置是否允许拦截

如果ViewGroup的onInterceptTouchEvent(ev) 当ACTION_MOVE时return true ,即拦截了子View的MOVE以及UP事件;那还有补救的措施。requestDisallowInterceptTouchEvent(true)便可以使子view接收到,因为会跳过if (!disallowIntercept && onInterceptTouchEvent(ev)),执行return target.dispatchTouchEvent(ev);通过源码很容易解释。

但是如果是在ACTION_DOWN时返回true来拦截的,那么子view无论怎么做都不可能捕获任何事件,因为此时target == null,肯定会执行return super.dispatchTouchEvent(ev);

时间: 2024-10-23 10:42:14

android的 View和 ViewGroup的事件分发机制的相关文章

ViewGroup的事件分发机制

我们用手指去触摸Android手机屏幕,就会产生一个触摸事件,但是这个触摸事件在底层是怎么分发的呢?这个我还真不知道,这里涉及到操作硬件 (手机屏幕)方面的知识,也就是Linux内核方面的知识,我也没有了解过这方面的东西,所以我们可能就往上层来分析分析,我们知道Android中负责 与用户交互,与用户操作紧密相关的四大组件之一是Activity, 所以我们有理由相信Activity中存在分发事件的方法,这个方法就是dispatchTouchEvent(),我们先看其源码吧 [java] view

Android:30分钟弄明白Touch事件分发机制

转载: 原文地址  点击打开链接 http://www.cnblogs.com/linjzong/p/4191891.html PS: 对照原文, 添加了一些文字, 表达更清晰(对本人来说) ---------------------------------------------质朴的分割线--------------------------------------------- Touch事件分发中只有两个主角:ViewGroup和View.Activity的Touch事件事实上是调用它内部

Android事件分发机制详解:史上最全面、最易懂

前言 Android事件分发机制是每个Android开发者必须了解的基础知识 网上有大量关于Android事件分发机制的文章,但存在一些问题:内容不全.思路不清晰.无源码分析.简单问题复杂化等等 今天,我将全面总结Android的事件分发机制,我能保证这是市面上的最全面.最清晰.最易懂的 本文秉着"结论先行.详细分析在后"的原则,即先让大家感性认识,再通过理性分析从而理解问题: 所以,请各位读者先记住结论,再往下继续看分析: 文章较长,阅读需要较长时间,建议收藏等充足时间再进行阅读 目

Android面试收集录6 事件分发机制

1.基础认知 1.1.事件分发的对象是谁? 答:事件. 当用户触摸屏幕时(View或ViewGroup派生的控件),将产生点击事件(Touch事件). Touch事件相关细节(发生触摸的位置.时间.历史记录.手势动作等)被封装成MotionEvent对象 主要发生的Touch事件有如下四种: MotionEvent.ACTION_DOWN:按下View(所有事件的开始) MotionEvent.ACTION_MOVE:滑动View MotionEvent.ACTION_CANCEL:非人为原因结

Android View的事件分发机制

准备了一阵子,一直想写一篇事件分发的文章总结一下.这个知识点实在是太重要了. 一个应用的布局是丰富的,有TextView,ImageView,Button等.这些子View的外层还有ViewGroup.如RelativeLayout.LinearLayout.作为一个开发人员,我们会思考.当点击一个button,Android系统是如何确定我点的就是button而不是TextView的?然后还正确的响应了button的点击事件. 内部经过了一系列什么过程呢? 先铺垫一些知识能更加清晰的理解事件分

Android 源码解析View的touch事件分发机制

概述 本篇主要分析的是touch事件的分发机制,网上关于这个知识点的分析文章非常多.但是还是想通过结合自身的总结,来加深自己的理解.对于事件分发机制,我将使用两篇文章对其进行分析,一篇是针对View的事件分发机制解析,一篇是针对ViewGroup的事件分发机制解析.本片是对View的事件分发机制进行解析,主要采用案例结合源码的方式来进行分析. 前言 在分析事件分发机制之前,我们先来学习一下基本的知识点,以便后面的理解. View中有两个关键方法参与到Touch事件分发 dispatchTouch

Android View 事件分发机制源码详解(View篇)

前言 在Android View 事件分发机制源码详解(ViewGroup篇)一文中,主要对ViewGroup#dispatchTouchEvent的源码做了相应的解析,其中说到在ViewGroup把事件传递给子View的时候,会调用子View的dispatchTouchEvent,这时分两种情况,如果子View也是一个ViewGroup那么再执行同样的流程继续把事件分发下去,即调用ViewGroup#dispatchTouchEvent:如果子View只是单纯的一个View,那么调用的是Vie

Android事件分发机制总结

理解事件的分发机制,需要对View和ViewGroup事件的分发分别探讨.View和ViewGroup的区别,一个View控件是指它里面不能再包含子控件了,常见的如TextView.Button.ImageView等,而ViewGroup是继承自View的,但是它里面可以包含一些子控件,包括View或者嵌套的ViewGroup,常用的大部分布局都是ViewGroup组件,如LinearLayout.RelativeLayout.FrameLayout等. 首先要明白的是,当我们触摸一个控件时(不

Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客开始,就零零散散在好多地方使用到了Android事件分发的知识.也有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图片使用Button而不用Ima