android事件如何分发给子view

哈哈,第一次使用markdown,看着挺高大上的啊。如果顺手了,会直接切换默认为markdown。

话说关于android事件分发的博客真的不在少数,基本都是基于源码分析+实例代码的形式讲解。今天的这篇博客呢,主要的侧重点并不是在事件分发上,而是在事件的转换上。

为什么需要事件转换? 打个比方吧:

我们点击一个TextView的左上角,加入这个TextView在它老子的中间位置,那我们点击的x/y应该是多少呢? 在它老子那这两个值可能是100/100,而在TextView上打印就会是1/1了,也就是说在事件分发给儿子之前会有一次事件的剪裁过程,这个过程稍后我们也会在源码中找到。

大家都知道事件的分发都是从 dispatchTouchEvent() 方法开始的,但是我们一般很少去重写 dispatchTouchEvent 方法,原因就是尽量避免破坏android原生的事件分发机制。但是今天我们就来试这重写一下 dispatchTouchEvent 方法,从最简单的代码探究事件的剪裁。(做好心理准备, 就两行代码)。

public class MyViewGroup extends LinearLayout {

    private View mFirstView;

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

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mFirstView = getChildAt(0);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean handled = false;
        float x = ev.getX();
        float y = ev.getY();

        System.out.println("老子:x = " + x + ", y = " + y);

        if(x < mFirstView.getLeft() || x > mFirstView.getRight()) return true;
        if(y < mFirstView.getTop() || y > mFirstView.getBottom()) return true;

        int offetX = getScrollX() - mFirstView.getLeft();
        int offetY = getScrollY() - mFirstView.getTop();
        ev.offsetLocation(offetX, offetY);
        handled = mFirstView.dispatchTouchEvent(ev);
        ev.offsetLocation(-offetX, -offetY);

        return handled;
    }
}

代码很简单,我们只需要关注dispatchTouchEvent 方法就ok, 可以看到 在dispatchTouchEvent 方法中,我们并没有任何 super.dispatchTouchEvent 的调用地方,也就是说,我们完全重写了android默认的事件分发机制。

现在我们来分析一下这段的代码。

前面的略过,21行代码打印了我们触摸的坐标。

23~24行,我们先不去管它。先去看下面的代码。

26~28行代码:

int offetX = getScrollX() - mFirstView.getLeft();
int offetY = getScrollY() - mFirstView.getTop();
ev.offsetLocation(offetX, offetY);

首先我们计算了offsetX和offsetY值,这两个值为什么要这么计算得出呢,我是怎么知道要这么计算的呢? 这里的答案是:看的android源码的事件剪裁~~~ 我是直接copy出android的源码来放这的,然后咱们再去理解他,要去理解它,我们还需要一张生动形象高端大气的图才ok。

来看图, scrollX代表这绿色部分在屏幕外面的部分,假如这里是50,

left是蓝色部分距离他老子(绿色部分)左边的值,即getLeft这里是100,

当我们触摸屏幕的时候,坐标是从屏幕的左上角开始计算,从这张图来看,我们触摸的位置在绿色部分其实是50(100 - 10)的位置.

ok,在来看看offsetX 如果还是拿这张图来说的话, offsetX = 50 - 100 = -50 。offsetY的值也相同。

说到这里,我们大概明白MotionEvent.offsetLocation 的作用了,它的作用就是根据你的两个参数去偏移坐标。这里,我们的x偏移了-50,计算一下,如果将这个剪裁后的事件分发给子view,那对于子view而言,点击的位置就是0了。哈哈,终于走通了。

那接下来,我们就通过log来验证咱们的猜想吧。

看log验证了我们的猜想,事件的坐标的确是经过了剪裁。

而,我们代码中那两个判断:

if(x < mFirstView.getLeft() || x > mFirstView.getRight()) return true;
if(y < mFirstView.getTop() || y > mFirstView.getBottom()) return true;

主要作用就是防止该事件没有发生到该view身上,而强制分发出去了。

最后,我们再去看看源码中是怎么操作的,是不是和我们的逻辑一样。

 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

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

看18~22行,代码和我们的一样!!!(其实是我们抄的源码,哈哈)。完结。

哦,对了,本篇博客并没有什么实质性的意义,就是去探究一下android事件在分发过程的剪裁,树立一个思想:

对于view而言,事件发生到我身上,我就把它看作我自己的,你点我拿,我就给你报哪。

在实际代码中,我们还是尽可能的避免重写dispatchTouchEvent方法,毕竟这里是android默认的事件分发机制。

