Android View事件分发机制详解

准备了一阵子,一直想写一篇事件分发的文章总结一下,这个知识点实在是太重要了。

一个应用的布局是丰富的,有TextView,ImageView,Button等,这些子View的外层还有ViewGroup,如RelativeLayout,LinearLayout。作为一个开发者,我们会思考,当点击一个按钮,Android系统是怎样确定我点的就是按钮而不是TextView的?然后还正确的响应了按钮的点击事件。内部经过了一系列什么过程呢?

先铺垫一些知识能更加清晰的理解事件分发机制:
1. 通过setContentView设置的View就是DecorView的子view,即DecorView是父容器。
2. 点击屏幕时,在手指按下和抬起间,会产生很多事件,down…move…move…up,中间会有很多的move事件,这一系列的事件为一个事件序列
3. dispatchTouchEvent方法用于分发事件
4. onInterceptTouchEvent方法用于拦截事件
5. onTouchEvent方法用于处理事件

当一个点击事件(MotionEvent)产生后,事件最先传递给当前的界面(Activity),这点是很好理解的。 Activity再将事件传递给窗口(Window),然后Window将事件传递给顶级View(DecorView)。此时,事件已经到达了View了。之后顶级View就会按照事件分发机制去分发事件。具体是这样的:

对于一个根ViewGroup来说,点击事件产生后,首先会传递给它,这时它的 dispatchTouchEvent 方法就会被调用,如果这个ViewGroup的onInterceptTouchEvent方法返回true就表示它要拦截当前事件,接着事件就会交给这个ViewGroup处理,即它的onTouchEvent方法就会被调用。如果这个ViewGroup的onInterceptTouchEvent方法返回false,就表示它不拦截当前事件,这时当前事件就会继续传递给它的子元素,接着子元素的dispatchTouchEvent方法就会被调用,如此反复直到事件被最终处理。
如果一个View的onTouchEvent方法返回false,那么它的父容器的onTouchEvent方法会被调用,如果它的父容器的onTouchEvent方法还是返回false,那就继续往上抛,当所有的元素都不处理这个事件,那么这个事件会最终传递给Activity处理,即Activity的onTouchEvent方法会被调用。

好了,现在已经铺垫了基础,那么接下来就从源码的角度来分析事件分发机制。

当然是从Activity的dispatchTouchEvent方法开始分析。源码如下:

public boolean dispatchTouchEvent(MotionEvent ev) {
  if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    onUserInteraction();
  }
  if (getWindow().superDispatchTouchEvent(ev)) {
    return true;
  }
  return onTouchEvent(ev);
}

如果当前事件是down的话,就调用onUserInteraction方法,onUserInteraction是一个空方法,我们可以暂时不搭理。然后调用getWindow方法获取到当前Activity关联的Window,Window再调用superDispatchTouchEvent方法将事件传入进行分发。
如果superDispatchTouchEvent方法返回true的话, view已经处理了事件。整个事件循环结束。如果返回false,没有view处理这个事件。事件往上抛,那就Activity自己处理了,即Activity的onTouchEvent方法会被调用。

因为想要知道事件的整个分发过程,现在关注的是Window的superDispatchTouchEvent方法,那么就跟进去看看:

public abstract boolean superDispatchTouchEvent(MotionEvent event);

Window是一个抽象类,superDispatchTouchEvent是一个抽象的方法,那么我们必须要找到window的实现类才行,可是茫茫人海怎么找呢?看到window类的说明就明白了

* <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window

意思是Window存在唯一的实现是android.view.PhoneWindow

那么PhoneWindow里的superDispatchTouchEvent方法就是我们要找的信息,如下:

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
  return mDecor.superDispatchTouchEvent(event);
}

直接将事件传递给了DecorView。这时事件已经是到达View了哦。

那么跟进DecorView的superDispatchTouchEvent方法看看,如下:

public boolean superDispatchTouchEvent(MotionEvent event) {
  return super.dispatchTouchEvent(event);
}

