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

一直对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源码如下:

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

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

@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方法的返回值,

而我们返回的是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;
			}
		});

运行结果:

晚上继续

时间: 2024-10-06 21:44:05

android 从源码分析view事件分发机制的相关文章

Android View 事件分发机制源码详解(View篇)

前言 在Android View 事件分发机制源码详解(ViewGroup篇)一文中,主要对ViewGroup#dispatchTouchEvent的源码做了相应的解析,其中说到在ViewGroup把事件传递给子View的时候,会调用子View的dispatchTouchEvent,这时分两种情况,如果子View也是一个ViewGroup那么再执行同样的流程继续把事件分发下去,即调用ViewGroup#dispatchTouchEvent:如果子View只是单纯的一个View,那么调用的是Vie

[Android]Fragment源码分析(一) 构造

Fragment是Android3.0之后提供的api,被大家广泛所熟知的主要原因还是因为随即附带的ViewPager控件.虽然我并不喜欢用它,但是它确实是一个相对不错的控件.还是我的一贯作风,我将从源码上向大家展示什么是Fragment.我们先写一个简单的代码对Fragment有个直观的认识:(为了保证我们方便调试,我们可以直接使用V4提供的源码包) FragmentTransaction t = getSupportFragmentManager().beginTransaction();

Android:View事件分发机制

关于View事件分发机制的文章已经有很多了,推荐郭霖和鸿洋的两篇文章, http://blog.csdn.net/guolin_blog/article/details/9097463 http://blog.csdn.net/lmj623565791/article/details/38960443 结合他们写的,自己简单总结一下,可能只适用个人. 流程 只要你触摸到了任何一个控件,就一定会调用该控件的dispatchTouchEvent方法,源码如下(最新的API源码已经不是这样了,但是分析

android 从源码分析为什么Listview初次显示时没滚动却自动调用onScroll方法的原因

我们做Listview的分批加载时,需要为Listview调用setOnScrollListener(具体代码可见我上一篇博客) 可是,我们会发现,当运行程序时,listview明明没有滚动,那为什么系统会调用onScroll方法呢?(补充:此时onScrollStateChanged并不会调用) 我们先看setOnScrollListener源码: public void setOnScrollListener(OnScrollListener l) { mOnScrollListener =

[Android]Volley源码分析(二)Cache

Cache作为Volley最为核心的一部分,Volley花了重彩来实现它.本章我们顺着Volley的源码思路往下,来看下Volley对Cache的处理逻辑. 我们回想一下昨天的简单代码,我们的入口是从构造一个Request队列开始的,而我们并不直接调用new来构造,而是将控制权反转给Volley这个静态工厂来构造. com.android.volley.toolbox.Volley: public static RequestQueue newRequestQueue(Context conte

[Android]Volley源码分析(肆)应用

通过前面的讲述,相信你已经对Volley的原理有了一定了解.本章将举一些我们能在应用中直接用到的例子,第一个例子是 NetworkImageView类,其实NetworkImageView顾名思义就是将异步的操作封装在了控件本身,这种设计可以充分保留控件的移植性和维护性.NetworkImageView通过调用setImageUrl来指定具体的url: public void setImageUrl(String url, ImageLoader imageLoader) { mUrl = ur

Android IntentService 源码分析

IntentService简介: IntentService是一个通过Context.startService(Intent)启动可以处理异步请求的Service,使用时你只需要继承IntentService和重写其中的onHandleIntent(Intent)方法接收一个Intent对象,该服务会在异步任务完成时自动停止服务. 所有的请求的处理都在IntentService内部工作线程中完成,它们会顺序执行任务(但不会阻塞主线程的执行),某一时刻只能执行一个异步请求. IntnetServi

[Android] Volley源码分析(一)体系结构

Volley:google出的一个用于异步处理的框架.由于本身的易用性和良好的api,使得它能得以广泛的应用.我还是一如既往从源码的方向上来把控它.我们先通过一段简单的代码来了解Volley RequestQueue queue = Volley.newRequestQueue(this); ImageRequest imagerequest = new ImageRequest(url, new Response.Listener<Bitmap>(){ @Override public vo

Android 消息处理源码分析(2)

Android 消息处理源码分析(1)点击打开链接 继续接着分析剩下的类文件 Looper.java public final class Looper { final MessageQueue mQueue; //消息队列 final Thread mThread; //Looper联系的线程 public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { //