View事件解析(下)

上一篇文章主要分析了一下事件的分发流程(view事件分析(上)这里写链接内容),这篇文章主要是通过源码来分析一下onTouchEvent,dispatchTouchEvent源码内部。
还是沿用上一篇文章的demo,采用sdk19的版本查看源码。 从上一篇文章中我们知道分发首先是从父类开始的,如果父类不拦截,然后交给子类自己分发,那我们先从父类的dispatchTouchEvent讲起。

   final int childrenCount = mChildrenCount;
   if (newTouchTarget == null && childrenCount != 0) {
       final float x = ev.getX(actionIndex);
       final float y = ev.getY(actionIndex);
       // Find a child that can receive the event.
       // Scan children from front to back.
       final View[] children = mChildren;

       final boolean customOrder = isChildrenDrawingOrderEnabled();
       for (int i = childrenCount - 1; i >= 0; i--) {
           final int childIndex = customOrder ?
                   getChildDrawingOrder(childrenCount, i) : i;
           final View child = children[childIndex];
           if (!canViewReceivePointerEvents(child)
                   || !isTransformedTouchPointInView(x, y, child, null)) {
               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();
               mLastTouchDownIndex = childIndex;
               mLastTouchDownX = ev.getX();
               mLastTouchDownY = ev.getY();
               newTouchTarget = addTouchTarget(child, idBitsToAssign);
               alreadyDispatchedToNewTouchTarget = true;
               break;
           }
       }
   }

这里挑选了主要代码,第10行开始for循环这里循环了所有的子类,然后寻找目标child(之前有一篇文章对这块做过详细的介绍dispatchTouchEvent小分析这里写链接内容)。这里是在第14行的if语句中进行判断的,如果能接收事件或者在子child的范围内,这里同时取反,如果不满足则直接跳过进行下一个循环,下面我们重点看一下是怎么判断是否在child范围内的。

   protected boolean isTransformedTouchPointInView(float x, float y, View child,
            PointF outLocalPoint) {
        float localX = x + mScrollX - child.mLeft;
        float localY = y + mScrollY - child.mTop;
        if (! child.hasIdentityMatrix() && mAttachInfo != null) {
            final float[] localXY = mAttachInfo.mTmpTransformLocation;
            localXY[0] = localX;
            localXY[1] = localY;
            child.getInverseMatrix().mapPoints(localXY);
            localX = localXY[0];
            localY = localXY[1];
        }
        //这里调用了下面的pointInView的方法来判断是都在其范围内
        final boolean isInView = child.pointInView(localX, localY);
        if (isInView && outLocalPoint != null) {
            outLocalPoint.set(localX, localY);
        }
        return isInView;
    }

    /**
     * Determines whether the given point, in local coordinates is inside the view.
     */
    /*package*/ final boolean pointInView(float localX, float localY) {
        return localX >= 0 && localX < (mRight - mLeft)
                && localY >= 0 && localY < (mBottom - mTop);

ok,这里已经分析了怎么确定了目标child的,确定了child后将自己分发给child,在上面dispatchTouchEvent中的第28行调用了这句话dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign),进入到源码看看,这里仍然展示了部分重要代码:

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

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            //上面已经确定了child,所有直接走了else
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                 //这里handle的值直接取决于child的dispatchTouchEvent返回值
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
        ........
    }

如果在这里子类的dispatchTouchEvent返回了false,也就是在viewGroup中if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) 这句,进入不到了if语句,下面的newTouchTarget = addTouchTarget(child, idBitsToAssign);alreadyDispatchedToNewTouchTarget = true;也无法进行赋值,也就是需要重新进行找target。这时候在进行for循环找是否有符合的对应child。下面我们来打log瞅瞅,将之前的代码小改了一下,显示了Btn3。

    <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"
            android:visibility="visible"
            />

    </com.example.ontouchtext.MyFrameLayout>

这里写图片描述
这里点击了btn2和btn3重合的区域,主要是为了btn3也能满足pointInView。然后讲btn2的dispatchTouchEvent返回了false。看看log:
这里写图片描述
正和我们预期的一样,先走了父类的分发,然后进入到for循环,找到btn2,btn2的dispatchTouchEvent返回了false,并没有给目标child赋值,这时候for循环接着找,然后找到了btn3。所以看到了我们上面的log。
下面我把btn的dispatchTouchEvent的返回值改为true,然后再看看log打印值:
这里写图片描述
看到的结果也是我们预期的,并没有走btn3,也就是btn2已经成为了目标child。
接着我们来说说onTouchEvent方法,我们常常说onTouchEvent方法返回true是用来消费掉事件的。我们来看看源码研究一下到底这个有啥功效。同样我们来看看dispatchTouchEvent方法,不过这次是看view的方法非viewGroup。

 public boolean dispatchTouchEvent(MotionEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                return true;
            }

            if (onTouchEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }

这里是不是有很惊喜的发现,第10行看起,如果onTouch返回了false,那么这里走到了第14行的if判断,这里如果onTouchEvent返回true,那么整个dispatchTouchEvent将返回true,否则第22行直接返回了false,也就是上面刚讲到的,view的dispatchTouchEvent返回false,不能成为目标child。所以该事件分发到btn2时没有进行消费。
这里也可以看到如果onTouch返回了true,也会整个view的dispatchTouchEvent返回true,同样也能消费掉这个事件。
最后如果btn2,btn3,和父类ViewGroup都不消费这个事件,到底事件会怎么处理了。下面接着改了一下demo。
将btn2,btn2,MyFragmentLayout的onTouchEvent全部返回false。
同时在MainActivity加了onTouchEvent方法:

public class MainActivity extends Activity {
    private MyFrameLayout frameLayout;
    private Button btn1;
    private Btn2 btn2;
    private Btn3 btn3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        frameLayout = (MyFrameLayout) findViewById(R.id.framelayout);
        btn1 = (Button) findViewById(R.id.btn1);
        btn2 = (Btn2) findViewById(R.id.btn2);

        btn2.bringToFront();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                System.out.println("Activity---onTouchEvent---DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                System.out.println("Activity---onTouchEvent---MOVE");
                break;
            case MotionEvent.ACTION_UP:
                System.out.println("Activity---onTouchEvent---UP");
                break;
            default:
                break;
        }
        return super.onTouchEvent(event);
    }
}

同样点击一下btn2和btn3重合处,打印log
这里写图片描述
哈哈,最后都不处理,会把事件交给activity进行处理。先交给btn2,btn2的onTouchEvent返回false说我不消费,然后接着到了btn3,btn3也返回false说我也不消费,返回到了MyFrameLayout,结果他也返回了fasle,最后就交给了activity处理了。

嗯,到此结束了,以上是我对view事件理解。(view事件分析(上)这里写链接内容

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值