Android事件分发中的View

版权声明:出于感谢(如果有收获)或对知识的尊重,未经允许禁止转载 https://blog.csdn.net/bendan50/article/details/85991193

看了许多文章,自己也把源码跟着翻了一次,写了两篇文章,感觉事件分发到了View这边几乎是类似的,但是,这篇文章的重点不仅仅是事件分发,还有几个我一直不太了解的函数,所以,解决掉这几个函数才是本文的重点。

之前写的两篇文章分别是:

Android的事件分发笔记(结论+图+源码)

Android事件分发中的ViewGroup

需要解决的函数:

public boolean dispatchTouchEvent(MotionEvent event){...}
public boolean performClick() {...}
public boolean onTouchEvent(MotionEvent event) {...}
public interface OnTouchListener { boolean onTouch(View v, MotionEvent event);}
public interface OnClickListener {void onClick(View v);}

直接看dispatchTouchEvent()的源码,现在可以直接看重点,去掉那些繁文缛节。

    if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
onFilterTouchEventForSecurity之前已经提过,这段代码有三个重点需要关注下:ListenerInfo类和先调用的onTouchListener.onTouch()后调用的view.onTouchEvent()

ListenerInfo是View里面的一个内部类,定义了一系列的Listener

static class ListenerInfo {
        /**
         * Listener used to dispatch focus change events.
         * This field should be made private, so it is hidden from the SDK.
         * {@hide}
         */
        protected OnFocusChangeListener mOnFocusChangeListener;

        /**
         * Listeners for layout change events.
         */
        private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;

        protected OnScrollChangeListener mOnScrollChangeListener;


        /**
         * Listener used to dispatch click events.
         * This field should be made private, so it is hidden from the SDK.
         * {@hide}
         */
        public OnClickListener mOnClickListener;

        /**
         * Listener used to dispatch long click events.
         * This field should be made private, so it is hidden from the SDK.
         * {@hide}
         */
        protected OnLongClickListener mOnLongClickListener;

        private OnKeyListener mOnKeyListener;

        private OnTouchListener mOnTouchListener;

        private OnHoverListener mOnHoverListener;
}

接下来再来过下onTouchEvent()函数的实现,分段摘录源码:

    if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            // 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)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
        }

在return上面有一个注释:即使不可用的视图但是能点击(包括长按),依然会消费掉事件,只是不做什么响应处理罢了。

接下来涉及返回(包含return 语句)的是代理,即:如果有代理,便会执行代理的onTouchEvent()方法。所以忽略它继续看后面的源码,便是一个onTouchEvent()函数的核心:switch-case语句,对各类事件类型进行分别处理。

if (((viewFlags & CLICKABLE) == CLICKABLE ||
     (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
      (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
     switch (action) {
         case MotionEvent.ACTION_UP:
          ...
        case MotionEvent.ACTION_DOWN:
            ...
        case MotionEvent.ACTION_CANCEL:
            ...
        case MotionEvent.ACTION_MOVE:
            ...
    return true;
}
return false;

通过源码可知,If判断成立时(View可以点击或者长按之类的),那么View肯定会消费掉事件,并返回true;如果If判断不成立,则不会消费掉事件,返回false;

接下来我们重点,关注ACTION_UP类型的事件,为什么呢?因为里面涉及我之前感到困惑的performClick()函数。拉下源码:

case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_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.
                            setPressed(true, x, y);
                       }

                        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)) {
                                    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();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

首先会判断当前View是否是pressed状态,即按下状态,如果是按下状态就会触发performClick()方法。

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

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

判断是否有onClickListener的回调,有则调用。显然,performClick()的执行在要onClickListener的onClick()函数之前。


是时候下个结论了:

首先会执行dispatchTouchEvent(); 然后是onTouchListener中的onTouch()函数;接着是onTouchEvent()函数,其次是performClick()函数和onClickListener的onClick()函数。

程序验证:

第一步:自定义视图

public class CrshButton extends View{
    public CrshButton(Context context) {
        super(context);
    }

    public CrshButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CrshButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.e("crsh","dispatchTouchEvent - event = " + event.getAction());
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("crsh","onTouchEvent - event = " + event.getAction());
        return super.onTouchEvent(event);
    }

    @Override
    public boolean performClick() {
        Log.e("crsh","performClick()");
        return super.performClick();
    }
}

第二步:引入,并设置监听

crshBtn.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.e("crsh","setOnTouchListener -- onTouch event = " + event.getAction());
                return false;
            }
        });

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

