View 体系详解:坐标系、滑动、手势和事件分发机制

一、位置

1.1 坐标系

下面是 Android 中的 View 坐标系的基本图。要得到一个 View 的位置,咱们能够借助两个对象,一个是 View ,一个是 MotionEvent。如下是它们的一些方法的位置的含义:git

 

Android View 坐标系

文末有免费福利哦github

在 View 中共有 mLeft, mRight, mTopmBottom 四个变量包含 View 的坐标信息,你能够在源码中获取它们的含义:面试

  1. mLeft:指定控件的左边缘距离其父控件左边缘的位置,单位:像素;
  2. mRight:指定控件的右边缘距离其父控件左边缘的位置,单位:像素;
  3. mTop:指定控件的上边缘距离其父控件上边缘的位置,单位:像素;
  4. mBottom:指定控件的下边缘距离其父控件上边缘的位置,单位:像素。

此外,View 中还有几个方法用来获取控件的位置等信息,实际上就是上面四个变量的 getter 方法:算法

  1. getLeft():即 mLeft
  2. getRight():即 mRight
  3. getTop():即 mTop
  4. getBottom():即 mBottom

因此,咱们能够获得两个获取 View 高度和宽度信息的方法:小程序

  1. getHeight():即 mBottom - mTop
  2. getWidth():即 mRight - mLeft

另外,就是 View 中的 getX()getY() 两个方法,你须要注意将其与 MotionEvent 中的同名方法进行区分。在没有对控件进行平移的时候,getX()getLeft() 返回结果相同,只是前者会在后者的基础上加上平移的距离:性能优化

  1. getX():即 mLeft + getTranslationX(),即控件的左边缘加上 X 方向平移的距离;
  2. getY():即 mTop + getTranslationY(),即控件的上边缘加上 Y 方向平移的距离;

以上是咱们对 View 中获取控件位置的方法的梳理,你能够到源码中查看它们更加相详尽的定义,那更有助于本身的理解。架构

1.2 MotionEvent

一般当你对控件进行触摸监听的时候会用到 MotionEvent ,它封住了触摸的位置等信息。下面咱们对 MotionEvent 中的获取点击事件的位置的方法进行梳理,它主要涉及下面四个方法:ide

  1. MotionEvent.getX():获取点击事件距离控件左边缘的距离,单位:像素;
  2. MotionEvent.getY():获取点击事件距离控件上边缘的距离,单位:像素;
  3. MotionEvent.getRawX():获取点击事件距离屏幕左边缘的距离,单位:像素;
  4. MotionEvent.getRawY():获取点击事件距离屏幕上边缘的距离,单位:像素。

另外是触摸事件中的三种典型的行为,按下、移动和抬起。接下来的代码示例中咱们会用到它们来判断手指的行为,并对其作响应的处理:源码分析

  1. MotionEvent.ACTION_DOWN:按下的行为;
  2. MotionEvent.ACTION_MOVE:手指在屏幕上移动的行为;
  3. MotionEvent.ACTION_UP:手指抬起的行为。

二、滑动

咱们有几种方式实现 View 的滑动:布局

2.1 layout() 方法

调用控件的 layout() 方法进行滑动,下面是该方法的定义:

public void layout(int l, int t, int r, int b) { /*...*/ }
复制代码

其中的四个参数 l, t, r, b分别表示控件相对于父控件的左、上、右、下的距离,分别对应于上面的 mLeft, mTop, mRightmBottom。因此,调用该方法同时能够改变控件的高度和宽度,但有时候咱们不须要改变控件的高度和宽度,只要移动其位置便可。因此,咱们又有方法 offsetLeftAndRight()offsetTopAndBottom() 可使用,后者只会对控件的位置进行平移。所以,咱们能够进行以下的代码测试:

private int lastX, lastY;

