Android 浅析View的事件分发机制

        对于一个Android开发者来说View事件分发机制是不得不了解的.不了解事件分发机制的话很多自定义控件都无法写出来.今天我们一起来看看View的事件分发机制是如何实现的.

       首先来看个栗子:

自定义一个MyButton继承Button并重写dispatchTouchEvent和onTouchEvent方法

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");
                break;
             case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");
                break;
             case MotionEvent.ACTION_UP:
                Log.e(TAG, "dispatchTouchEvent ACTION_UP");
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "onTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "onTouchEvent ACTION_UP");
                break;
        }
        return super.onTouchEvent(event);
    }

xml里面调用 

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mFrameLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <event.test.hxy.com.testenent.MyButton
        android:id="@+id/my_bt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        android:layout_gravity="center"
        android:text="按钮"/>

</FrameLayout>
        MyButton mButton= (MyButton) findViewById(R.id.my_bt);

        mButton.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        Log.e(TAG, "onTouch ACTION_DOWN");
                        break;
                    case MotionEvent.ACTION_MOVE:
                        Log.e(TAG, "onTouch ACTION_MOVE");
                        break;
                    case MotionEvent.ACTION_UP:
                        Log.e(TAG, "onTouch ACTION_UP");
                        break;
                }
                return false;
            }
        });

        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e(TAG, "onClick");
            }
        });
        mButton.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                Log.e(TAG, "onLongClick");
                return false;
            }
        });

当我们只点击一下的时候日志打出

08-14 11:47:38.173 15712-15712/fun.hxy.com.testalg E/MyButton: dispatchTouchEvent ACTION_DOWN
08-14 11:47:38.173 15712-15712/fun.hxy.com.testalg E/MainActivity: onTouch ACTION_DOWN
08-14 11:47:38.174 15712-15712/fun.hxy.com.testalg E/MyButton: onTouchEvent ACTION_DOWN
08-14 11:47:38.199 15712-15712/fun.hxy.com.testalg E/MyButton: dispatchTouchEvent ACTION_UP
08-14 11:47:38.199 15712-15712/fun.hxy.com.testalg E/MainActivity: onTouch ACTION_UP
08-14 11:47:38.199 15712-15712/fun.hxy.com.testalg E/MyButton: onTouchEvent ACTION_UP
08-14 11:47:38.200 15712-15712/fun.hxy.com.testalg E/MainActivity: onClick

我们发现时间都是从dispatchTouchEvent开始的然后onTouch->onTouchEvent->onClick

为什么会这样呢?我们来看一下View的源码:

public boolean dispatchTouchEvent(MotionEvent event) {

1      ....

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


3            if (!result && onTouchEvent(event)) {
                result = true;
            }
       

        .....

        return result;
    }

在View的dispatchTouchEvent里面我们可以发现View先判断li.mOnTouchListener.onTouch(this, event)先执行了onTouch()事件在到第三行的onTouchEvent(),并且如果onTouch()的返回值返回了true的话就不会在往下执行onTouchEvent()方法了;

我们在看看onTouchEvent()的源码:

public boolean onTouchEvent(MotionEvent event) {
        
        ......

            switch (action) {
                case MotionEvent.ACTION_UP:
                     if (!focusTaken) {
                            
                      if (!post(mPerformClick)) {
                             performClick();
                         }
                     }
                      
                    break;
           
            }

            return true;
        }

        return false;
    }

我们发现onClick的方法是在onTouch()方法的up里面执行的.这就是为什么View点击的时候会先调用onTouch()  -> onTouchEvent() -> onClick(),面试的时候经常会问到

我们在来看一下长按事件:

现在我们长按button按钮