内部调用了父类的dispatchTouchEvent方法,那么DecorView的父类是什么呢?DecorView肯定是View的,那么刚才开篇提到,我们通过setContentView设置的View,是DecorView的子View。那么更加准确的说DecorView是一个ViewGroup。

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker

可以看到DecorView是继承自FrameLayout,FrameLayout是ViewGroup,也就是说DecorView是一个ViewGroup。

那么现在只需要关注ViewGroup的dispatchTouchEvent方法。继续前进

ViewGroup的事件分发

ViewGroup的dispatchTouchEvent方法如下:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

    //代码省略

    // Check for interception.
    final boolean intercepted;
    if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
      final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
      if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // restore action in case it was changed
      } else {
        intercepted = false;
      }
    } else {
      // There are no touch targets and this action is not an initial down
      // so this view group continues to intercept touches.
      intercepted = true;
    }

    //代码省略

    if (!canceled && !intercepted) {

    //代码省略

        final int childrenCount = mChildrenCount;
        if (newTouchTarget == null && childrenCount != 0) {
          final float x = ev.getX(actionIndex);
          final float y = ev.getY(actionIndex);
          // Find a child that can receive the event.
          // Scan children from front to back.
          final ArrayList<View> preorderedList = buildOrderedChildList();
          final boolean customOrder = preorderedList == null
              && isChildrenDrawingOrderEnabled();
          final View[] children = mChildren;
          for (int i = childrenCount - 1; i >= 0; i--) {
            final int childIndex = customOrder
                ? getChildDrawingOrder(childrenCount, i) : i;
            final View child = (preorderedList == null)
                ? children[childIndex] : preorderedList.get(childIndex);

            // If there is a view that has accessibility focus we want it
            // to get the event first and if not handled we will perform a
            // normal dispatch. We may do a double iteration but this is
            // safer given the timeframe.
            if (childWithAccessibilityFocus != null) {
              if (childWithAccessibilityFocus != child) {
                continue;
              }
              childWithAccessibilityFocus = null;
              i = childrenCount - 1;
            }

            if (!canViewReceivePointerEvents(child)
                || !isTransformedTouchPointInView(x, y, child, null)) {
              ev.setTargetAccessibilityFocus(false);
              continue;
            }

            newTouchTarget = getTouchTarget(child);
            if (newTouchTarget != null) {
              // Child is already receiving touch within its bounds.
              // Give it the new pointer in addition to the ones it is handling.
              newTouchTarget.pointerIdBits |= idBitsToAssign;
              break;
            }

            resetCancelNextUpFlag(child);
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
              // Child wants to receive touch within its bounds.
              mLastTouchDownTime = ev.getDownTime();
              if (preorderedList != null) {
                // childIndex points into presorted list, find original index
                for (int j = 0; j < childrenCount; j++) {
                  if (children[childIndex] == mChildren[j]) {
                    mLastTouchDownIndex = j;
                    break;
                  }
                }
              } else {
                mLastTouchDownIndex = childIndex;
              }
              mLastTouchDownX = ev.getX();
              mLastTouchDownY = ev.getY();
              newTouchTarget = addTouchTarget(child, idBitsToAssign);
              alreadyDispatchedToNewTouchTarget = true;
              break;
            }

            // The accessibility focus didn't handle the event, so clear
            // the flag and do a normal dispatch to all children.
            ev.setTargetAccessibilityFocus(false);
          }
          if (preorderedList != null) preorderedList.clear();
        }

        if (newTouchTarget == null && mFirstTouchTarget != null) {
          // Did not find a child to receive the event.
          // Assign the pointer to the least recently added target.
          newTouchTarget = mFirstTouchTarget;
          while (newTouchTarget.next != null) {
            newTouchTarget = newTouchTarget.next;
          }
          newTouchTarget.pointerIdBits |= idBitsToAssign;
        }
      }
    }

    // Dispatch to touch targets.
    if (mFirstTouchTarget == null) {
      // No touch targets so treat this as an ordinary view.
      handled = dispatchTransformedTouchEvent(ev, canceled, null,
          TouchTarget.ALL_POINTER_IDS);
    }

    //代码省略

    return handled;
}

