ViewGroup事件分发机制源码解析(一)View篇

本篇基于Android9.0的源码进行分析View的事件分发机制,源码见https://github.com/Oaman/Forward。

View事件处理机制源码分析

布局文件如下,根View是一个LinearLayout,内部放了两个view分别是Button和ImageView.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/rootView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/buttonView"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:text="button" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_marginTop="10dp"
        android:background="@color/colorPrimaryDark"
        android:text="image" />

</LinearLayout>

我们在MainActivity中对此Button设置点击监听和Touch事件

		button.setOnClickListener(new View.OnClickListener() {
     
            @Override
            public void onClick(View v) {
     
                Log.e("normally", "button onClick");
            }
        });

        button.setOnTouchListener(new View.OnTouchListener() {
     
            @Override
            public boolean onTouch(View v, MotionEvent event) {
     
                Log.e("normally", "button onTouch: " + getAction(event.getAction()) + "--clickable:" + button.isClickable());

                return false;
            }
        });

运行后点击按钮打印如下结果

/com.oman.dispatch E/normally: button onTouch:  DOWN--clickable:true
/com.oman.dispatch E/normally: button onTouch:  MOVE--clickable:true
/com.oman.dispatch E/normally: button onTouch:  UP--clickable:true
/com.oman.dispatch E/normally: button onClick

这里可以看到首先执行的是onTouch方法,然后执行onClick方法,我点击的时候故意滑动了一下,打印了一个MOVE的动作,这样DOWN,MOVE,UP三个动作,每个动作都打印了。

大家可能会发现,这个setOnTouchListener方法有一个返回值,这里是默认的false,如果我们将返回值改为true试一试呢,看下打印结果:

/com.oman.dispatch E/normally: button onTouch:  DOWN--clickable:true
/com.oman.dispatch E/normally: button onTouch:  MOVE--clickable:true
/com.oman.dispatch E/normally: button onTouch:  UP--clickable:true

会发现只执行了onTouch方法,没有执行onClick方法,这是为什么呢,下面我们从源码角度看一下为什么?

首先需要知道,当我们触摸任何一个控件的时候,会调用这个控件的dispatchTouchEvent方法,那么我们看一下这个方法的源码,Button继承自TextView,TextView继承自View,我们在View中找到了这个方法,看dispatchTouchEvent方法的源码

	public boolean dispatchTouchEvent(MotionEvent event) {
     
        ...
        boolean result = false;
        ...
        if (onFilterTouchEventForSecurity(event)) {
     
            ...
            ListenerInfo li = mListenerInfo;
            // 1 
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
     
                result = true;
            }
			// 2
            if (!result && onTouchEvent(event)) {
     
                result = true;
            }
        }
        // 3
        return result;
    }

在注释1处有四个条件,当这四个条件都为true时候dispatchTouchEvent返回true,代表事件已经处理了,否则就会执行注释2处的onTouchEvent方法,下面我们来看这四个条件

  • 第一个和第二个是mOnTouchListener不为空,其实在我们设置setOnTouchListener时候就已经设置了,这里ListenerInfo其实是持有多个listener的对象

    public void setOnTouchListener(OnTouchListener l) {
           
        getListenerInfo().mOnTouchListener = l;
    }
    
  • 第三个条件是(mViewFlags & ENABLE_MASK)== ENABLE, 判断当前控件是否是可以点击的,Button默认都是可以点击的,所以此处为true。

  • 第四个条件li.mOnTouchListener.onTouch(this,event)方法,其实这个地方就是回调我们注册的onTouch事件的onTouch方法了,如果返回值为true就整体返回true,如果返回值为false就会继续执行onTouchEvent(event)方法。

说到这里我们就会明白之前打印的结果,首先执行dispatchTouchEvent里面的onTouch方法,如果onTouch的返回值为false的话会执行onTouchEvent就会打印onClick方法,说明onClick方法是在onTouchEvent方法中执行的;如果返回了true的话后面的onTouchEvent方法就不会执行了,就不会执行onClick了。


View.onTouchEvent

说到了onTouchEvent方法,那么我们就要看看它的源码:

	public boolean onTouchEvent(MotionEvent event) {
     
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();
		...
		// 1
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
     
            switch (action) {
     
                case MotionEvent.ACTION_UP:
                   
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
     
                        
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
     
                            // 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)) {
     
                                	// 2
                                    performClickInternal();
                                }
                            }
                        }

                        ...
                    }
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_DOWN:
                    ...
                    break;

                case MotionEvent.ACTION_CANCEL:
                    ...
                    break;

                case MotionEvent.ACTION_MOVE:
                    ...
                    break;
            }

            return true;
        }

        return false;
    }

