android事件分发

在android的自定义控件中,如果涉及到滑动、点击等操作就会遇到事件分发机制,或者在将listview添加到swipView中后发现listview滑动失效了,这就是因为事件分发没有处理好。在这个问题上我困扰了很久,并试图通过阅读源码和查阅别人blog试图理解,但是并没有一片透彻的文章让人醍醐灌顶,所有我视图写这篇文章。

view和viewgroup认识

Java.lang.Object
↳ android.view.View
↳ android.view.ViewGroup
↳ android.widget.LinearLayout

首先最基础的就是知道view和viewgroip都是View的子类,关于这两个控件的的区别与认识可以参考我的另一篇blog:http://blog.csdn.net/mr_zhaojy/article/details/52918984
简单来说就是viewgrop的view的子类,所有view中有的方法viewgroup中也有,最大区别在于viewgrop有很多的子view和自己的布局方法。
感觉上就是有子view的view就是viewgroup,没有子view的view就只是个view。

事件分发过程

事件首先是被activity捕获,然后调用自己rootLayout的dispatchTouchEvent,由此开始是事件的传递之旅。
这里写图片描述
假如mview是我们的activity,那么1就是activity的rootLayout,当点击屏幕产生触屏事件,down这个事件的分发流程是1/2/5/6/7/3/4,我们来屡一下流程。

  1. down事件产生,activity调用1的dispatchTouchEvent
  2. 1的dispatchTouchEvent分别调用2的dispatchTouchEvent
  3. 2的dispatchTouchEvent首先被调用,然后依次调用5/6/7的dispatchTouchEvent,如果567有子view继续递归向下遍历
  4. 2节点dispatchTouchEvent遍历完,相同方法遍历3和4
  5. 至此所有view都执行了一遍dispatchTouchEvent

    这里声明两个名词,节点和叶子节点,学过二叉树的一看就明白了。
    节点就是图片整的每个view,叶子节点就是没有子view的view;这里的节点就是1到7,叶子节点就是56734

ok,下面继续。如果我的rootLayout中有一个子布局fatherLayout,fatherLayout中有个子控件sonView1、sonView2和sonView3,我们点击sonView2。流程是怎样的呢?

  1. rootLaout开始执行dispatchTouchEvent
  2. fatherLayout开始执行dispatchTouchEvent,依次调用子view的dispatchTouchEvent
  3. 【sonView1】开始执行dispatchTouchEvent,判断事件不是我的菜,返回false,fatherLayout继续分发
    【sonView2】开始执行dispatchTouchEvent,判断事件是我的菜,返回true,fatherLayout不在向下分发
    【sonView3】不会执行dispatchTouchEvent
  4. fatherLayout的dispatchTouchEvent在调用【sonView2】的dispatchTouchEvent时返回true,所有会有个View mTarget的变量指向sonView2,并且不在调用sonView3的dispatchTouchEvent。

    以上就是down事件的分发流程,经过down的分发形成一条隧道,依次是rootLayout->fatherLayout->sonView2,这三个view都是自己的局部变量mTarget保存。在接下来的move、up事件都将直接通过隧道传递到mTarget

三个函数

三个函数分别为dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent

首先看dispatchTouchEvent

View mTarget=null;//保存捕获Touch事件处理的View
public boolean dispatchTouchEvent(MotionEvent ev) {
    //....其他处理,在此不管
    if(ev.getAction()==KeyEvent.ACTION_DOWN){
        //每次Down事件,都置为Null
             if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
        mOnTouchListener.onTouch(this, event)) {  
    return true;  
}  

        if(!onInterceptTouchEvent()){
        mTarget=null;
        View[] views=getChildView();
        for(int i=0;i<views.length;i++){
            if(views[i].dispatchTouchEvent(ev))
                mTarget=views[i];
                return true;
        }
      }
    }
    //当子View没有捕获down事件时,ViewGroup自身处理。这里处理的Touch事件包含Down、Up和Move
    if(mTarget==null){
        return super.dispatchTouchEvent(ev);
    }
        //...其他处理,在此不管
        if(onInterceptTouchEvent()){

         //...其他处理,在此不管    
     }

    //这一步在Action_Down中是不会执行到的,只有Move和UP才会执行到。
    return mTarget.dispatchTouchEvent(ev);
}

