dispatchTouchEvent小分析

对于dispatchTouchEvent一直都处于迷迷糊糊的理解状态,就知道他有分发事件的作用,但是真正每次用起来却又很茫然。今天小小的研究了一下,希望能给大家带来帮助。
下面我一点一点的贴出源码来分析。

public boolean dispatchTouchEvent(MotionEvent ev) {  
    final int action = ev.getAction();  
    final float xf = ev.getX();  
    final float yf = ev.getY();  
    final float scrolledXFloat = xf + mScrollX;  
    final float scrolledYFloat = yf + mScrollY;  
    final Rect frame = mTempRect;  
    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
    if (action == MotionEvent.ACTION_DOWN) {  
        if (mMotionTarget != null) {  
            mMotionTarget = null;  
        }  
        /**是否拦截 || onInterceptTouchEvent是否返回fasle,注意这里是取反. *这里引用一下老郭blog中说的一句话"1.disallowIntercept是指是否禁用掉事件拦截的功能,默认是 *false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改.2.竟然就是 *对onInterceptTouchEvent方法的返回值取反!也就是说如果我们在onInterceptTouchEvent方法中 *返回false,就会让第二个值为true,从而进入到条件判断的内部,如果我们在nInterceptTouchEvent *方法中返回true,就*会让第二个值为false,从而跳出了这个条件判断" */       
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
            ev.setAction(MotionEvent.ACTION_DOWN);  
            final int scrolledXInt = (int) scrolledXFloat;  
            final int scrolledYInt = (int) scrolledYFloat;  
            final View[] children = mChildren;  
            final int count = mChildrenCount;  
            //然后这里开始循环子view(注意这里是倒序)
            for (int i = count - 1; i >= 0; i--) {  
                final View child = children[i];  
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                        || child.getAnimation() != null) {  
                    child.getHitRect(frame);  
                    //如果x,y坐标在子view中这进入if
                    if (frame.contains(scrolledXInt, scrolledYInt)) {  
                        final float xc = scrolledXFloat - child.mLeft;  
                        final float yc = scrolledYFloat - child.mTop;  
                        ev.setLocation(xc, yc);  
                        child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
                        if (child.dispatchTouchEvent(ev))  {  
                           //赋值给目标对象
                            mMotionTarget = child;  
                            return true;  
                        }  
                    }  
                }  
            }  
        }  
    } 
    .......
     return target.dispatchTouchEvent(ev);  
}  

好的,简单的注释了一下代码。接下来我们写一个小demo来实践一下。RelativeLayout中写2个button,btn1在下层,btn2在上层,分别点击看其点击是否有效。

<RelativeLayout 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=".MainActivity" >
    <Button  android:id="@+id/btn1" android:layout_width="300dp" android:layout_height="300dp" android:background="#a59a6a" android:text="btn1" />
    <Button  android:id="@+id/btn2" android:layout_width="100dp" android:layout_height="100dp" android:text="btn2" android:background="#aaaaaa" />
</RelativeLayout>

然后注册点击事件:

findViewById(R.id.btn1).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "btn1~~~~~", 1).show();
                Log.d("MainActivity", "btn1 on click~~~~~");
            }
        });
findViewById(R.id.btn2).setOnClickListener(new OnClickListener() {

    @Override
    public void onClick(View v) {
        Toast.makeText(MainActivity.this, "btn2~~~~~", 1).show();
        Log.d("MainActivity", "btn2 on click~~~~~");
    }
});

dispatchTouchEvent小分析_第1张图片
然后分别点击btn1,和btn2.打出log:
这里写图片描述

当然大伙很容就说了,当然了点击btn1当然打出log,btn2当然打出log。这还用说吗。好的。用源码的角度来解释一下,点击btn1的时候,点击x、y的坐标点在btn1上,不在btn2上,所以btn2不响应。点击btn2的时候x,y的坐标在2个上面,那问题来了,为什么就btn2相应了而btn2没有相应了。
来看看源码,首先进行for循环,然后取mChildrenCount-1的view,也就是btn2后return结束。
相信大家用过bringToFront,说我用代码强制绘制到顶部。那这种分发机制还管用吗。ok,改一下demo。
dispatchTouchEvent小分析_第2张图片
在xml中btn3放在btn2的上面,然后在代码中将btn2强制置顶。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/root"
    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=".MainActivity">

    <Button
        android:id="@+id/btn1"
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:background="#a59a6a"
        android:text="btn1" />

    <com.example.ontouchtext.MyFrameLayout
        android:id="@+id/framelayout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <com.example.ontouchtext.Btn2
            android:id="@+id/btn2"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="#aaaaaa"
            android:text="btn2" />

        <com.example.ontouchtext.Btn3
            android:id="@+id/btn3"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginLeft="50dp"
            android:layout_marginTop="50dp"
            android:background="#1babaa"
            android:text="btn3" />

    </com.example.ontouchtext.MyFrameLayout>


</RelativeLayout>

然后在activity中编写置顶代码。

    btn2.bringToFront();

自定义Btn2,Bnt3。

public class Btn2 extends Button{

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            Log.d("Btn2", "btn2 on click~~~~~");
        }
        return super.onTouchEvent(event);
    }
}
public class Btn3 extends Button{

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            Log.d("Btn3", "btn3 on click~~~~~");
        }
        return super.onTouchEvent(event);
    }
}

自定一个MyFrameLayout

public class MyFrameLayout extends FrameLayout {

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final float xf = ev.getX();
        final float yf = ev.getY();
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            Rect frame = new Rect();
            for (int i = getChildCount() - 1; i >= 0; i--) {
                Button v = (Button) getChildAt(i);
                Log.d("MainActivity", v.getText().toString());
                v.getHitRect(frame);
                Log.d("MainActivity", frame.contains((int)xf,(int)yf)+"");
            }
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(" onTouchEvent", "MyFrameLayout onTouchEvent go on~~");
        return super.onTouchEvent(event);
    }
}

dispatchTouchEvent小分析_第3张图片
这时候如果我点击btn2和btn3的交集处,到底会触发bt2还是btn3了,我们看看log。
这里写图片描述
btn2触发了,再看看上面的先打印的是btn2,btn3说明我们调用了btn2.bringToFront()后系统将本来在最后面的btn3放在了btn2的后面,倒序循环后,btn2最先进入到for循环中,然后复制给了target后return。
希望对大家有帮助,如有错误希望大家指出。

附上代码:这里写链接内容

你可能感兴趣的:(事件分发)