android view 原理 -- 事件传递

1 概述

我们在view上进行的点击事件,是如何传递到对应的控件的,这篇文章主要讲解控件的事件传递原理。

2 布局关系

这里写图片描述

相信大家都知道,viewGroup可以包含viewGroup也可以包含view。这里只是列举了这两种可能性,具体应用的层级深度和广度肯定有所不同,但是基本都是这两种情况的变种。嵌套关系基本类似。

3 示例

这里讲解相关的示例,分为3个方面来讲解。

(1)不拦截

这里看上层的viewGroup在onInterceptTouchEvent方法中返回false的情况,也就是上层并不拦截点击事件。

这里写图片描述

如上图的一个布局,点击button会走什么流程,让事件传递到button呢。

先看一看xml:

<?xml version="1.0" encoding="utf-8"?>
<com.mahuafactory.animation.custom.MyRelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.mahuafactory.animation.EventActivity">

    <com.mahuafactory.animation.custom.MyLinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <com.mahuafactory.animation.custom.MyButton
            android:id="@+id/button"
            android:layout_width="200dp"
            android:layout_height="50dp"
            android:text="event_test"/>

    </com.mahuafactory.animation.custom.MyLinearLayout>


</com.mahuafactory.animation.custom.MyRelativeLayout>

xml很简单,就和上面的图里的布局基本一致。

再看布局的代码以及日志格式;

public class MyRelativeLayout extends RelativeLayout {
    private static final String TAG = "MyRelativeLayout";

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

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

    public MyRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e(TAG, "dispatchTouchEvent--" + ev.getActionMasked());
        boolean b = super.dispatchTouchEvent(ev);
        Log.e(TAG, "dispatchTouchEvent--" + ev.getActionMasked() + "-- return -- " + b);
        return b;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e(TAG, "onInterceptTouchEvent--" + ev.getActionMasked());
        boolean b = super.onInterceptTouchEvent(ev);
        Log.e(TAG, "onInterceptTouchEvent--" + ev.getActionMasked() + "-- return --" + b);
        return b;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(TAG, "onTouchEvent--" + event.getActionMasked());
        boolean b = super.onTouchEvent(event);
        Log.e(TAG, "onTouchEvent--" + event.getActionMasked() + "-- return --" + b);
        return b;
    }
}
package com.mahuafactory.animation.custom;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;

/**
 * Created by wt on 2016/5/25.
 */
public class MyLinearLayout extends LinearLayout{
    private static final String TAG = "MyLinearLayout";

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

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

    public MyLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e(TAG, "dispatchTouchEvent--" + ev.getActionMasked());
        boolean b = super.dispatchTouchEvent(ev);
        Log.e(TAG, "dispatchTouchEvent--" + ev.getActionMasked()+ "-- return --" + b);
        return b;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e(TAG, "onInterceptTouchEvent--" + ev.getActionMasked());
        boolean b = super.onInterceptTouchEvent(ev);
        Log.e(TAG, "onInterceptTouchEvent--" + ev.getActionMasked()+ "-- return --" + b);
        return b;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(TAG, "onTouchEvent--" + event.getActionMasked());
        boolean b = super.onTouchEvent(event);
        Log.e(TAG, "onTouchEvent--" + event.getActionMasked()+ "-- return --" + b);
        return b;
    }
}
package com.mahuafactory.animation.custom;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.Button;

/**
 * Created by wt on 2016/5/25.
 */
public class MyButton extends Button{
    private static final String TAG = "MyButton";

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

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

    public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e(TAG, "dispatchTouchEvent--" + ev.getActionMasked());
        boolean b = super.dispatchTouchEvent(ev);
        Log.e(TAG, "dispatchTouchEvent--" + ev.getActionMasked()+ "-- return --" + b);
        return b;
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(TAG, "onTouchEvent--" + event.getActionMasked());
        boolean b = super.onTouchEvent(event);
        Log.e(TAG, "onTouchEvent--" + event.getActionMasked()+ "-- return --" + b);
        return b;
    }
}

日志格式比较简单,就是进入方法的时候打印一下,随后返回前打印一下,这样可以理清楚递归关系。按下button看下日志:

这里写图片描述

