android事件分发教程(二):ViewGroup

上一篇博客android事件分发教程(一):View我们以一种最简单易懂的方式学习了View的事件分发机制,那么趁热(火)打铁(劫),今天来个进阶- -,学习哈ViewGroup中的事件分发。

首先要引进一个方法:onInterceptTouchEvent;这个事ViewGroup独有的,从字面意思可以理解到是个拦截事件;既然本篇是研究ViewGroup的,那么新建一个MyLayout.java,继承自RelativeLayout,用于测试用:

package com.cjt.event;

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

/**
 * Created by mvp-cjt on 2016/6/14.
 * Email:879309896@qq.com
 */
public class MyLayout extends RelativeLayout implements View.OnTouchListener{

    public final static String TAG = "mvp-cjt";

    public MyLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        setOnTouchListener(this);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.i(TAG, "MyLayout dispatchTouchEvent: ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(TAG, "MyLayout dispatchTouchEvent: ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.i(TAG, "MyLayout dispatchTouchEvent: ACTION_UP");
                break;
            default:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.i(TAG, "MyLayout onInterceptTouchEvent: ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(TAG, "MyLayout onInterceptTouchEvent: ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.i(TAG, "MyLayout onInterceptTouchEvent: ACTION_UP");
                break;
            default:
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouch(View view, MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.i(TAG, "MyLayout onTouch: ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(TAG, "MyLayout onTouch: ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.i(TAG, "MyLayout onTouch: ACTION_UP");
                break;
            default:
                break;
        }
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.i(TAG, "MyLayout onTouchEvent: ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(TAG, "MyLayout onTouchEvent: ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.i(TAG, "MyLayout onTouchEvent: ACTION_UP");
                break;
            default:
                break;
        }
        return super.onTouchEvent(ev);
    }
}

然后把上篇的两个空间MyButton、MyImage添加进来,最终的测试界面activity_main.xml则是:

<?xml version="1.0" encoding="utf-8"?>
<com.cjt.event.MyLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.cjt.event.MyButton
        android:id="@+id/my_button"
        android:layout_centerInParent="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="button"/>

    <com.cjt.event.MyImage
        android:layout_below="@+id/my_button"
        android:layout_centerHorizontal="true"
        android:src="@mipmap/ic_launcher"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</com.cjt.event.MyLayout>

还是先点击一下MyImage,观测控制台输出:

06-15 21:49:40.906 32285-32285/com.cjt.event I/mvp-cjt: MyLayout dispatchTouchEvent: ACTION_DOWN
06-15 21:49:40.915 32285-32285/com.cjt.event I/mvp-cjt: MyLayout onInterceptTouchEvent: ACTION_DOWN
06-15 21:49:40.915 32285-32285/com.cjt.event I/mvp-cjt: MyImage dispatchTouchEvent: ACTION_DOWN
06-15 21:49:40.916 32285-32285/com.cjt.event I/mvp-cjt: MyImage onTouch: ACTION_DOWN
06-15 21:49:40.916 32285-32285/com.cjt.event I/mvp-cjt: MyImage onTouchEvent: ACTION_DOWN
06-15 21:49:40.917 32285-32285/com.cjt.event I/mvp-cjt: MyLayout onTouch: ACTION_DOWN
06-15 21:49:40.917 32285-32285/com.cjt.event I/mvp-cjt: MyLayout onTouchEvent: ACTION_DOWN

然后点击一下MyButton,同样观测控制台输出:

06-15 21:52:26.965 19602-19602/com.cjt.event I/mvp-cjt: MyLayout dispatchTouchEvent: ACTION_DOWN
06-15 21:52:26.965 19602-19602/com.cjt.event I/mvp-cjt: MyLayout onInterceptTouchEvent: ACTION_DOWN
06-15 21:52:26.965 19602-19602/com.cjt.event I/mvp-cjt: MyButton dispatchTouchEvent: ACTION_DOWN
06-15 21:52:26.965 19602-19602/com.cjt.event I/mvp-cjt: MyButton onTouch: ACTION_DOWN
06-15 21:52:26.965 19602-19602/com.cjt.event I/mvp-cjt: MyButton onTouchEvent: ACTION_DOWN
06-15 21:52:27.014 19602-19602/com.cjt.event I/mvp-cjt: MyLayout dispatchTouchEvent: ACTION_MOVE
06-15 21:52:27.014 19602-19602/com.cjt.event I/mvp-cjt: MyLayout onInterceptTouchEvent: ACTION_MOVE
06-15 21:52:27.014 19602-19602/com.cjt.event I/mvp-cjt: MyButton dispatchTouchEvent: ACTION_MOVE
06-15 21:52:27.014 19602-19602/com.cjt.event I/mvp-cjt: MyButton onTouch: ACTION_MOVE
06-15 21:52:27.014 19602-19602/com.cjt.event I/mvp-cjt: MyButton onTouchEvent: ACTION_MOVE
06-15 21:52:27.015 19602-19602/com.cjt.event I/mvp-cjt: MyLayout dispatchTouchEvent: ACTION_UP
06-15 21:52:27.015 19602-19602/com.cjt.event I/mvp-cjt: MyLayout onInterceptTouchEvent: ACTION_UP
06-15 21:52:27.015 19602-19602/com.cjt.event I/mvp-cjt: MyButton dispatchTouchEvent: ACTION_UP
06-15 21:52:27.015 19602-19602/com.cjt.event I/mvp-cjt: MyButton onTouch: ACTION_UP
06-15 21:52:27.015 19602-19602/com.cjt.event I/mvp-cjt: MyButton onTouchEvent: ACTION_UP

可能log有点多,不要慌,慢慢看。根据这个log,能够初拟出事件在MyLayout、MyImage、MyButton中各个方法的传递,先看看第一次点击MyImage的时候事件传递方向:
这里写图片描述
注意这种情况无法触发一个完整的事件DOWN、MOVE、UP,在DOWN事件沿着图上的方向传递完后就结束了。

再看看点击MyButton的时候事件传递方向:
这里写图片描述
很明显的是在事件返回最上层(MyLayout)的时候,在MyButton这里就被消费掉了,不再向外分发。另外,这种情况能够触发一个完整的事件DOWN、MOVE、UP。

这个“干货”未免也太干了,没点理论支持是站不住脚的,那追溯下ViewGroup的dispatchTouchEvent的源码,还是截取部分,关键的代码:

public boolean dispatchTouchEvent(MotionEvent event) {
    /*-------------------------- action为DOWN ----------------------------*/
    if (action == MotionEvent.ACTION_DOWN) {
        // disallowIntercept:是否不允许拦截,默认false,可通过viewGroup.requestDisallowInterceptTouchEvent(boolean);进行设置
        // onInterceptTouchEvent:可重写,默认返回false
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {
            final View[] children = mChildren;
            final int count = mChildrenCount;
            // 遍历子View
            for (int i = count - 1; i >= 0; i--) {
                final View child = children[i]; 
                // 如果touch事件在该子View上,就继续分发
                if (frame.contains(scrolledXInt, scrolledYInt)) {  
                    if (child.dispatchTouchEvent(ev))  {
                        // 子View的dispatchTouchEvent返回true的话,代表具有消费能力,return true,跳出遍历子View循环
                        mMotionTarget = child;  
                        return true;  
                    } 
                } 
            }  
        }  
    }

    /*-------------------------- action为MOVE、UP、CANCEL ----------------------------*/
    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL);
    final View target = mMotionTarget;
    if (target == null) {
        // target为null说明子View都不具有消费能力,这里的super就是ViewGroup的父类View
        return super.dispatchTouchEvent(event);  
    }
    if (!disallowIntercept && onInterceptTouchEvent(event)) {  
        mMotionTarget = null;  
        return true;  
    }
    if (isUpOrCancel) {  
        mMotionTarget = null;  
    } 
    // 执行最靠近外层且具有消费能力View的dispatchTouchEvent方法(类似递归,如果子View是ViewGroup又会嵌套执行)
    return target.dispatchTouchEvent(event);  
}

这个已经是精简的核心代码,一定要认真仔细地弄清楚。这样就不难解释上述得出的两幅图了。结合源码,待我细细道来(⊙o⊙)。

点击MyImage;先进入MyLayout的dispatchTouchEvent,作出action判断,进入DOWN方法体,然后判断是否拦截if (disallowIntercept || !onInterceptTouchEvent(ev)),接着遍历子View,遍历至MyImage时,frame.contains(scrolledXInt, scrolledYInt)为true,进入child.dispatchTouchEvent(ev),child就会接着执行dispatchTouch中的onTouchonTouchEvent;结合View的事件分发,返回false,退出for循环,mMotionTarget 为null,然后执行super.dispatchTouchEvent(event); ViewGroup的父类就是View,又回到View的dispatchTouchEvent中了,接着执行MyLayout的onTouch和onTouchEvent方法。

点击MyButton;与上面MyImage不同的是,MyButton的dispatchTouchEvent返回的是true,然后执行mMotionTarget = child; return true; 这样直接返回true了,就没有后面MyLayout的onTouch和onTouchEvent事件了。

好了,又到了文末总结的时候了:
1:消费能力,就是在ACTION为DOWN的时候,child的dispatchTouchEvent返回true,这只是针对ViewGroup中来说的,这样就能够触发一个完整的事件(DOWN、MOVE、UP);
2:是否拦截onInterceptTouchEvent的返回值并不是决定性的,它与另外一个disallowIntercept共同决定,disallowIntercept 可以在子类通过getParent.requestDisallowInterceptTouchEvent设置。但若onInterceptTouchEvent在down返回true的话,mMotionTarget 为null,这样子View是无法获取到事件的;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值