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

android中的事件处理,以及解决滑动冲突问题都离不开事件分发机制,android中的事件流,即MotionEvent都会经历一个从分发,拦截到处理的一个过程。即dispatchTouchEvent(),onInterceptEvent()到onTouchEvent()的一个过程,在dispatchTouchEvent()负责了事件的分发过程,在dispatchTouchEvent()中会调用onInterceptEvent()与onTouchEvent(),如果onInterceptEvent()返回true,那么会调用到当前view的onTouchEvent()方法,如果不拦截,事件就会下发到子view的dispatchTouchEvent()中进行同样的操作。本文将带领大家从源码角度来分析android是如何进行事件分发的。

android中的事件分发流程最先从activity的dispatchTouchEvent()开始:

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

这里调用了getWindow().superDispatchTouchEvent(ev),这里可以看出activity将MotionEvent传寄给了Window。而Window是一个抽象类,superDispatchTouchEvent()也是一个抽象方法,这里用到的是window的子类phoneWindow。

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

从这里可以看出,event事件被传到了DecorView,也就是我们的顶层view.我们继续跟踪:

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

这里调用到了父类的dispatchTouchEvent()方法,而DecorView是继承自FrameLayout,FrameLayout继承了ViewGroup,所以这里会调用到ViewGroup的dispatchTouchEvent()方法。

所以整个事件流从activity开始,传递到window,最后再到我们的view(viewGroup也是继承自view)中,而view才是我们整个事件处理的核心阶段。

我们来看一下viewGroup的dispatchTouchEvent()中的实现:

if (actionMasked == MotionEvent.ACTION_DOWN) {
      // Throw away all previous state when starting a new touch gesture.
      // The framework may have dropped the up or cancel event for the previous gesture
      // due to an app switch, ANR, or some other state change.
      cancelAndClearTouchTargets(ev);
      resetTouchState();
    }

这是dispatchTouchEvent()开始时截取的一段代码,我们来看一下,首先,当我们手指按下view时,会调用到resetTouchState()方法,在resetTouchState()中:

private void resetTouchState() {
  clearTouchTargets();
  resetCancelNextUpFlag(this);
  mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
  mNestedScrollAxes = SCROLL_AXIS_NONE;
}

我们继续跟踪clearTouchTargets()方法:

private void clearTouchTargets() {
  TouchTarget target = mFirstTouchTarget;
  if (target != null) {
    do {
      TouchTarget next = target.next;
      target.recycle();
      target = next;
    } while (target != null);
    mFirstTouchTarget = null;
  }
}

在clearTouchTargets()方法中,我们最终将mFirstTouchTarget赋值为null,我们继续回到dispatchTouchEvent()中,接着执行了下段代码:

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

当view被按下或mFirstTouchTarget != null 的时候,从前面可以知道,当每次view被按下时,也就是重新开始一次事件流的处理时,mFirstTouchTarget都会被设置成null,一会我们看mFirstTouchTarget是什么时候被赋值的。

从disallowIntercept属性我们大概能猜到是用来判断是否需要坐拦截处理,而我们知道可以通过调用父view的requestDisallowInterceptTouchEvent(true)可以让我们的父view不能对事件进行拦截,我们先来看看requestDisallowInterceptTouchEvent()方法中的实现:

@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

  if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
    // We're already in this state, assume our ancestors are too
    return;
  }

  if (disallowIntercept) {
    mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
  } else {
    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
  }

  // Pass it up to our parent
  if (mParent != null) {
    mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
  }
}

这里也是通过设置标志位做判断处理,所以这里是通过改变mGroupFlags标志,然后在dispatchTouchEvent()刚发中变更disallowIntercept的值判断是否拦截,当为true时,即需要拦截,这个时候便会跳过onInterceptTouchEvent()拦截判断,并标记为不拦截,即intercepted = false,我们继续看viewGroup的onInterceptTouchEvent()处理:

public boolean onInterceptTouchEvent(MotionEvent ev) {
  if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
      && ev.getAction() == MotionEvent.ACTION_DOWN
      && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
      && isOnScrollbarThumb(ev.getX(), ev.getY())) {
    return true;
  }
  return false;
}

即默认情况下,只有在ACTION_DOWN时,viewGroup才会表现为拦截。