第三步:打印Log结果

01-07 15:37:17.140 9811-9811/com.crsh.test.learnviewpager E/crsh: dispatchTouchEvent - event = 0
01-07 15:37:17.140 9811-9811/com.crsh.test.learnviewpager E/crsh: setOnTouchListener -- onTouch event = 0
01-07 15:37:17.140 9811-9811/com.crsh.test.learnviewpager E/crsh: onTouchEvent - event = 0
01-07 15:37:17.154 9811-9811/com.crsh.test.learnviewpager E/crsh: dispatchTouchEvent - event = 2
01-07 15:37:17.154 9811-9811/com.crsh.test.learnviewpager E/crsh: setOnTouchListener -- onTouch event = 2
01-07 15:37:17.154 9811-9811/com.crsh.test.learnviewpager E/crsh: onTouchEvent - event = 2
01-07 15:37:17.171 9811-9811/com.crsh.test.learnviewpager E/crsh: dispatchTouchEvent - event = 2
01-07 15:37:17.171 9811-9811/com.crsh.test.learnviewpager E/crsh: setOnTouchListener -- onTouch event = 2
01-07 15:37:17.171 9811-9811/com.crsh.test.learnviewpager E/crsh: onTouchEvent - event = 2

01-07 15:37:19.117 9811-9811/com.crsh.test.learnviewpager E/crsh: dispatchTouchEvent - event = 2
01-07 15:37:19.117 9811-9811/com.crsh.test.learnviewpager E/crsh: setOnTouchListener -- onTouch event = 2
01-07 15:37:19.118 9811-9811/com.crsh.test.learnviewpager E/crsh: onTouchEvent - event = 2
01-07 15:37:19.120 9811-9811/com.crsh.test.learnviewpager E/crsh: dispatchTouchEvent - event = 1
01-07 15:37:19.120 9811-9811/com.crsh.test.learnviewpager E/crsh: setOnTouchListener -- onTouch event = 1
01-07 15:37:19.120 9811-9811/com.crsh.test.learnviewpager E/crsh: onTouchEvent - event = 1
01-07 15:37:19.121 9811-9811/com.crsh.test.learnviewpager E/crsh: performClick()
01-07 15:37:19.121 9811-9811/com.crsh.test.learnviewpager E/crsh: setOnClickListener -- onClick

MotionEvent的值看一下:

/**
     * Constant for {@link #getActionMasked}: A pressed gesture has started, the
     * motion contains the initial starting location.
     * <p>
     * This is also a good time to check the button state to distinguish
     * secondary and tertiary button clicks and handle them appropriately.
     * Use {@link #getButtonState} to retrieve the button state.
     * </p>
     */
    public static final int ACTION_DOWN             = 0;

    /**
     * Constant for {@link #getActionMasked}: A pressed gesture has finished, the
     * motion contains the final release location as well as any intermediate
     * points since the last down or move event.
     */
    public static final int ACTION_UP               = 1;

    /**
     * Constant for {@link #getActionMasked}: A change has happened during a
     * press gesture (between {@link #ACTION_DOWN} and {@link #ACTION_UP}).
     * The motion contains the most recent point, as well as any intermediate
     * points since the last down or move event.
     */
    public static final int ACTION_MOVE             = 2;

所以,完全符合我们之前下的结论:

先影响Down事件,中间影响多次Move事件,最后是Up事件。当是Up事件时触发performClick()函数,又因为重写了onClickListener监听,所以回调onClick()函数。

完美!

猜你喜欢

转载自blog.csdn.net/bendan50/article/details/85991193
今日推荐