分析:
1、如果view是个viewGroup,那么依次执行子view的dispatchTouchEvent,
1.1、如果某个子view返回true,函数返回true,且后面的子view和viewGroup的onTouchEvent函数不会得到执行。
1.2、如果所有子view都返回false,那么viewGroup后面的onTouchEvent等函数得到执行,也就是说子在判断完所有子view都不消费该事件后,就该轮到viewGroup自己调用onTouchEvent判断自己是否要消费这个事件。如果viewGroup不消费,则viewGroup的dispatchTouchEvent返回false。
2、如果view就是个view,他没有子view,则view的dispatchTouchEvent回直接执行自己的onTouchEvent,判断是否为自己的菜

onInterceptTouchEvent

这个函数一看就知道是拦截事件的,作用是在viewGroup遍历子view之前执行,来拦截事件,如果拦截成功,就直接返回true,子view将得不到分发。

onTouchEvent

在dispatchTouchEvent中可以看到,onTouch是在onTouchEvent之前执行的,使用setOnTouchListen设置onTouch,使用setOnclickedListen设置onTouchEvent中要调用的onClicked函数

public boolean onTouchEvent(MotionEvent event) {  
    final int viewFlags = mViewFlags;  
    if ((viewFlags & ENABLED_MASK) == DISABLED) {  
        // A disabled view that is clickable still consumes the touch  
        // events, it just doesn't respond to them.  
        return (((viewFlags & CLICKABLE) == CLICKABLE ||  
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  
    }  
    if (mTouchDelegate != null) {  
        if (mTouchDelegate.onTouchEvent(event)) {  
            return true;  
        }  
    }  
    if (((viewFlags & CLICKABLE) == CLICKABLE ||  
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
        switch (event.getAction()) {  
            case MotionEvent.ACTION_UP:  
                boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
                if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
                    // take focus if we don't have it already and we should in  
                    // touch mode.  
                    boolean focusTaken = false;  
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
                        focusTaken = requestFocus();  
                    }  
                    if (!mHasPerformedLongPress) {  
                        // This is a tap, so remove the longpress check  
                        removeLongPressCallback();  
                        // Only perform take click actions if we were in the pressed state  
                        if (!focusTaken) {  
                            // Use a Runnable and post this rather than calling  
                            // performClick directly. This lets other visual state  
                            // of the view update before click actions start.  
                            if (mPerformClick == null) {  
                                mPerformClick = new PerformClick();  
                            }  
                            if (!post(mPerformClick)) {  
                                performClick();  
                            }  
                        }  
                    }  
                    if (mUnsetPressedState == null) {  
                        mUnsetPressedState = new UnsetPressedState();  
                    }  
                    if (prepressed) {  
                        mPrivateFlags |= PRESSED;  
                        refreshDrawableState();  
                        postDelayed(mUnsetPressedState,  
                                ViewConfiguration.getPressedStateDuration());  
                    } else if (!post(mUnsetPressedState)) {  
                        // If the post failed, unpress right now  
                        mUnsetPressedState.run();  
                    }  
                    removeTapCallback();  
                }  
                break;  
            case MotionEvent.ACTION_DOWN:  
                if (mPendingCheckForTap == null) {  
                    mPendingCheckForTap = new CheckForTap();  
                }  
                mPrivateFlags |= PREPRESSED;  
                mHasPerformedLongPress = false;  
                postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
                break;  
            case MotionEvent.ACTION_CANCEL:  
                mPrivateFlags &= ~PRESSED;  
                refreshDrawableState();  
                removeTapCallback();  
                break;  
            case MotionEvent.ACTION_MOVE:  
                final int x = (int) event.getX();  
                final int y = (int) event.getY();  
                // Be lenient about moving outside of buttons  
                int slop = mTouchSlop;  
                if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
                        (y < 0 - slop) || (y >= getHeight() + slop)) {  
                    // Outside button  
                    removeTapCallback();  
                    if ((mPrivateFlags & PRESSED) != 0) {  
                        // Remove any future long press/tap checks  
                        removeLongPressCallback();  
                        // Need to switch from pressed to not pressed  
                        mPrivateFlags &= ~PRESSED;  
                        refreshDrawableState();  
                    }  
                }  
                break;  
        }  
        return true;  
    }  
    return false;  
}  

看着很复杂,核心逻辑是switch中的down分支调用performClick()函数,

public boolean performClick() {  
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
    if (mOnClickListener != null) {  
        playSoundEffect(SoundEffectConstants.CLICK);  
        mOnClickListener.onClick(this);  
        return true;  
    }  
    return false;  
}  
很明显是调用我们注册的onClick函数。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值