真正完毕了,吃饭去鸟~~~

时间: 2024-10-29 12:08:40

android事件如何分发给子view的相关文章

Android事件分发详解(一)——View的事件分发

MainActivity如下: package cc.cv; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnTouchListener; import android.widget.Button; import android.widge

Android事件的分发、拦截和执行

在平常的开发中,我们经常会遇到点击,滑动之类的事件.有时候不同的view之间也存在各种滑动冲突.比如布局的内外两层都能滑动的话,那么就会出现冲突了.这个时候我们就需要了解Android的事件分发机制. 介绍 Android的触摸事件分发过程由三个很重要的方法来共同完成:dispatchTouchEvent.onInterceptTouchEvent.onTouchEvent.我先将这三个方法大体的介绍一下. public boolean dispatchTouchEvent(MotionEven

从源代码解释Android事件分发机制

在ViewRootImpl的setView方法中.用户的触摸按键消息是体如今窗体上的.而windowManagerService则是管理这些窗体,它一旦接收到用户对窗体的一些触摸按键消息,会进行对应的动作,这样的动作是须要体如今详细的view上面.在Android中.一个详细的界面是由一个Activity呈现的,而Activity中则包括了一个window,此window中又包括了一个phoneWindow.这个phoneWindow才是真正意义上的窗体.它把一个框架布局进行了一定的包装.并提供

Android事件分发详解(三)——ViewGroup的dispatchTouchEvent()源码学习

package cc.aa; import android.os.Environment; import android.view.MotionEvent; import android.view.View; public class UnderstandDispatchTouchEvent { /** * dispatchTouchEvent()源码学习及其注释 * 常说事件传递中的流程是:dispatchTouchEvent->onInterceptTouchEvent->onTouchE

Android事件分发机制源码分析

小小感慨一下,做android有一段时间了,一直以来都是习惯整理笔记存到有道笔记上,没有写博客的习惯.以后逐步分类整理出来,也算"复习"一遍了 - _ - . android的事件分发相关的方法有三个: 1.public booleandispatchTouchEvent(MotionEvent ev) 2.public boolean onInterceptTouchEvent(MotionEvent ev) 3.public booleanonTouchEvent(MotionEv

从源码解释Android事件分发机制

在ViewRootImpl的setView方法中,用户的触摸按键消息是体现在窗体上的,而windowManagerService则是管理这些窗口,它一旦接收到用户对窗体的一些触摸按键消息,会进行相应的动作,这种动作是需要体现在具体的view上面,在Android中,一个具体的界面是由一个Activity呈现的,而Activity中则包含了一个window,此window中又包含了一个phoneWindow,这个phoneWindow才是真正意义上的窗口,它把一个框架布局进行了一定的包装,并提供了

android事件分发流程

1.描述 说到android事件的分发机制,真的是感觉既熟悉又陌生,因为每次需要用到的时候查看相关的源码,总能找到一些所以然来,但是要根据自己理解从头到尾说一遍,却一点都说不上.总结原因吧,感觉是自己不善于总结,过目就忘,并没有把心思放在上面,自然也就没有一点概念咯~~所以在这里主要是把自己理解的一些东西记录下来,不涉及源代码. 好吧,接下来简单说说android事件分发流程吧,说到事件分发,首先应该想到的是两个类,View和ViewGroup,ViewGroup是继承自View实现的,View

Android事件分发机制源代码分析

小小感慨一下,做android有一段时间了,一直以来都是习惯整理笔记存到有道笔记上,没有写博客的习惯. 以后逐步分类整理出来,也算"复习"一遍了 - _ - . android的事件分发相关的方法有三个: 1.public booleandispatchTouchEvent(MotionEvent ev) 2.public boolean onInterceptTouchEvent(MotionEvent ev) 3.public booleanonTouchEvent(MotionE

你真的看懂Android事件分发了吗?

引子 Android事件分发其实是老生常谈了,但是说实话,我觉得很多人都只是懂其大概,模棱两可.本文的目的就是再次从源码层次梳理一下,重点放在ViewGroup的dispatchTouchEvent方法上,这个方法是事件分发的核心中的核心!我们借此以小见大,理解事件分发的机制.ps,本文着重在源码和分析,就不怎么画图了(其实是懒),大家可以看网上相关图片,随便一搜很多. 先简单讲一下事件分发的源头 很多人讲事件分发,都说其开始是从Activity的dispatchTouchEvent开始的,大家