代码比较长,一点一点分析,先看到一开始的判断

if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)

mFirstTouchTarget != null的意义是ViewGroup不拦截事件并将事件交由子元素处理,先这样记着,这从后面的addTouchTarget方法可以得出结论的。

然后又会来到这个if判断。

if (!disallowIntercept) {
  intercepted = onInterceptTouchEvent(ev);
  ev.setAction(action); // restore action in case it was changed
}

那我们看看disallowIntercept。而disallowIntercept的赋值过程中,有一个 FLAG_DISALLOW_INTERCEPT 标记位

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

这个 FLAG_DISALLOW_INTERCEPT 标记位是可以通过requestDisallowInterceptTouchEvent方法来设置的。

回到if (!disallowIntercept)的判断,进入这个if判断后,就会来到

intercepted = onInterceptTouchEvent(ev);

调用onInterceptTouchEvent方法,询问ViewGroup是否拦截事件。

读到这里,可以回忆下开篇时铺垫的结论,对于ViewGroup,点击事件产生后,首先会传递给它,这时它的 dispatchTouchEvent 方法就会被调用,接着会调用它的onInterceptTouchEvent方法,如果这个ViewGroup的onInterceptTouchEvent方法返回true就表示它要拦截当前事件,接着事件就会交给这个ViewGroup处理,即它的onTouchEvent方法就会被调用。如果返回false表示不拦截,通常ViewGroup也是不拦截事件的。

那现在先分析不拦截的情况,不拦截那就好办了的。经过一系列的判断,就会来到一个for循环遍历。

for (int i = childrenCount - 1; i >= 0; i--)

这时ViewGroup开始分发传递事件,遍历子元素了。

首先肯定需要过滤掉一些无关点击事件的子元素的,判断子元素是否能够接收点击事件,点击事件的坐标是否落在子元素区域内。

if (!canViewReceivePointerEvents(child)
    || !isTransformedTouchPointInView(x, y, child, null)) {
  ev.setTargetAccessibilityFocus(false);
  continue;
}

如果不能够接收点击事件或者点击事件的坐标没有落在子元素区域,就会跳出当前循环,继续遍历下一个子元素。这下就知道了Android系统为什么能够知道点击的是Button而不是TextView,其实内部就只是做了一个判断嘛。

那么继续分析,子元素符合以上两个条件后,就将事件传递给这个子元素。会来到了这个判断。

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))

执行dispatchTransformedTouchEvent方法,将子元素传进去。这个方法很重要,那么跟进看看

/**
 * Transforms a motion event into the coordinate space of a particular child view,
 * filters out irrelevant pointer ids, and overrides its action if necessary.
 * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
 */
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
    View child, int desiredPointerIdBits) {
  final boolean handled;

  // Canceling motions is a special case. We don't need to perform any transformations
  // or filtering. The important part is the action, not the contents.
  final int oldAction = event.getAction();
  if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
    event.setAction(MotionEvent.ACTION_CANCEL);
    if (child == null) {
      handled = super.dispatchTouchEvent(event);
    } else {
      handled = child.dispatchTouchEvent(event);
    }
    event.setAction(oldAction);
    return handled;
  }

  //代码省略
}

我们看到child!=null的情况,如果子元素不为空,调用子元素的dispatchTouchEvent方法继续分发事件,同时返回处理结果布尔值,这时就将事件传递到了子View处理。完成了一轮的事件分发。这个方法先到这里就好。

再看回ViewGroup的dispatchTouchEvent方法,如果dispatchTransformedTouchEvent方法返回true的话,这时事件已经传递给子元素处理,ViewGroup已经不管这个事件了。
那么就会进入if语句,最后会来到addTouchTarget方法,这个方法之前是提到过的,用于mFirstTouchTarget标记位的赋值。