private void layoutMove(MotionEvent event) {
    int x = (int) event.getX(), y = (int) event.getY();
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            lastX = x;
            lastY = y;
            break;
        case MotionEvent.ACTION_MOVE:
            int offsetX = x - lastX, offsetY = y - lastY;
            getBinding().v.layout(getBinding().v.getLeft() + offsetX,
                    getBinding().v.getTop() + offsetY,
                    getBinding().v.getRight() + offsetX,
                    getBinding().v.getBottom() + offsetY);
            break;
        case MotionEvent.ACTION_UP:
            break;
    }
}
复制代码

上面的代码的效果是指定的控件会随着手指的移动而移动。这里咱们先记录下按下的位置,而后手指移动的时候记录下平移的位置,最后调用 layout() 便可。

文末有免费福利哦

2.2 offsetLeftAndRight() 和 offsetTopAndBottom()

上面已经提到过这两个方法,它们只改变控件的位置,没法改变大小。咱们只须要对上述代码作少许修改就能够实现一样的效果:

getBinding().v.offsetLeftAndRight(offsetX);
getBinding().v.offsetTopAndBottom(offsetY);
复制代码

2.3 改变布局参数

经过获取并修改控件的 LayoutParams,咱们同样能够达到修改控件的位置的目的。毕竟,自己这个对象就表明着控件的布局:

FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getBinding().v.getLayoutParams();
lp.leftMargin = getBinding().v.getLeft() + offsetX;
lp.topMargin = getBinding().v.getTop() + offsetY;
getBinding().v.setLayoutParams(lp);
复制代码

2.4 动画

使用动画咱们也能够实现控件移动的效果,这里所谓的动画主要是操做 View 的 transitionXtransitionY 属性:

getBinding().v.animate().translationX(5f);
getBinding().v.animate().translationY(5f);
复制代码

关于动画的内容,咱们会在后面详细介绍。

2.5 scrollTo() 和 scrollBy()

scrollBy() 方法内部调用了 scrollTo(),如下是这部分的源码。scrollBy() 表示在当前的位置上面进行平移,而 scrollTo() 表示平移到指定的位置:

public void scrollBy(int x, int y) {
    scrollTo(mScrollX + x, mScrollY + y);
}
复制代码

一样对上述代码进行修改,咱们也能够实现以前的效果:

((View) getBinding().v.getParent()).scrollBy(-offsetX, -offsetY);
复制代码

或者

View parent = ((View) getBinding().v.getParent());
parent.scrollTo(parent.getScrollX()-offsetX, parent.getScrollY()-offsetY);
复制代码

此外,还有一个须要注意的地方是:与上面的 offsetLeftAndRight()offsetTopAndBottom() 不一样的是,这里咱们用了平移的值的相反数。缘由很简单,由于咱们要使用这两个方法的时候须要对指定的控件所在的父容器进行调用(正如上面是先获取父控件)。当咱们但愿控件相对于以前的位置向右下方向移动,就应该让父容器相对于以前的位置向左上方向移动。由于实际上该控件相对于父控件的位置没有发生变化,变化的是父控件的位置。(参考的坐标系不一样)

2.6 Scroller

上面,咱们的测试代码是让指定的控件随着手指移动,可是假如咱们但愿控件从一个位置移动到另外一个位置呢?固然,它们也能够实现,可是这几乎就是在瞬间完成了整个操做,实际的UI效果确定不会好。因此,为了让滑动的过程看起来更加流畅,咱们能够借助 Scroller 来实现。

在使用 Scroller 以前,咱们须要先实例化一个 Scroller

private Scroller scroller = new Scroller(getContext());
复制代码

而后,咱们须要覆写自定义控件的 computeScroll() 方法,这个方法会在绘制 View 的时候被调用。因此,这里的含义就是,当 View 重绘的时候会调用 computeScroll() 方法,而 computeScroll() 方法会判断是否须要继续滚动,若是须要继续滚动的时候就调用 invalidate() 方法,该方法会致使 View 进一步重绘。因此,也就是靠着这种不断进行重绘的方式实现了滚动的效果。

滑动效果最终结束的判断是经过 ScrollercomputeScrollOffset() 方法实现的,当滚动中止的时候,该方法就会返回 false,这样不会继续调用 invalidate() 方法,于是也就不会继续绘制了。下面是该方法典型的覆写方式:

@Override
public void computeScroll() {
    super.computeScroll();
    if (scroller.computeScrollOffset()) {
        ((View) getParent()).scrollTo(scroller.getCurrX(), scroller.getCurrY());
        invalidate();
    }
}
复制代码

而后,咱们再加入一个滚动到指定位置的方法,在该方法内部咱们使用了 2000ms 来指定完成整个滑动所须要的时间:

public void smoothScrollTo(int descX, int descY) {
    scroller.startScroll(getScrollX(), getScrollY(), descX - getScrollX(), descY - getScrollY(), 2000);
    invalidate();
}
复制代码

这样定义了以后,咱们只须要在须要滚动的时候调用自定义 View 的 smoothScrollTo() 方法便可。

三、手势

3.1 ViewConfiguration

在类 ViewConfiguration 中定义了一些列的常量用来标志指定的行为,好比,TouchSlop 就是滑动的最小的距离。你能够经过 ViewConfiguration.get(context) 来获取 ViewConfiguration 实例,而后经过它的 getter 方法来获取这些常量的定义。

3.2 VelocityTracker

VelocityTracker 用来检测手指滑动的速率,它的使用很是简单。在使用以前,咱们先使用它的静态方法 obtain() 获取一个实例,而后在 onTouch() 方法中调用它的 addMovement(MotionEvent) 方法:

velocityTracker = VelocityTracker.obtain();
复制代码

随后,当咱们想要得到速率的时候,先调用 computeCurrentVelocity(int) 传入一个时间片断,单位是毫秒,而后调用 getXVelocity()getYVelocity() 分别得到在水平和竖直方向上的速率便可:

velocityTracker.computeCurrentVelocity((int) duration);
getBinding().tvVelocity.setText("X:" + velocityTracker.getXVelocity() + "\n"
        + "Y:" + velocityTracker.getYVelocity());
复制代码

本质上,计算速率的时候是用指定时间的长度变化除以咱们传入的时间片。当咱们使用完了 VelocityTracker 以后,须要回收资源:

velocityTracker.clear();
velocityTracker.recycle();
复制代码

3.3 GestureDectector

GestureDectector 用来检测手指的手势。在使用它以前咱们须要先获取一个 GestureDetector 的实例:

mGestureDetector = new GestureDetector(getContext(), new MyOnGestureListener());
复制代码

这里咱们用了 GestureDetector 的构造方法,须要传入一个 OnGestureListener 对象。这里咱们用了 MyOnGestureListener 实例。 MyOnGestureListener 是一个自定义的类,实现了 OnGestureListener 接口:

private class MyOnGestureListener extends GestureDetector.SimpleOnGestureListener {

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        ToastUtils.makeToast("Click detected");
        return false;
    }

    @Override
    public void onLongPress(MotionEvent e) {
        LogUtils.d("Long press detected");
    }

    @Override
    public boolean onDoubleTap(MotionEvent e) {
        LogUtils.d("Double tab detected");
        return true;
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        LogUtils.d("Fling detected");
        return true;
    }
}
复制代码

MyOnGestureListener 中,咱们覆写了它的一些方法。好比,单击、双击和长按等等,当检测到相应的手势的时候这些方法就会被调用。

而后,咱们能够这样使用 GestureDetector,只要在控件的触摸事件回调中调用便可:

getBinding().vg.setOnTouchListener((v, event) -> {
    mGestureDetector.onTouchEvent(event);
    return true;
});
复制代码

四、事件分发机制

4.1 事件传递的过程

当讨论事件分发机制的时候,咱们首先要了解 Android 中 View 的组成结构。在 Android 中,一个 Activity 包含一个 PhoneWindow,当咱们在 Activity 中调用 setContentView() 方法的时候,会调用该 PhoneWindowsetContentView() 方法,并在这个方法中生成一个 DecorView 做为 Activity 的跟 View

根据上面的分析,当一个点击事件被触发的时候,首先接收到该事件的是 Activity。由于,Activity 覆盖了整个屏幕,咱们须要先让它接收事件,而后它把事件传递给根 View 以后,再由根 View 向下继续传递。这样不断缩小搜索的范围,直到最顶层的 View。固然,任何的父容器均可以决定这个事件是否是要继续向下传递,所以,咱们能够大体获得下面这个事件传递的图:

 