我们继续往下看:

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 = buildTouchDispatchChildList();
   final boolean customOrder = preorderedList == null
              && isChildrenDrawingOrderEnabled();
   final View[] children = mChildren;
   for (int i = childrenCount - 1; i >= 0; i--) {
      final int childIndex = getAndVerifyPreorderedIndex(
                childrenCount, i, customOrder);
            final View child = getAndVerifyPreorderedView(
                preorderedList, children, 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();
        }

这段代码首先会通过一个循环去遍历所有的子view,最终会调用到dispatchTransformedTouchEvent()方法,我们继续看dispatchTransformedTouchEvent()的实现:

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

  // Calculate the number of pointers to deliver.
  final int oldPointerIdBits = event.getPointerIdBits();
  final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

  // If for some reason we ended up in an inconsistent state where it looks like we
  // might produce a motion event with no pointers in it, then drop the event.
  if (newPointerIdBits == 0) {
    return false;
  }

  // If the number of pointers is the same and we don't need to perform any fancy
  // irreversible transformations, then we can reuse the motion event for this
  // dispatch as long as we are careful to revert any changes we make.
  // Otherwise we need to make a copy.
  final MotionEvent transformedEvent;
  if (newPointerIdBits == oldPointerIdBits) {
    if (child == null || child.hasIdentityMatrix()) {
      if (child == null) {
        handled = super.dispatchTouchEvent(event);
      } else {
        final float offsetX = mScrollX - child.mLeft;
        final float offsetY = mScrollY - child.mTop;
        event.offsetLocation(offsetX, offsetY);

        handled = child.dispatchTouchEvent(event);

        event.offsetLocation(-offsetX, -offsetY);
      }
      return handled;
    }
    transformedEvent = MotionEvent.obtain(event);
  } else {
    transformedEvent = event.split(newPointerIdBits);
  }

  // Perform any necessary transformations and dispatch.
  if (child == null) {
    handled = super.dispatchTouchEvent(transformedEvent);
  } else {
    final float offsetX = mScrollX - child.mLeft;
    final float offsetY = mScrollY - child.mTop;
    transformedEvent.offsetLocation(offsetX, offsetY);
    if (! child.hasIdentityMatrix()) {
      transformedEvent.transform(child.getInverseMatrix());
    }

    handled = child.dispatchTouchEvent(transformedEvent);
  }

  // Done.
  transformedEvent.recycle();
  return handled;
}

这段代码就比较明显了,如果child不为null,始终会调用到child.dispatchTouchEvent();否则调用super.dispatchTouchEvent();

如果child不为null时,事件就会向下传递,如果子view处理了事件,即dispatchTransformedTouchEvent()即返回true。继续向下执行到addTouchTarget()方法,我们继续看addTouchTarget()方法的执行结果:

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
  final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
  target.next = mFirstTouchTarget;
  mFirstTouchTarget = target;
  return target;
}

这个时候我们发现mFirstTouchTarget又出现了,这时候会给mFirstTouchTarget重新赋值,即mFirstTouchTarget不为null。也就是说,如果事件被当前view或子view消费了,那么在接下来的ACTION_MOVE或ACTION_UP事件中,mFirstTouchTarget就不为null。但如果我们继承了该viewGroup,并在onInterceptTouchEvent()的ACTION_MOVE中拦截了事件,那么后续事件将不会下发,将由该viewGroup直接处理,从下面代码我们可以得到:

// Dispatch to touch targets, excluding the new touch target if we already
      // dispatched to it. Cancel touch targets if necessary.
      TouchTarget predecessor = null;
      TouchTarget target = mFirstTouchTarget;
      while (target != null) {
        final TouchTarget next = target.next;
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
          handled = true;
        } else {
          final boolean cancelChild = resetCancelNextUpFlag(target.child)
              || intercepted;
          if (dispatchTransformedTouchEvent(ev, cancelChild,
              target.child, target.pointerIdBits)) {
            handled = true;
          }
          if (cancelChild) {
            if (predecessor == null) {
              mFirstTouchTarget = next;
            } else {
              predecessor.next = next;
            }
            target.recycle();
            target = next;
            continue;
          }
        }
        predecessor = target;
        target = next;
      }

当存在子view并且事件被子view消费时,即在ACTION_DOWN阶段mFirstTouchTarget会被赋值,即在接下来的ACTION_MOVE事件中,由于intercepted为true,所以将ACTION_CANCEL 事件传递过去,从dispatchTransformedTouchEvent()中可以看到:

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

并将mFirstTouchTarget 最终赋值为 next,而此时mFirstTouchTarget位于TouchTarget链表尾部,所以mFirstTouchTarget会赋值为null,那么接下来的事件将不会进入到onInterceptTouchEvent()中。也就会直接交由该view处理。

如果我们没有进行事件的拦截,而是交由子view去处理,由于ViewGroup的onInterceptTouchEvent()默认并不会拦截除了ACTION_DOWN以外的事件,所以后续事件将继续交由子view去处理,如果存在子view且事件位于子view内部区域的话。

所以无论是否进行拦截,事件流都会交由view的dispatchTouchEvent()中进行处理,我们接下来跟踪一下view中的dispatchTouchEvent()处理过程:

if (actionMasked == MotionEvent.ACTION_DOWN) {
    // Defensive cleanup for new gesture
    stopNestedScroll();
  }

  if (onFilterTouchEventForSecurity(event)) {
    if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
      result = true;
    }
    //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;
    }
  }