那跟进这个方法看看

/**
 * Adds a touch target for specified child to the beginning of the list.
 * Assumes the target child is not already present.
 */
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
  TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
  target.next = mFirstTouchTarget;
  mFirstTouchTarget = target;
  return target;
}

其实就是让mFirstTouchTarget指向子元素。

执行完这个addTouchTarget方法后,最终会到break语句,那么就会跳出整个for循环体。ViewGroup结束分发过程!

又回到dispatchTransformedTouchEvent方法,如果dispatchTransformedTouchEvent方法返回false,那么if语句的一大段代码都不执行了,而是回到for循环继续遍历子元素进行分发。如此重复完成事件的传递过程。

现在分析ViewGroup拦截事件的情况,如果ViewGroup拦截事件的话,那么就会进入以下这个判断

if (mFirstTouchTarget == null) {
  // No touch targets so treat this as an ordinary view.
  handled = dispatchTransformedTouchEvent(ev, canceled, null,
  TouchTarget.ALL_POINTER_IDS);
}

注意到dispatchTransformedTouchEvent方法的第三个参数child传入的是null,那么就是在dispatchTransformedTouchEvent方法中走以下的语句

if (child == null) {
  handled = super.dispatchTouchEvent(event);
}

而ViewGroup是继承自View的,那么就是ViewGroup自己处理事件了。这点我们以下分析了View的事件分发过程就能搞明白了。

以上就是ViewGroup的事件分发

那么现在分析已经将事件传递给了子View的情况,View继续调用dispatchTouchEvent方法,那我们看看View的dispatchTouchEvent方法。

View的事件分发

View的dispatchTouchEvent方法源码如下:

/**
 * 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 (onFilterTouchEventForSecurity(event)) {
    //noinspection SimplifiableIfStatement
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnTouchListener != null
        && (mViewFlags & ENABLED_MASK) == ENABLED
        && li.mOnTouchListener.onTouch(this, event)) {
      result = true;
    }

    if (!result && onTouchEvent(event)) {
      result = true;
    }
  }

  if (!result && mInputEventConsistencyVerifier != null) {
    mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
  }

  // Clean up after nested scrolls if this is the end of a gesture;
  // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
  // of the gesture.
  if (actionMasked == MotionEvent.ACTION_UP ||
      actionMasked == MotionEvent.ACTION_CANCEL ||
      (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
    stopNestedScroll();
  }

  return result;
}

相比于ViewGroup的dispatchTouchEvent方法,View的dispatchTouchEvent方法代码量少了。也相对简单些了。
首先会来到如下判断:

if (li != null && li.mOnTouchListener != null
    && (mViewFlags & ENABLED_MASK) == ENABLED
    && li.mOnTouchListener.onTouch(this, event))

li变量在哪里被赋值的呢?通常是在setOnClickListener方法或setOnTouchListener方法的时候。

public void setOnClickListener(@Nullable OnClickListener l) {
  if (!isClickable()) {
    setClickable(true);
  }
  getListenerInfo().mOnClickListener = l;
}

public void setOnTouchListener(OnTouchListener l) {
  getListenerInfo().mOnTouchListener = l;
}

而这个getListenerInfo()如下:

ListenerInfo getListenerInfo() {
  if (mListenerInfo != null) {
    return mListenerInfo;
  }
  mListenerInfo = new ListenerInfo();
  return mListenerInfo;
}

ListenerInfo是一个内部类,里面存放的是各种监听事件的引用。

之后会判断如下条件:

li.mOnTouchListener != null

同理只要setOnTouchListener方法设置了,这个引用就不空。这些都是好理解的。那关键到了,

li.mOnTouchListener.onTouch(this, event)

到了最后一个条件。这个onTouch方法是我们去实现的,它也返回一个布尔值,如果返回true的话,那么就会进入这个if判断最终返回true,跳出整个方法,那么我们可以看到接下来的onTouchEvent方法是不会得到执行的。
也就是onTouch的执行在onTouchEvent之前。那么如果我们也调用了setOnClickListener方法监听点击事件的话,onClick方法是在哪里调用的呢?我们有理由相信是在onTouchEvent方法里调用的。那么就跟进看看。

public boolean onTouchEvent(MotionEvent event) {

  //代码省略

  if (((viewFlags & CLICKABLE) == CLICKABLE ||
      (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
      (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
    switch (action) {
      case MotionEvent.ACTION_UP:
        boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;

  //代码省略

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

       //代码省略 

        break;
    }

    return true;
  }

  return false;
}

只要CLICKABLE或LONG_CLICKABLE不空, 就会处理这个事件,然而怎么保证CLICKABLE或LONG_CLICKABLE不空呢?其实细心的你会发现,刚才上面贴出的setOnClickListener源代码中,会将CLICKABL属性设置会true

public void setOnClickListener(@Nullable OnClickListener l) {
  if (!isClickable()) {
    setClickable(true);
  }
  getListenerInfo().mOnClickListener = l;
}

这样就能进入if判断去处理这个事件了,之后就会来到performClick()方法,应该就是它了,跟进去看看吧。

public boolean performClick() {
  final boolean result;
  final ListenerInfo li = mListenerInfo;
  if (li != null && li.mOnClickListener != null) {
    playSoundEffect(SoundEffectConstants.CLICK);
    li.mOnClickListener.onClick(this);
    result = true;
  } else {
    result = false;
  }

  sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
  return result;
}

熟悉接口回调机制的你,一定也读懂了performClick()方法的源码,

li.mOnClickListener.onClick(this);

是在执行这行代码时,调用了我们熟悉的onClick方法

以上就是View的事件分发机制。

此时已经将事件分发机制分析完了,由于我的技术的原因,驾驭的不好,有些关键点还是没分析清楚,但我相信学完了这篇文章能让我和你都对事件分发机制的实现有一个大致的认识,有这个已经可以了,之后还可以一点点去强化锻炼,深入理解事件分发机制。才能为自定义控件铺垫良好的基础。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android View的事件分发机制

    一.Android View框架提供了3个对事件的主要操作概念. 1.事件的分发机制,dispatchTouchEvent.主要是parent根据触摸事件的产生位置,以及child是否愿意负责处理该系列事件等状态,向其child分发事件的机制. 2.事件的拦截机制,onInterceptTouchEvent.主要是parent根据它内部的状态.或者child的状态,来把事件拦截下来,阻止其进一步传递到child的机制. 3.事件的处理机制,onTouchEvent.主要是事件序列的接受者(可以是

  • android事件分发机制的实现原理

    android中的事件处理,以及解决滑动冲突问题都离不开事件分发机制,android中的事件流,即MotionEvent都会经历一个从分发,拦截到处理的一个过程.即dispatchTouchEvent(),onInterceptEvent()到onTouchEvent()的一个过程,在dispatchTouchEvent()负责了事件的分发过程,在dispatchTouchEvent()中会调用onInterceptEvent()与onTouchEvent(),如果onInterceptEven

  • Android事件分发机制(下) View的事件处理

    综述 在上篇文章Android中的事件分发机制(上)--ViewGroup的事件分发中,对ViewGroup的事件分发进行了详细的分析.在文章的最后ViewGroup的dispatchTouchEvent方法调用dispatchTransformedTouchEvent方法成功将事件传递给ViewGroup的子View.并交由子View进行处理.那么现在就来分析一下子View接收到事件以后是如何处理的. View的事件处理 对于这里描述的View,它是ViewGroup的父类,并不包含任何的子元

  • Android 事件分发详解及示例代码

    事件分发是Android中非常重要的机制,是用户与界面交互的基础.这篇文章将通过示例打印出的Log,绘制出事件分发的流程图,让大家更容易的去理解Android的事件分发机制. 一.必要的基础知识 1.相关方法 Android中与事件分发相关的方法主要包括dispatchTouchEvent.onInterceptTouchEvent.onTouchEvent三个方法,而事件分发一般会经过三种容器,分别为Activity.ViewGroup.View.下表对这三种容器分别拥有的事件分发相关方法进行

  • Android View 事件分发机制详解

    Android开发,触控无处不在.对于一些 不咋看源码的同学来说,多少对这块都会有一些疑惑.View事件的分发机制,不仅在做业务需求中会碰到这些问题,在一些面试笔试题中也常有人问,可谓是老生常谈了.我以前也看过很多人写的这方面的文章,不是说的太啰嗦就是太模糊,还有一些在细节上写的也有争议,故再次重新整理一下这块内容,十分钟让你搞明白View事件的分发机制. 说白了这些触控的事件分发机制就是弄清楚三个方法,dispatchTouchEvent(),OnInterceptTouchEvent(),o

  • Android View事件分发和消费源码简单理解

    Android View事件分发和消费源码简单理解 前言: 开发过程中觉得View事件这块是特别烧脑的,看了好久,才自认为看明白.中间上网查了下singwhatiwanna粉丝的读书笔记,有种茅塞顿开的感觉. 很重要的学习方法:化繁为简,只抓重点. 源码一坨,不要指望每一行代码都看懂.首先是没必要,其次大量非关键代码会让你模糊真正重要的部分. 以下也只是学姐的学习成果,各位同学要想理解深刻,还需要自己亲自去看源码. 2.源码分析 由于源码实在太长,而且也不容易看懂,学姐这里就不贴出来了,因为没必

  • Android Touch事件分发深入了解

    本文带着大家深入学习触摸事件的分发,具体内容如下 1. 触摸动作及事件序列 (1)触摸事件的动作 触摸动作一共有三种:ACTION_DOWN.ACTION_MOVE.ACTION_UP.当用户手指接触屏幕时,便产生一个动作为ACTION_DOWN的触摸事件,此时若用户的手指立即离开屏幕,会产生一个动作为ACTION_UP的触摸事件:若用户手指接触屏幕后继续滑动,当滑动距离超过了系统中预定义的距离常数,则产生一个动作为ACTION_MOVE的触摸事件,系统中预定义的用来判断用户手指在屏幕上的滑动是

  • Android事件分发机制(上) ViewGroup的事件分发

    综述 Android中的事件分发机制也就是View与ViewGroup的对事件的分发与处理.在ViewGroup的内部包含了许多View,而ViewGroup继承自View,所以ViewGroup本身也是一个View.对于事件可以通过ViewGroup下发到它的子View并交由子View进行处理,而ViewGroup本身也能够对事件做出处理.下面就来详细分析一下ViewGroup对时间的分发处理. MotionEvent 当手指接触到屏幕以后,所产生的一系列的事件中,都是由以下三种事件类型组成.

  • Android事件分发机制的详解

    Android事件分发机制 我们只考虑最重要的四个触摸事件,即:DOWN,MOVE,UP和CANCEL.一个手势(gesture)是一个事件列,以一个DOWN事件开始(当用户触摸屏幕时产生),后跟0个或多个MOVE事件(当用户四处移动手指时产生),最后跟一个单独的UP或CANCEL事件(当用户手指离开屏幕或者系统告诉你手势(gesture)由于其他原因结束时产生).当我们说到"手势剩余部分"时指的是手势后续的MOVE事件和最后的UP或CANCEL事件. 在这里我也不考虑多点触摸手势(我

  • 谈谈对Android View事件分发机制的理解

    最近因为项目中用到类似一个LinearLayout中水平布局中,有一个TextView和Button,然后对该LinearLayout布局设置点击事件,点击TextView能够触发该点击事件,然而奇怪的是点击Button却不能触发.然后google到了解决办法(重写Button,然后重写其中的ontouchEvent方法,且返回值为false),但是不知道原因,这两天看了几位大神的博客,然后自己总结下. public class MyButton extends Button { private

随机推荐