可以看出,这是一个递归的效果,整个事件由最外层的布局通过调用dispatchTouchEvent往最里面传递,在处理之后,则返回true,随后,dispatchTouchEvent方法依次返回。当然,这是在上层并不主动拦截事件的前提下,也就是onInterceptTouchEvent返回的是false。流程图如下:

这里写图片描述

结论:在不拦截的情况下,事件从上往下,然后再从下往上回传,这也是递归调用导致的。

(2)拦截

这里看在中层的LinearLayout中,直接在onInterceptTouchEvent中返回true,从而拦截所有事件的情况,看日志如下:

这里写图片描述

可以看到,事件依然从上层的RelaytiveLayout开始,由于它没有拦截,所以到达LinearLayout,但是它拦截了事件,事件没有继续往下,而是直接进入了LinearLayout的ontouchEvent方法,随后该方法并没有返回true,按照上面不拦截情况来看,如果这个方法返回true,那么就应该从下往上的调用dispatchTouchEvent而结束整个过程,但是这里由于返回的false,代表该LinearLayout没有处理touch事件,从而,没有立即调用RelativeLayout的dispatchTouchEvent结束事件传递,而是进入了LinearLayout上一层的RelativeLayout的onTouchEvent方法,进行进一步处理,由于这里也没有处理,所以最后调用了dispatchTouchEvent结束了整个事件的传递。

结合上面的不拦截情况,我们完善他的传递流程图,如下:

这里写图片描述

其中onTouchEvent返回true则递归结束,所以这里没有画出来。可以看到,在LinearLayout里面拦截了事件之后,事件到达了它的onTouchEvent方法,随后返回了false,又传递到了上层的onTouchEvent方法中。

(3)拦截非down事件

上面的拦截是直接将onInterceptTouchEvent的返回值设置为true,拦截了一切的事件,那么如果,我们不拦截down事件,也就是不拦截按下事件,move和up都拦截,而在button中,也只处理down事件,不处理其他事件,会出现什么情况呢:

这里写图片描述
这里写图片描述

日志中的0代表down,1代表up,2代表move,3代表cancel,分析这里的日志可以看出,

首先,down事件的传递比较正常,处理也正常,而移动事件,在LinearLayout中遭到拦截,奇怪的是,拦截以后事件并没有直接进入到LinearLayout的onTouchEvent方法中,而是转换为了cancel事件继续传递给Button,由于这里设置了button只处理down事件,所以返回了false,随后该事件结束。接着,又有move事件到来,此时上层没有再传递cancel事件给button,由此可见,只有拦截的第一次会传递一个cancel事件,后续将不再传递,后续的move事件直接进入了LinearLayout的onTouchEvent中,包括最后的up事件也是如此。

可以看出,down事件在整个事件传递中有着很重要的地位,他的接收者和处理者会直接影响后续的事件传递过程。

结论:事件从上往下传递的过程中,有如下几种情况

如果当前事件是down事件:

如果被拦截:

直接进入当前view的onTouchEvent方法,如果在该方法中没有处理,则交给上层view的onTouchEvent方法处理,如果当前view处理了则结束传递

未被拦截:

逐层传递,交给最底层的view(无论是否是viewGroup)的onTouchEvent方法,如果处理则完成传递,如果未处理则交给上层view的onTouchEvent方法。

如果当前事件不是down事件

down事件是由哪个view处理,那么down事件所引发的一系列事件(包括move,up等)都属于这个view,即使后续的非down事件它自己不处理,也不会交给上层view处理,而是直接结束

如果被拦截:

即使被拦截,这个事件系列依然属于down事件的处理者,这里只是拦截了后续的事件,所以即使拦截的view不处理后续事件,那么这个事件也不会交给上层view来处理。

拦截的时候,会传递一个cancel事件到down事件处理者的onTouchEvent,通知它后续的事件系列将不再传递给你,将拦截view之下的view从事件接收层级中除名,从下一个事件开始把拦截事件的view当作底层的接收view,事件直接进入拦截者的onTouchEvent方法中,不经过onInterceptTouchEvent,因为下层的view被除名,所以他是最底层的接收view,也就直接从它的onTouchEvent方法开始执行。无论在onTouchEvent方法中是否处理,事件传递都结束。不会向上传递。只有down事件可以向上传递。

未被拦截:

事件继续交给down事件的处理view处理,即使未处理也不交由上层view处理,而是直接结束

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值