事件传递图

 

左边的图是一个 Activity 内部的 ViewWindow 的组织结构。右面的图能够看做它的切面图,其中的黑色箭头表示事件的传递过程。这里事件传递的过程是先从下到上,而后再从上到下。也就是从大到小,不判定位到触摸的控件,其中每一个父容器能够决定是否将事件传递下去。(须要注意的地方是,若是一个父容器有多个子元素的话,那么在这些子元素中进行遍历的时候,顺序是从上往下的,也就是按照展现的顺序)。

上面咱们分析了 Android 事件传递的过程,相信你有了一个大体的了解。可是,想要了解整个事件传递过程具体涉及了哪些方法、如何做用等,还须要咱们对源码进行分析。

文末有免费福利哦

4.2 事件传递的原理

当触摸事件发生的时候,首先会被 Activity 接收到,而后该 Activity 会经过其内部的 dispatchTouchEvent(MotionEvent) 将事件传递给内部的 PhoneWindow;接着 PhoneWindow 会把事件交给 DecorView,再由 DecorView 交给根 ViewGroup。剩下的事件传递就只在 ViewGroupView 之间进行。咱们能够经过覆写 Activity 的 dispatchTouchEvent(MotionEvent) 来阻止把事件传递给 PhoneWindow。实际上,在咱们开发的时候不会对 Window 的事件传递方法进行重写,通常是对 ViewGroup 或者 View。因此,下面咱们的分析只在这两种控件之间进行。

当讨论 View 的事件分发机制的时候,无外乎下面三个方法:

  1. boolean onInterceptTouchEvent(MotionEvent ev):用来对事件进行拦截,该方法只存在于 ViewGroup 中。通常咱们会经过覆写该方法来拦截触摸事件,使其再也不继续传递给子 View。
  2. boolean dispatchTouchEvent(MotionEvent event):用来分发触摸事件,通常咱们不覆写该方法,返回 true 则表示事件被处理了。在 View 中,它负责根据手势的类型和控件的状态对事件进行处理,会回调咱们的 OnTouchListener 或者 OnClickListener;在 ViewGroup 中,该方法被覆写,它的责任是对事件进行分发,会对全部的子 View 进行遍历,决定是否将事件分发给指定的 View。
  3. boolean onTouchEvent(MotionEvent event):用于处理触摸事件,返回 true 表示触摸事件被处理了。ViewGroup 没有覆写该方法,故在 ViewGroup 中与 View 中的功能是同样的。须要注意的是,若是咱们为控件设置了 OnTouchListener 而且在或者中返回了 true,那么这个方法不会被调用,也就是 OnTouchListener 比该方法的优先级较高。对咱们开发来讲,就是 OnTouchListenerOnClickListenerOnLongClickListener 的优先级要高。

因而,咱们能够获得以下的伪代码。这段代码是存在于 ViewGroup 中的,也就是事件分发机制的核心代码:

boolean dispatchTouchEvent(MotionEvent e) {
    boolean result;
    if (onInterceptTouchEvent(e)) {
        result = super.dispatchTouchEvent(e);
    } else {
        result = child.dispatchTouchEvent(e);
    }
    return result;
}
复制代码

按照上述分析,触摸事件通过 Activity 传递给根 ViewGroup 以后:

若是 ViewGourp 覆写了 onInterceptTouchEvent() 而且返回了 true 就表示但愿拦截该方法,因而就把触摸事件交给当前 ViewGroup 进行处理(触发 OnTouchListener 或者 OnClickListener 等);不然,会交给子元素的继续分发。若是该子元素是 ViewGroup 的话,就会在该子 View 中执行一遍上述逻辑,不然会在当前的子元素中对事件进行处理(触发 OnTouchListener 或者 OnClickListener 等)……就这样一层层地遍历下去,本质上是一个深度优先的搜索算法。

这里咱们对整个事件分发机制的总体作了一个素描,在接下来的文章中咱们会对各个方法的细节进行源码分析,为了防止您在接下来的行文中迷路,咱们先把这个总体逻辑按下图进行描述:

 

事件分发机制原理

 

4.3 事件传递的源码分析

上述咱们分析了事件分发机制的原理,下面咱们经过源代码来更具体地了解这块是如何设计的。一样,咱们的焦点也只在那三个须要重点关注的方法。

4.3.1 决定是否拦截事件

首先,咱们来看 ViewGroup 中的 dispatchTouchEvent(MotionEvent) 方法,咱们节选了其一部分:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    // ...
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;
        if (actionMasked == MotionEvent.ACTION_DOWN) { // 1
            // 这里表示若是是一个新的触摸事件就要重置全部的状态,其中包括将 mFirstTouchTarget 置为 null
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }
        // 在这里检查是否拦截了事件,mFirstTouchTarget 是以前处理触摸事件的 View 的封装
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
            // 这里判断该 ViewGroup 是否禁用了拦截,由 requestDisallowInterceptTouchEvent 设置
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action);
            } else {
                intercepted = false;
            }
        } else {
            // 非按下事件而且 mFirstTouchTarget 为 null,说明判断过拦截的逻辑而且启用了拦截
            intercepted = true;
        }
        // ...           
    }
    // ...
    return handled;
}
复制代码

上面代码是咱们节选的 ViewGroup 拦截事件的部分代码,这里的逻辑显然比伪代码复杂的多。不过,尽管如此,这些代码确实必不可少的。由于,当咱们要去判断是否拦截一个触摸事件的时候,此时触摸的事件仍然在继续,这意味着这个方法会被持续调用;抬起的时候再按下,又是另外一次调用。考虑到这个连续性,咱们须要多作一些逻辑。

这里咱们首先在 1 处经过行为是不是“按下”的来判断是不是一次新的触摸事件,若是是的话咱们须要重置当前的触摸状态。其次,咱们须要根据事件的类型来决定是否应该调用 onInterceptTouchEvent(),由于对一次触摸事件,咱们只须要在“按下”的时候判断一次就够了。因此,显然咱们须要将 MotionEvent.ACTION_DOWN 做为一个判断条件。而后,咱们使用 mFirstTouchTarget 这个全局的变量来记录上次拦截的结果——若是以前的事件交给过子元素处理,那么它就不为空。

除了 mFirstTouchTarget,咱们还须要用 mGroupFlagsFLAG_DISALLOW_INTERCEPT 标志位来判断该 ViewGroup 是否禁用了拦截。这个标志位能够经过 ViewGroup 的 requestDisallowInterceptTouchEvent(boolean) 来设置。只有没有禁用拦截事件的时候咱们才须要调用 onInterceptTouchEvent() 判断是否开启了拦截。

4.3.2 分发事件给子元素

若是在上面的操做中事件没有被拦截而且没有被取消,那么就会进入下面的逻辑。这部分代码处在 dispatchTouchEvent() 中。在下面的逻辑中会根据子元素的状态将事件传递给子元素:

// 对子元素进行倒序遍历,即从上到下进行遍历
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 (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
        ev.setTargetAccessibilityFocus(false);
        continue;
    }
    // ...
    // 在这里调用了 dispatchTransformedTouchEvent() 方法将事件传递给子元素
    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
        // ... 记录一些状态信息
        // 在这里完成对 mFirstTouchTarget 的赋值,表示触摸事件被子元素处理
        newTouchTarget = addTouchTarget(child, idBitsToAssign);
        alreadyDispatchedToNewTouchTarget = true;
        // 结束循环,完成子元素的遍历
        break;
    }
    // 显然,若是到了这一步,那么子元素的遍历仍将继续
}
复制代码

当判断了指定的 View 能够接收触摸事件以后会调用 dispatchTransformedTouchEvent() 方法分发事件。其定义的节选以下:

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) {
    final boolean handled;
    // ...
    if (child == null) {
        // 本质上逻辑与 View 的 dispatchTouchEvent() 一致
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        // ...
        // 交给子元素继续分发事件
        handled = child.dispatchTouchEvent(transformedEvent);
    }
    return handled;
}
复制代码