08-14 14:16:16.725 11084-11084/fun.hxy.com.testalg E/MyButton: dispatchTouchEvent ACTION_DOWN
08-14 14:16:16.726 11084-11084/fun.hxy.com.testalg E/MainActivity: onTouch ACTION_DOWN
08-14 14:16:16.726 11084-11084/fun.hxy.com.testalg E/MyButton: onTouchEvent ACTION_DOWN
08-14 14:16:16.875 11084-11084/fun.hxy.com.testalg E/MyButton: dispatchTouchEvent ACTION_MOVE
08-14 14:16:16.875 11084-11084/fun.hxy.com.testalg E/MainActivity: onTouch ACTION_MOVE
08-14 14:16:16.875 11084-11084/fun.hxy.com.testalg E/MyButton: onTouchEvent ACTION_MOVE
08-14 14:16:16.890 11084-11084/fun.hxy.com.testalg E/MyButton: dispatchTouchEvent ACTION_MOVE
08-14 14:16:16.890 11084-11084/fun.hxy.com.testalg E/MainActivity: onTouch ACTION_MOVE
08-14 14:16:16.890 11084-11084/fun.hxy.com.testalg E/MyButton: onTouchEvent ACTION_MOVE
08-14 14:16:16.906 11084-11084/fun.hxy.com.testalg E/MyButton: dispatchTouchEvent ACTION_MOVE

......

08-14 14:16:17.142 11084-11084/fun.hxy.com.testalg E/MainActivity: onLongClick
08-14 14:16:17.189 11084-11084/fun.hxy.com.testalg E/MyButton: dispatchTouchEvent ACTION_MOVE
08-14 14:16:17.190 11084-11084/fun.hxy.com.testalg E/MainActivity: onTouch ACTION_MOVE
08-14 14:16:17.190 11084-11084/fun.hxy.com.testalg E/MyButton: onTouchEvent ACTION_MOVE

.....

08-14 14:16:17.948 11084-11084/fun.hxy.com.testalg E/MyButton: dispatchTouchEvent ACTION_UP
08-14 14:16:17.949 11084-11084/fun.hxy.com.testalg E/MainActivity: onTouch ACTION_UP
08-14 14:16:17.949 11084-11084/fun.hxy.com.testalg E/MyButton: onTouchEvent ACTION_UP
08-14 14:16:17.954 11084-11084/fun.hxy.com.testalg E/MainActivity: onClick

我们发现长按的时候会执行ACTION_DOWN因为手指按住可能会有一点抖,会执行不等次的ACTION_MOVE,然后先执行onLongClick后面放手在执行onClick.

再次翻看onTouchEvent()的源码:

public boolean onTouchEvent(MotionEvent event) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                ......
                
                if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                    if (!post(mPerformClick)) {
                        performClick();
                    }
                }
                ......
                break;

            case MotionEvent.ACTION_DOWN:
                ......

                checkForLongClick(0, x, y);

                ......
                break;
        }

        return false;
    }

我们发现在ACTION_DOWN的时候,系统就会执行checkForLongClick方法

private void checkForLongClick(int delayOffset, float x, float y) {
        if (mPendingCheckForLongPress == null) {
                mPendingCheckForLongPress = new CheckForLongPress();
            }
            postDelayed(mPendingCheckForLongPress,
                    ViewConfiguration.getLongPressTimeout() - delayOffset);
        
    }

这个checkForLongClick里面执行了一个延迟操作延迟五百毫秒时候就会执行CheckForLongPress这个延迟事件

@Override
        public void run() {
            if (isPressed() && (mParent != null)
                    && mOriginalWindowAttachCount == mWindowAttachCount) {
                if (performLongClick(mX, mY)) {
                    mHasPerformedLongPress = true;
                }
            }
        }

这个方法里执行了

    public boolean performLongClick(float x, float y) {
        mLongClickX = x;
        mLongClickY = y;
        final boolean handled = performLongClick();
        mLongClickX = Float.NaN;
        mLongClickY = Float.NaN;
        return handled;
    }

我们可以看到performLongClick这个方法的返回值为boolean类型,所以当onLongClickListener方法返回值改为true的时候,mHasPerformedLongPress 被置为true,从上面又可以看到只有在mHasPerformedLongPress为false的时候performClick()方法才会执行.

好了,到这里View的事件机制差不多就看完一遍了,View的事件机制还是比较简单的,下一篇我将跟大家一起学习一下ViewGroup的事件机制.希望大家都能有所收获.

猜你喜欢

转载自blog.csdn.net/u013107751/article/details/51851719