本网站(662p.com)打包出售,且带程序代码数据,662p.com域名,程序内核采用TP框架开发,需要联系扣扣:2360248666 /wx:lianweikj
精品域名一口价出售:1y1m.com(350元) ,6b7b.com(400元) , 5k5j.com(380元) , yayj.com(1800元), jiongzhun.com(1000元) , niuzen.com(2800元) , zennei.com(5000元)
需要联系扣扣:2360248666 /wx:lianweikj
一文读懂 Android TouchEvent 事件分发、拦截、处理过程
飘飘悠悠 · 1031浏览 · 发布于2019-09-09 +关注

什么是事件?事件是用户触摸手机屏幕,引起的一系列TouchEvent,包括ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCEL等,这些action组合后变成点击事件、长按事件等。

在这篇文章中,用打Log测试的方法来了解Android TouchEvent 事件分发,拦截,处理过程。虽然看了一些其他的文章和源码及相关的资料,但是还是觉得需要打下Log和画图来了解一下,不然很容易忘记了事件传递的整个过程。所以写下这篇文章,达到看完这篇文章基本可以了解整个过程,并且可以自己画图画出来给别人看。

先看几个类,主要是画出一个3个ViewGroup叠加的界面,并在事件分发、拦截、处理时打下Log.

GitHub地址:https://github.com/libill/TouchEventDemo

一、通过打log分析事件分发

这里在一个Activity上添加三个ViewGroup来分析,这里值得注意的是Activity、View是没有onInterceptTouchEvent方法的。

一、了解Activity、ViewGroup1、ViewGroup2、ViewGroup3四个类

  1. activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
        <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.touchevent.demo.MyActivity">
        <com.touchevent.demo.ViewGroup1
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorAccent">
        <com.touchevent.demo.ViewGroup2
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="50dp"
            android:background="@color/colorPrimary">
            <com.touchevent.demo.ViewGroup3
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_margin="50dp"
                android:background="@color/colorPrimaryDark">
            </com.touchevent.demo.ViewGroup3>
        </com.touchevent.demo.ViewGroup2>
        </com.touchevent.demo.ViewGroup1>
    </android.support.constraint.ConstraintLayout>
  1. 主界面:MainActivity.java

public class MyActivity extends AppCompatActivity {
    private final static String TAG = MyActivity.class.getName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(TAG, "dispatchTouchEvent    action:" + StringUtils.getMotionEventName(ev));
        boolean superReturn = super.dispatchTouchEvent(ev);
        Log.d(TAG, "dispatchTouchEvent    action:" + StringUtils.getMotionEventName(ev) + " " + superReturn);
        return superReturn;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        Log.i(TAG, "onTouchEvent          action:" + StringUtils.getMotionEventName(ev));
        boolean superReturn = super.onTouchEvent(ev);
        Log.d(TAG, "onTouchEvent          action:" + StringUtils.getMotionEventName(ev) + " " + superReturn);
        return superReturn;
    }
}
  1. 三个ViewGroup,里面的代码完全一样:ViewGroup1.java,ViewGroup2.java,ViewGroup3.java。由于代码一样所以只贴其中一个类。

public class ViewGroup1 extends LinearLayout {
    private final static String TAG = ViewGroup1.class.getName();

    public ViewGroup1(Context context) {
        super(context);
    }

    public ViewGroup1(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(TAG, "dispatchTouchEvent    action:" + StringUtils.getMotionEventName(ev));
        boolean superReturn = super.dispatchTouchEvent(ev);
        Log.d(TAG, "dispatchTouchEvent    action:" + StringUtils.getMotionEventName(ev) + " " + superReturn);
        return superReturn;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i(TAG, "onInterceptTouchEvent action:" + StringUtils.getMotionEventName(ev));
        boolean superReturn = super.onInterceptTouchEvent(ev);
        Log.d(TAG, "onInterceptTouchEvent action:" + StringUtils.getMotionEventName(ev) + " " + superReturn);
        return superReturn;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        Log.i(TAG, "onTouchEvent          action:" + StringUtils.getMotionEventName(ev));
        boolean superReturn = super.onTouchEvent(ev);
        Log.d(TAG, "onTouchEvent          action:" + StringUtils.getMotionEventName(ev) + " " + superReturn);
        return superReturn;
    }
}

二、不拦截处理任何事件

添加没有拦截处理任何事件的代码,看看事件是怎么传递的,选择Info,查看Log.

从流程图可以看出,事件分发从Activity开始,然后分发到ViewGroup,在这个过程中,只要ViewGroup没有拦截处理,最后还是会回到Activity的onTouchEvent方法。

三、ViewGroup2的dispatchTouchEvent返回true

把ViewGroup2.java的dispatchTouchEvent修改一下,return 返回true使事件不在分发

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
 Log.i(TAG, "dispatchTouchEvent    action:" + StringUtils.getMotionEventName(ev));
 Log.d(TAG, "onInterceptTouchEvent action:" + StringUtils.getMotionEventName(ev) + " " + true);
 return true;
}

此时的Log

从图片可以看出,当ViewGroupon2的dispatchTouchEvent返回true后,事件不会再分发传送到ViewGroup3了,也不会分发到Activity的onTouchEvent了。而是事件到了ViewGroupon2的dispatchTouchEvent后,就停止了。dispatchTouchEvent返回true表示着事件不用再分发下去了。

四、ViewGroup2的onInterceptTouchEvent返回true