在注释1处,只要控件是可点击的就会进入到switch判断中去,当抬起手指时候的ACTION_UP事件,经过各种判断后会进入到注释2处的performClickInternal()方法中,我们看里面的源码:

	private boolean performClickInternal() {
     
        return performClick();
    }
	
	public boolean performClick() {
     
        ...
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
     
            playSoundEffect(SoundEffectConstants.CLICK);
            // 1
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
     
            result = false;
        }
        return result;
    }

上面最终执行到了注释1处,只要mOnClickListener不为null的话就会执行onClick方法,那么mOnClickListener是在哪里赋值的呢,其实当我们设置onClick监听的时候就自动给赋值了,代码如下:

	public void setOnClickListener(@Nullable OnClickListener l) {
     
        if (!isClickable()) {
     
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

现在看来是不是整个思路很清晰了,这里有一点需要注意,就是如果控件是可点击状态,执行dispatchTouchEvent方法,那么在进入onTouchEvent方法时候不管之前onTouch返回值如何,最后返回值都为true,不信的话,看上面的onTouchEvent源码,进入switch之后最后都是返回的true。


ImageView的测试情况

那么如果我们将Button换为ImageView呢(此处仅仅设置setOnTouchListener,没有设置setOnClickListener),打印一下结果大家看一下

	imageView.setOnTouchListener(new View.OnTouchListener() {
     
        @Override
        public boolean onTouch(View v, MotionEvent event) {
     
            Log.e("normally", "image onTouch: " + getAction(event.getAction()) + "--clickable:" + imageView.isClickable());
            return false;
        }
    });

打印结果如下:

/com.oman.dispatch E/normally: image onTouch:  DOWN--clickable:false

看到仅仅执行了ACTION_DOWN动作,这是因为ImageView是默认不可点击的,所以在执行onTouchEvent方法时候不会进入到switch判断中,就仅仅执行了一个按下动作,最终导致事件imageView不处理,交由父控件处理。如果将onTouch的返回值改为true的话就会打印如下结果:

/com.oman.dispatch E/normally: image onTouch:  DOWN--clickable:false
/com.oman.dispatch E/normally: image onTouch:  MOVE--clickable:false
/com.oman.dispatch E/normally: image onTouch:  UP--clickable:false

这是为什么呢,因为有一个很重要的知识点,就是Touch事件的层级传递,在dispatchTouchEvent方法执行的时候,只有前一个action返回值为true时候,才会触发下一个action。

如果我们给ImageView同时注册onClick和onTouch监听(返回值为false)的话,

	imageView.setOnTouchListener(new View.OnTouchListener() {
     
        @Override
        public boolean onTouch(View v, MotionEvent event) {
     
            Log.e("normally", "image onTouch: " + getAction(event.getAction()) + "--clickable:" + imageView.isClickable());
            return false;
        }
        });
    imageView.setOnClickListener(new View.OnClickListener() {
     
        @Override
        public void onClick(View v) {
     
            Log.e("normally", "image onClick");
        }
    });

点击imageView打印如下:

/com.oman.dispatch E/normally: image onTouch:  DOWN--clickable:true
/com.oman.dispatch E/normally: image onTouch:  MOVE--clickable:true
/com.oman.dispatch E/normally: image onTouch:  UP--clickable:true
/com.oman.dispatch E/normally: image onClick

如果onTouch返回值为true的话,打印结果如下:

/com.oman.dispatch E/normally: image onTouch:  DOWN--clickable:true
/com.oman.dispatch E/normally: image onTouch:  MOVE--clickable:true
/com.oman.dispatch E/normally: image onTouch:  UP--clickable:true

这是因为你设置了setOnClickListener方法,源码如下:

	public void setOnClickListener(@Nullable OnClickListener l) {
     
        if (!isClickable()) {
     
            // 1
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

源码中注释1处看到将ImageView设置为CLICKABLE状态,就算你onTouch返回false但是可以进入onTouchEvent的switch中了,就打印出你看到的结果了;当你设置onTouch返回值为true时候就不会执行onTouchEvent方法,所以就没有打印onClick方法。

小结

以上是View的事件分发和处理,下一篇将分析ViewGroup的事件分发和滑动冲突解决,点击ViewGroup事件分发和滑动冲突查看。

你可能感兴趣的:(源码分析,事件分发机制源码解析)