Anroid View事件响应机制和ViewGroup的事件响应分发机制

发布时间:2020-07-30 21:06:23 作者:屠夫章哥
来源:网络 阅读:603

 

注:低版本的源码内容比高版本的源码简单,分析起来方便,但是高版本源码更为严密。

 

 

View的事件响应机制

涉及2个方法dispatchTouchEventonTouchEvent

 

1.ViewdispatchTouchEvent方法(事件传递到ViewView的这个方法就自动执行。)

dispatchTouchEvent返回true,响应事件;返回false,不响应事件。

public boolean dispatchTouchEvent(MotionEvent event) {

       ...

        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;

            }    //等价于return onTouchEvent(event)

 

        }

 


        ...

 

        return false;       //(如果View没有setOnTouchListener,默认dispatchTouchEvent

   事件就是返回false)

}

 

 

----------------------------------------------------------------------------------------------------------------------

上述源码中的ListenerInfoView类中的一个静态成员类,里面封装了各种事件类型的监听

XxxListener的变量,包括private OnTouchListener mOnTouchListener;

 

View这个类中有ListenerInfo mListenerInfo;这个类型的成员变量

mListenerInfo是通过下面这个方法来返回的,从方法可以看出返回值肯定不为空。

 ListenerInfo getListenerInfo() {

        if (mListenerInfo != null) {

            return mListenerInfo;

        }

        mListenerInfo = new ListenerInfo();

        return mListenerInfo;

 }

 结论1所以ViewdispatchTouchEvent方法中的if判断中li(也就是mListenerInfo)!=null

 

 

----------------------------------------------------------------------------------------------------------------------

再看ViewsetOnTouchListener这个方法,只要这个方法被调用了,参数不为空,那么

mListenerInfo.mOnTouchListener就不为空。

 public void setOnTouchListener(OnTouchListener l) {

        getListenerInfo().mOnTouchListener = l;

}

结论2所以ViewdispatchTouchEvent方法中的if判断中li.mOnTouchListener != null

 

所以

if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event))

这个条件组只是和li.mOnTouchListener.onTouch(this, event)这个回调方法有关

如果返回true的话,ImageViewdispatchTouchEvent方法也返回true

如果返回false的话,会继续进行下面的一个if判断,也就是onTouchEvent方法的判断。

 if (onTouchEvent(event)) {

     return true; 

 }

 上面这3行代码等价于return onTouchEvent(event),其实2.3.3的源码就是这么写的。

 

###############################################################################

所以对于dispatchTouchEvent()方法,如果直接继承自View的控件

1.没有调用setOnTouchListener()设置监听者,那么

  li.mOnTouchListener == nulldispatchTouchEvent()方法默认就会返回false,这时

  onTouchEvent 方法和dispatchTouchEvent()方法就没有任何的关联了。

2.调用setOnTouchListener()设置监听者,那么li.mOnTouchListener = null

  dispatchTouchEvent()方法就会判断li.mOnTouchListener.onTouch(this, event), 即监听者

  的onTouch回调方法(表明用户的意图)的返回值

  ***onTouch 回调方法返回truedispatchTouchEvent()方法就会返回true

  ***onTouch 回调方法返回falsedispatchTouchEvent()方法就会调用onTouchEvent处理

     事件,即把事件转交给onTouchEvent方法进行处理

###############################################################################

 

 

2.ViewonTouchEvent方法