当被按下时,即ACTION_DOWN时,view会停止内部的滚动,如果view没有被覆盖或遮挡时,首先会进行mListenerInfo是否为空的判断,我们看下mListenerInfo是在哪里初始化的:

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

这里可以看出,mListenerInfo一般不会是null,知道在我们使用它时调用过这段代码,而当view被加入window中的时候,会调用下面这段代码,从注释中也可以看出来:

/**
 * Add a listener for attach state changes.
 *
 * This listener will be called whenever this view is attached or detached
 * from a window. Remove the listener using
 * {@link #removeOnAttachStateChangeListener(OnAttachStateChangeListener)}.
 *
 * @param listener Listener to attach
 * @see #removeOnAttachStateChangeListener(OnAttachStateChangeListener)
 */
public void addOnAttachStateChangeListener(OnAttachStateChangeListener listener) {
  ListenerInfo li = getListenerInfo();
  if (li.mOnAttachStateChangeListeners == null) {
    li.mOnAttachStateChangeListeners
        = new CopyOnWriteArrayList<OnAttachStateChangeListener>();
  }
  li.mOnAttachStateChangeListeners.add(listener);
}

到这里我们就知道,mListenerInfo一开始就是被初始化好了的,所以li不可能为null,li.mOnTouchListener != null即当设置了TouchListener时不为null,并且view是enabled状态,一般情况view都是enable的。这个时候会调用到onTouch()事件,当onTouch()返回true时,这个时候result会赋值true。而当result为true时,onTouchEvent()将不会被调用。

从这里可以看出,onTouch()会优先onTouchEvent()调用;
当view设置touch监听并返回true时,那么它的onTouchEvent()将被屏蔽。否则会调用onTouchEvent()处理。

那么让我们继续来看看onTouchEvent()中的事件处理:

if ((viewFlags & ENABLED_MASK) == DISABLED) {
    if (action == 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)
        || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
  }

首先,当view状态是DISABLED时,只要view是CLICKABLE或LONG_CLICKABLE或CONTEXT_CLICKABLE,都会返回true,而button默认是CLICKABLE的,textview默认不是CLICKABLE的,而view一般默认都不是LONG_CLICKABLE的。

我们继续向下看:

if (mTouchDelegate != null) {
    if (mTouchDelegate.onTouchEvent(event)) {
      return true;
    }
  }

如果有代理事件,仍然会返回true.

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

          if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
            // 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();
        }
        mIgnoreNextUpEvent = false;
        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();
          }
          mPendingCheckForTap.x = event.getX();
          mPendingCheckForTap.y = event.getY();
          postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
        } else {
          // Not inside a scrolling container, so show the feedback right away
          setPressed(true, x, y);
          checkForLongClick(0, x, y);
        }
        break;

      case MotionEvent.ACTION_CANCEL:
        setPressed(false);
        removeTapCallback();
        removeLongPressCallback();
        mInContextButtonPress = false;
        mHasPerformedLongPress = false;
        mIgnoreNextUpEvent = false;
        break;

      case MotionEvent.ACTION_MOVE:
        drawableHotspotChanged(x, y);

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

当view是CLICKABLE或LONG_CLICKABLE或CONTEXT_CLICKABLE状态时,当手指抬起时,如果设置了click监听,最终会调用到performClick(),触发click()事件。这点从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;
}

从这里我们也可以得出,click事件会在onTouchEvent()中被调用,如果view设置了onTouch()监听并返回true,那么click事件也会被屏蔽掉,不过我们可以在onTouch()中通过调用view的performClick()继续执行click()事件,这个就看我们的业务中的需求了。

从这里我们可以看出,如果事件没有被当前view或子view处理,即返回false,那么事件就会交由外层view继续处理,直到被消费。

如果事件一直没有被处理,会最终传递到Activity的onTouchEvent()中。

到这里我们总结一下:

事件是从Activity->Window->View(ViewGroup)的一个传递流程;

如果事件没有被中途拦截,那么它会一直传到最内层的view控件;

如果事件被某一层拦截,那么事件将不会向下传递,交由该view处理。如果该view消费了事件,那么接下来的事件也会交由该view处理;如果该view没有消费该事件,那么事件会交由外层view处理,...并最终调用到activity的onTouchEvent()中,除非某一层消费了该事件;

一个事件只能交由一个view处理;

DispatchTouchEvent()总是会被调用,而且最先被调用,onInterceptTouchEvent()和onTouchEvent()在DispatchTouchEvent()内部调用;

子view不能干扰ViewGroup对ACTION_DOWN事件的处理;

子view可以通过requestDisallowInterceptTouchEvent(true)控制父view不对事件进行拦截,跳过onInterceptTouchEvent()方法的执行。

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

(0)

相关推荐

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

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

  • Android View事件分发机制详解

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

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

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

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

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

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

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

  • Android View 事件分发机制详解

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

  • Android View的事件分发机制

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

  • Android Touch事件分发深入了解

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

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

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

  • Android事件分发机制的详解

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

随机推荐