码迷,mamicode.com
首页 > 移动开发 > 详细

android 从源码分析view事件分发机制

时间:2014-11-04 19:50:49      阅读:311      评论:0      收藏:0      [点我收藏+]

标签:android   style   blog   http   io   color   ar   os   java   

一直对View的事件分发机制不太明白,在项目开发中也遇到过,在网上也找到一些解决问题方法,但是其原理并不太了解,现在辞职了有时间,今天写写View的事件分发,结合android源码一起来学习下,如果讲的不对,往指出一起学习提高,言归正传。

新建一个android项目,里面只有一个activity,有一个button,我们给Button设置setOnClickListener(),setOnTouchListener(),通过log看看结果:

btnClick.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				Log.i("com.example.demo","button click ");
			}
		});

Button的Touch事件:

<span style="font-size:18px;">btnClick.setOnTouchListener(new OnTouchListener() {
			@Override
			public boolean onTouch(View v, MotionEvent event) {
				Log.i("com.example.demo","button touch "+event.getAction());
				return false;
			}
		});</span>

log:

com.example.demo(30220): button touch 0
com.example.demo(30220): button touch 2
com.example.demo(30220): button touch 2
com.example.demo(30220): button touch 2
com.example.demo(30220): button touch 2
com.example.demo(30220): button touch 2
com.example.demo(30220): button touch 1
com.example.demo(30220): button click 

可以看到onTouch优先于OnClick,而且OnTouch执行了好几次,因为OnTouch事件由DWON,MOVE,UP这三部分构成,所以onTouch执行了好几次,那么为什么执行的顺序是先onTouch后onClick呢?

观察onClick和onTouch会发现onTouch()方法有返回值,默认是返回false,如果我们改为返回true,会有什么不同,点击打印log看看:

com.example.demo(3280): button touch 0
com.example.demo(3280): button touch 2
com.example.demo(3280): button touch 2
com.example.demo(3280): button touch 2
com.example.demo(3280): button touch 2
com.example.demo(3280): button touch 1

通过log结果发现Onclick事件没有执行,我们可以理解onTouch返回true时,Button的事件被消费了,就相当于把view的事件拦截了就不会再继续向下传递,因此OnClick事件没有执行,

首先知道一点,只要你触摸到了界面上的任何一个控件,就一定会调用该控件的dispatchTouchEvent方法。这个方法优先于onTouch和onClick先执行,当我们去点击按钮的时候,就会去调用Button类里的dispatchTouchEvent方法,那我们去Button源码中找这个方法,Button源码很少,没有这个方法,Button源码如下:

bubuko.com,布布扣

虽然没有这个方法,但我们看出Button继承了TextView,那就到TextView中取找,但是在TextView中并没有找到dispatchTouchEvent()方法,那就只能找TextView的父类了,而TextView的父类就是View对象了,那在View源码中找dispatchTouchEvent()方法看看它执行逻辑:

bubuko.com,布布扣

我们首先翻译下这个方法的说明:

@param event The motion event to be dispatched,事件动作事件派遣

@return True if the event was handled by the view, false otherwise.如果这个事件被处理了就返回true,否则会返回false,

现在看下dispatchTouchEvent()方法里的代码,看源码得有个方法,不是所有的代码都要看懂,

在dispatchTouchEvent()方法中重点是看

 if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                    mOnTouchListener.onTouch(this, event)) {
                return true;
            }

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


首先看第一个if语句,
mOnTouchListener变量在什么时候初始化呢?我们追踪下,发现它的初始化时在
<pre name="code" class="java"> public void setOnTouchListener(OnTouchListener l) {
        mOnTouchListener = l;
    }

因此这个是我们在setOntouchListener的时候,mOnTouchListener就可以赋值了,因此这个变量不会为null,

第二个条件(mViewFlags & ENABLED_MASK) == ENABLED是判断当前点击的控件是否是enable的,按钮默认都是enable的,因此这个条件恒定为true

if条件的第三个条件mOnTouchListener.onTouch(this, event)我们点击onTouch()方法里发现:

 public interface OnTouchListener {
        /**
         * Called when a touch event is dispatched to a view. This allows listeners to
         * get a chance to respond before the target view.
         *
         * @param v The view the touch event has been dispatched to.
         * @param event The MotionEvent object containing full information about
         *        the event.
         * @return True if the listener has consumed the event, false otherwise.
         */
        boolean onTouch(View v, MotionEvent event);
    }

发现它是一个接口中的方法,用于回调的,其实就是我们设置onTouchListener方法的返回值,

bubuko.com,布布扣

而我们返回的是true,因此这个if条件判断返回的是true,那么就不会执行下面的语句了

 if (onTouchEvent(event)) {
                return true;
            }
我们看看onTouchEvent(event)方法的逻辑:源码如下:

 public boolean onTouchEvent(MotionEvent event) {
        final int viewFlags = mViewFlags;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
                mPrivateFlags &= ~PRESSED;
                refreshDrawableState();
            }
            // 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 (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            mPrivateFlags |= PRESSED;
                            refreshDrawableState();
                       }

                        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) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }
                        removeTapCallback();
                    }
                    break;

                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        mPrivateFlags |= PRESSED;
                        refreshDrawableState();
                        checkForLongClick(0);
                    }
                    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
                    if (!pointInView(x, y, mTouchSlop)) {
                        // 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;
    }
我们发现这方法里面的源码太多了,但是onTouchEvent()方法其实我们只要看case MotionEvent.ACTION_UP里面的代码,因为我们手触摸到最后倒是要执行这里,在经过种种判断之后,代码会走到这里:

 if (!post(mPerformClick)) {
                                    performClick();
      }
然后执行performClick()方法,performClick方法里面的代码:

 public boolean performClick() {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        if (mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            mOnClickListener.onClick(this);
            return true;
        }

        return false;
    }
首先看if条件,mOnClickListener这个变量就是我们点击的时候设置的,因此不会为null,然后我们看一个重要的方法,也是回调方法,

 mOnClickListener.onClick(this);

这就是设置view的点击事件,通过源码我们现在应该明白了最初我们设置onClick和onTouch事件的传递顺序,

总结:

1:如果view对象setOnTouchListener方法返回true,那么view对象就不会执行click事件,如果setTouchListener设置为false,view才会执行click事件,

还有一个重要的知识,我们知道touch事件由DWON,MOVE,UP组成,如下:

btnClick.setOnTouchListener(new OnTouchListener() {
			@Override
			public boolean onTouch(View v, MotionEvent event) {
				if(event.getAction()==MotionEvent.ACTION_DOWN){
					Log.i("com.example.demo","ACTION_DOWN");
					return false;
				}else if(event.getAction()==MotionEvent.ACTION_MOVE){
					Log.i("com.example.demo","ACTION_MOVE");	
				}else{
					Log.i("com.example.demo","ACTION_UP");
				}
				return false;
			}
		});
运行结果:

晚上继续












android 从源码分析view事件分发机制

标签:android   style   blog   http   io   color   ar   os   java   

原文地址:http://blog.csdn.net/coderinchina/article/details/40787195

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!