dispatchTransformedTouchEvent() 会根据传入的 child 是否为 null 分红两种调用的情形:事件没有被拦截的时候,让子元素继续分发事件;另外一种是当事件被拦截的时候,调用当前的 ViewGroup 的 super.dispatchTouchEvent(transformedEvent) 处理事件。

4.3.3 View 中的 dispatchTouchEvent

上面咱们分析的 dispatchTouchEvent(MotionEvent) 是 ViewGroup 中重写以后的方法。可是,正如咱们上面的分析,重写以前的方法老是会被调用,只是对象不一样。这里咱们就来分析如下这个方法的做用。

public boolean dispatchTouchEvent(MotionEvent event) {
    // ...
    boolean result = false;
    // ....
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        // 这里回调了 setOnTouchListener() 方法传入的 OnTouchListener
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        // 若是 OnTouchListener 没有被回调过或者返回了 false,就会调用 onTouchEvent() 进行处理
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    // ...
    return result;
}
复制代码

根据上面的源码分析,咱们知道,若是当前的 View 设置过 OnTouchListener, 而且在 onTouch() 回调方法中返回了 true,那么 onTouchEvent(MotionEvent) 将不会获得调用。那么,咱们再来看一下 onTouchEvent() 方法:

public boolean onTouchEvent(MotionEvent event) {
    // ...
    // 判断当前控件是不是能够点击的:实现了点击、长按或者设置了可点击属性
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
    // ...
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                // ...
                if (!focusTaken) {
                    if (mPerformClick == null) {
                        mPerformClick = new PerformClick();
                    }
                    if (!post(mPerformClick)) {
                        performClick();
                    }
                }
                // ...
                break;
            case MotionEvent.ACTION_DOWN:
                // ...
                if (!clickable) {
                    checkForLongClick(0, x, y);
                    break;
                }
                // ...
                break;
            // ...
        }
        return true;
    }
    return false;
}
复制代码

这里先判断指定的控件是不是可点击的,便是否设置过点击或者长按的事件。而后会在手势抬起的时候调用 performClick() 方法,并会在这个方法中尝试从 ListenerInfoOnClickListener 进行回调;会在长按的时候进行监听以调用相应长按事件;其余的事件与之相似,能够自行分析。因此,咱们能够得出结论:当为控件的触摸事件进行了赋值而且在其中返回了 true 就表明该事件被消费了,即便设置过单击和长按事件也不会被回调,触摸事件的优先级比后面二者要高。

通过上述分析,咱们能够知道 View 中的 dispatchTouchEvent(MotionEvent) 方法就是用来对手势进行处理的,因此回到 4.3.2,那里的意思就是:若是 ViewGroup 拦截了触摸事件,那么它就本身来对事件进行处理;不然就把触摸事件传递给子元素,让它来进行处理。

4.4.4 总结

以上就是咱们对 Android 中事件分发机制的详解,你能够经过图片和代码结合来更透彻得了解这方面的内容。虽然这部分代码比较多、比较长,可是每一个地方的设计都是合情合理的。

源代码

你能够在Github获取以上程序的源代码: Android-references

最后给你们分享一份很是系统和全面的Android进阶技术大纲及进阶资料,及面试题集

想学习更多Android知识,请加入Android技术开发企鹅交流 7520 16839

进群与大牛们一块儿讨论,还可获取Android高级架构资料、源码、笔记、视频

包括 高级UI、Gradle、RxJava、小程序、Hybrid、移动架构、React Native、性能优化等全面的Android高级实践技术讲解性能优化架构思惟导图,和BATJ面试题及答案!

群里免费分享给有须要的朋友,但愿可以帮助一些在这个行业发展迷茫的,或者想系统深刻提高以及困于瓶颈的朋友,在网上博客论坛等地方少花些时间找资料,把有限的时间,真正花在学习上,因此我在这免费分享一些架构资料及给你们。但愿在这些资料中都有你须要的内容。