把ViewGroup2.java的onInterceptTouchEvent修改一下,return 返回true把事件拦截了

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Log.i(TAG, "dispatchTouchEvent    action:" + StringUtils.getMotionEventName(ev));
    boolean superReturn = super.dispatchTouchEvent(ev);
    Log.d(TAG, "dispatchTouchEvent    action:" + StringUtils.getMotionEventName(ev) + " " + superReturn);
    return superReturn;
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    Log.i(TAG, "onInterceptTouchEvent action:" + StringUtils.getMotionEventName(ev));
    Log.d(TAG, "onInterceptTouchEvent action:" + StringUtils.getMotionEventName(ev) + " " + true);
    return true;
}

此时的Log


可以看出ViewGroup2拦截了事件,就不会继续分发到ViewGroup3;而且ViewGroup3拦截了事件又不处理事件,会把事件传递到Activity的onTouchEvent方法。

五、ViewGroup2的onInterceptTouchEvent、onTouchEvent返回true

把ViewGroup2.java的onTouchEvent修改一下,return 返回true把事件处理了

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    Log.i(TAG, "onInterceptTouchEvent action:" + StringUtils.getMotionEventName(ev));
    Log.d(TAG, "onInterceptTouchEvent action:" + StringUtils.getMotionEventName(ev) + " " + true);
    return true;
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
    Log.i(TAG, "onTouchEvent          action:" + StringUtils.getMotionEventName(ev));
    Log.d(TAG, "onTouchEvent          action:" + StringUtils.getMotionEventName(ev) + " " + true);
    return true;
}


从流程可以总结出,当ViewGroup2的onInterceptTouchEvent、onTouchEvent都返回true时,事件最终会走到ViewGroup2的onTouchEvent方法处理事件,后续的事件都会走到这里来。

上面通过log分析很清楚了,是不是就这样够了?其实还不行,还要从源码的角度去分析下,为什么事件会这样分发。

二、通过源码分析事件分发

一、Activity的dispatchTouchEvent

先看看Activity下的dispatchTouchEvent

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

onUserInteraction方法

public void onUserInteraction() {
}

从代码可以了解

  1. 调用Activity的onUserInteraction方法,action为down时会进去onUserInteraction方法,但是这个是空方法不做任何事情,可以忽略。

  2. 调用window的superDispatchTouchEvent方法,返回true时事件分发处理结束,否则会调用Activity的onTouchEvent方法。

  3. 调用Activity的onTouchEvent方法,进入这个条件的方法是window的superDispatchTouchEvent方法返回false。从上面的分析(二、不拦截处理任何事件)可以知道,所有子View的dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent都返回false时会调动Activity的onTouchEvent方法,这个时候也是使window的superDispatchTouchEvent方法返回false成立。

二、window的superDispatchTouchEvent

Activity的getWindow方法

public Window getWindow() {
    return mWindow;
}

mWindow是如何赋值的?
是在Activity的attach方法赋值的,其实mWindow是PhoneWindow。

Activity的attach方法

final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window, ActivityConfigCallback activityConfigCallback) {
    attachBaseContext(context);

    mFragments.attachHost(null /*parent*/);

    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    ...
}

PhoneWindow的superDispatchTouchEvent方法

private DecorView mDecor;

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

DevorView的superDispatchTouchEvent

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

而mDecor是一个继承FrameLayout的DecorView,就这样把事件分发到ViewGroup上了。

三、ViewGroup的dispatchTouchEvent

3.1 ViewGroup拦截事件的情况

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

这里分为2种情况会判断是否需要拦截,也就是当某一条件成立时,会执行onInterceptTouchEvent判断是否需要拦截事件。

  1. 当actionMasked == MotionEvent.ACTION_DOWN时。

  2. 当mFirstTouchTarget != null时。mFirstTouchTarget是成功处理事件的ViewGroup的子View,也就是ViewGroup的子View在以下情况返回true时,这个在log分析流程图轻易得到:

    2.1 dispatchTouchEvent返回true

    2.2 如果子View是ViewGroup时,onInterceptTouchEvent、onTouchEvent返回true

另外还有一种情况是disallowIntercept为true时,intercepted直接赋值false不进行拦截。FLAG_DISALLOW_INTERCEPT是通过requestDisallowInterceptTouchEvent方法来设置的,用于在子View中设置,设置后ViewGroup只能拦截down事件,无法拦截其他move、up、cancel事件。为什么ViewGroup还能拦截down事件呢?因为ViewGroup在down事件时进行了重置,看看以下代码

// Handle an initial down.
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();
}

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

通过源码可以了解到,ViewGroup拦截事件后,不再调用onInterceptTouchEvent,而是直接交给mFirstTouchTarget的onTouchEvent处理,如果该onTouchEvent不处理最终会交给Activity的onTouchEvent。

3.2 ViewGroup不拦截事件的情况

ViewGroup不拦截事件时,会遍历子View,使事件分发到子View进行处理。

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 (ch                

相关推荐

android下vulkan与opengles纹理互通

talkchan · 1179浏览 · 2020-11-23 10:37:39
Android 使用RecyclerView实现轮播图

奔跑的男人 · 2175浏览 · 2019-05-09 17:11:13
微软发布新命令行工具 Windows Terminal

吴振华 · 869浏览 · 2019-05-09 17:15:04
Facebook 停止屏蔽部分区块链广告

· 754浏览 · 2019-05-09 17:20:08
加载中

0评论

评论
分类专栏
小鸟云服务器
扫码进入手机网页