public boolean onTouchEvent(MotionEvent event) {

...

if (((viewFlags & CLICKABLE) == CLICKABLE ||

      (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {

...

 switch (event.getAction()) {

                case MotionEvent.ACTION_UP:

...

if (!post(mPerformClick)) {

                                    performClick();  /*

底层实现:li.mOnClickListener.onClick(this);

View的点击事件真正是在UP事件之后执行的。

*/

}

...

return true;

}

return false;

     }

---------------------------------------------------------------------

ViewonTouchEvent的返回值,取决于View是否可点击。

 这就解释了为什么当ImageViewButton的监听事件的OnTouch事件都是返回false时,ImageView只能响应按下事件,而Button能响应所有事件的原因了:因为ImageView默认的clickable属性为false,而Buttonclickable属性为true

 

想让ImageView监听事件的onTouch返回false也能响应所有的事件,有2种方式

1种:直接将ImageViewclickable属性设置为true

2种:为ImageView添加点击事件,通过查看下面的源码可以看出,点击事件会将View

    的clickable属性设置为true

public void setOnClickListener(OnClickListener l) {

        if (!isClickable()) {

            setClickable(true);

        }

        getListenerInfo().mOnClickListener = l;}

 假如Button添加了点击事件,如果去屏蔽它的点击事件呢?

在添加点击事件的代码后面设置onclickable属性为false

onTouchListener监听的onTouch方法里返回true。  

原理:因为View的点击事件的本质是由onTouchEvent方法中的,performClick

 这个方法所执行的,不让View执行onTouchEventclickablefalse

 即不会执行点击事件。

 由此也可以看出onTouch事件和onClick事件不是一回事   

 

 

 

 

 

 

ViewGroup的事件响应和传递机制(以2.3.3的源码来分析)

涉及3个方法dispatchTouchEventonInterceptTouchEventonTouchEvent

 

ViewGroup的事件传递,是伴随着递归算法查找坐标落在那一个控件的范围,由父控件

向子控件,由外到内。

 

Anroid View事件响应机制和ViewGroup的事件响应分发机制 

 @Override

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;  //矩形类 mTempRect = new Rect();

 

        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

 

       

 if (action == MotionEvent.ACTION_DOWN) {

           ...

            // If we're disallowing intercept or if we're allowing and we didn't intercept

//如果onInterceptTouchEvent(ev)没有拦截事件

//只有自己先不拦截,才有必要去判断有没有子View去响应这个事件。

            if (disallowIntercept || !onInterceptTouchEvent(ev)) {

                // reset this event's action (just to protect ourselves)

                ev.setAction(MotionEvent.ACTION_DOWN);

                // We know we want to dispatch the event down, find a child

                // who can handle it, start with the front-most child.

                final int scrolledXInt = (int) scrolledXFloat;

                final int scrolledYInt = (int) scrolledYFloat;

                final View[] children = mChildren;

                final int count = mChildrenCount;

 

                for (int i = count - 1; i >= 0; i--) {

                    final View child = children[i];

                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE

                            || child.getAnimation() != null) {  //child

可见或有动画时

                        child.getHitRect(frame);    //测量子控件的矩形参数

   //判断事件的坐标有没有包含在子控件的矩形范围之内 

                        if (frame.contains(scrolledXInt, scrolledYInt)) {

                           //计算事件在子View中的坐标

                            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))  {

                                // Event handled, we have a target now.!!!!

  //View如果处理了事件,就把这个View作为事件的目

   标

                                mMotionTarget = child;

                           return true;

                            }

                            // The event didn't get handled, try the next view.

                            // Don't reset the event's location, it's not

                            // necessary here.

                        }

                    }

//View能够成为mMotionTarget的前提是事件坐标落在它的

矩形范围之内,并且它响应处理了这个事件。

                }

            }

        }  Action_downif结束

 

 

 

       ...

 

       

 // The event wasn't an ACTION_DOWN, dispatch it to our target if

     we have one.(**没有子View响应**)

        final View target = mMotionTarget;

        if (target == null) {

            // We don't have a target, this means we're handling the

            // event as a regular view.(**如果没有子View响应这个事件,这个

事件就会当作一般的View的事件来处理,即ViewGroup执行像View

一样的去执行事件分发**)

            ev.setLocation(xf, yf);

            if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {

                ev.setAction(MotionEvent.ACTION_CANCEL);

                mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;

            }

            return super.dispatchTouchEvent(ev);

        }

 

 

 

      

  // if have a target, see if we're allowed to and want to intercept its events**有子View响应**

        if (!disallowIntercept && onInterceptTouchEvent(ev)) {

            final float xc = scrolledXFloat - (float) target.mLeft;

            final float yc = scrolledYFloat - (float) target.mTop;

            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;

            ev.setAction(MotionEvent.ACTION_CANCEL);

            ev.setLocation(xc, yc);

            if (!target.dispatchTouchEvent(ev)) {

                // target didn't handle ACTION_CANCEL. not much we can do

                // but they should have.

            }

            // clear the target

            mMotionTarget = null;

            // Don't dispatch this event to our own view, because we already

            // saw it when intercepting; we just want to give the following

            // event to the normal onTouchEvent().

            return true;

        }

 

 

 

       

 

       

 // finally offset the event to the target's coordinate system and

        // dispatch the event.**把事件转交给子View去处理**

        final float xc = scrolledXFloat - (float) target.mLeft;

        final float yc = scrolledYFloat - (float) target.mTop;

        ev.setLocation(xc, yc);

 

        if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {

            ev.setAction(MotionEvent.ACTION_CANCEL);

            target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;

            mMotionTarget = null;

        }

 

        return target.dispatchTouchEvent(ev);

 

 

}


推荐阅读:
  1. Android编程下Touch事件的分发和消费机制理解是怎样的
  2. 随我一起慢慢揭开Andoird里事件分发机制的神秘面纱

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

android经验 gr roi

上一篇:Centos8上搭建dhcp服务器

下一篇:JVM监控命令

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》