View的事件分发机制解析

引言

Android事件构成

在Android中,事件主要包含点按、长按、拖拽、滑动等,点按又包含单击和双击,另外还包含单指操作和多指操作。全部这些都构成了Android中的事件响应。总的来说。全部的事件都由例如以下三个部分作为基础:

  • 按下(ACTION_DOWN)
  • 移动(ACTION_MOVE)
  • 抬起(ACTION_UP)

全部的操作事件首先必须运行的是按下操作(ACTION_DOWN)。之后全部的操作都是以按下操作作为前提,当按下操作完毕后。接下来可能是一段移动(ACTION_MOVE)然后抬起(ACTION_UP)。或者是按下操作运行完毕后没有移动就直接抬起。这一系列的动作在Android中都能够进行控制。

这些操作事件都发生在我们手机的触摸屏上面,而我们手机上响应我们各种操作事件的就是各种各样的视图组件也就是View,在Android中,全部的视图都继承于View,另外通过各种布局组件(ViewGroup)来对View进行布局,ViewGroup也继承于View。全部的UI控件比如Button、TextView都是继承于View,而全部的布局控件比如RelativeLayout、容器控件比如ListView都是继承于ViewGroup。

所以。我们的事件操作主要就是发生在ViewViewGroup之间。

事件分发的概念

所谓点击事件的事件分发,就是当一个MotionEvent产生了以后,系统须要把这个事件传递给一个详细的View(ViewGroup也继承于View),这个传递的过程就叫做分发过程,这个点击事件的分发过程须要三个非常重要的方法来共同完毕:disPatchTouchEvent、onInterceptTouchEvent、onTouchEvent

  • public boolean disPatchTouchEvent(MotionEvent ev)

    用来进行事件的分发,假设事件能够传递给当前的View,那么此方法一定会被调用,Android中全部的点击事件都必须经过这种方法的分发。然后决定是自身消费当前事件还是继续往下分发给子控件处理。返回true表示不继续分发。事件被消费了,返回false则继续往下分发,假设是ViewGroup则分发给onInterceptTouchEvent进行推断是否拦截该事件,这种方法的返回结果受到当前ViewonTouchEvent和下级ViewdisPatchTouchEvent方法的影响,返回结果表示是否消耗当前事件。


  • public boolean onTouchEvent(MotionEvent ev)

    diaPatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,假设不消耗,则在同一个事件序列其中,当前View无法再次接收到事件。


  • public boolean onInterceptTouchEvent(MotionEvent ev)

    是ViewGroup中才有的方法,View中没有,它的作用是负责事件的拦截,返回true的时候表示拦截当前事件,不继续往下分发,交给自身的onTouchEvent进行处理。返回false则不拦截,继续往下传。

    这是ViewGroup特有的方法。由于ViewGroup中可能还有子View,而在Android中View中是不能再包含子View的(IOS能够),在上述方法内部被调用,假设当前View拦截了某个事件,那么同一个事件序列中(指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,中间含有不定的ACTION_MOVE事件,终于以ACTION_UP事件结束)此方法不会被再次调用,返回结果表示是否拦截当前事件。

这三个方法能够用例如以下伪代码表示:

public boolean disPathchTouchEvent(MotionEvent ev){
//consume指代点击事件是否被消耗
     boolean consume=false;
     //表示当前父布局要拦截该事件
     if(onInterceptTouchEvent(MotionEvent ev)){
              consume=onTouchEvent(ev);
     }else{
     //传递给子元素去处理
   child.disPatchTouchEvent(ev);
   }
   return consume;
}

分析View的事件分发机制

为了简单起见我们先从View的事件分发机制開始分析。然后在分析ViewGroup的,首先我们建一个简单的项目。这个项目里仅仅有一个Button,而且我们给这个Button设置点击事件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
 >

    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="100dp"
        android:text="Click me" />

</RelativeLayout>
package com.example.testbtn;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.Button;

public class MainActivity extends Activity implements OnClickListener,OnTouchListener{

    private Button btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn=(Button) findViewById(R.id.btn);
        btn.setOnClickListener(this);
        btn.setOnTouchListener(this);
    }
    @Override
    public void onClick(View v) {
        Log.d("TAG", "OnClick");
    }
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        Log.d("TAG", "onTouch"+event.getAction());
        return false;
    }

}

界面是这样的:

运行这个程序。点击Button。查看Log打印输出的信息:

(这里onTouch0代表的是ACTION_DOWN,onTouch1代表的是ACTION_UP,onTouch2表示ACTION_MOVE,由于我们仅仅是稳稳的点击了一下Button所以不会有ACTION_MOVE的Log信息出现)

这样我们能够得到一个初步的结论:onTouch()方法是优先于onClick()运行的。然后我们会发现onTouch()方法有一个非常明显的和onClick()方法不同的地方的,那就是它有一个Boolean类型的返回值,假设我们把这个默觉得False的返回值改为True会怎么样呢:

发现了什么:onClick()方法没有被运行,这里我们把这样的现象叫做点击事件被onTouch()消费掉了。事件不会在继续向onClick()方法传递了,那么事件分发机制最基本的几条我们已经了解了,以下我们来分析产生这样的机制的根本原因。

View对点击事件的处理过程

首先我们给出一个结论:Android中全部的事件都必须经过disPatchTouchEvent(MotionEvent ev)这种方法的分发,然后决定是自身消费当前事件还是继续往下分发给子控件处理,那么我们就来看看这个disPatchTouchEvent(MotionEvent ev)究竟干了什么。

    public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {
            // We don‘t have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }

        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }

        if (onFilterTouchEventForSecurity(event)) {
            //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;
            }
        }

        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }

        // Clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an ACTION_DOWN but we didn‘t want the rest
        // of the gesture.
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

        return result;
    }

代码有点多。我们一步步来看:

  // If the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {
            // We don‘t have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }

最前面这一段就是推断当前事件能否获得焦点,假设不能获得焦点或者不存在一个View那我们就直接返回False跳出循环。接下来:

  if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }

设置一些标记和处理input与手势等传递。不用管,到这里:

   if (onFilterTouchEventForSecurity(event)) {
            //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;
            }
        }

这里if (onFilterTouchEventForSecurity(event))是用来推断View是否被遮住等。ListenerInfoView的静态内部类,专门用来定义一些XXXListener等方法的,到了重点:

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

非常长的一个推断。一个个来解释:第一个li肯定不为空。由于在这个If推断语句之前就new了一个li,第二个条件li.mOnTouchListener != null,怎么确定这个mOnTouchListener不为空呢?我们在View类里面发现了例如以下方法:

 /**
     * Register a callback to be invoked when a touch event is sent to this view.
     * @param l the touch listener to attach to this view
     */
    public void setOnTouchListener(OnTouchListener l) {
        getListenerInfo().mOnTouchListener = l;
    }

意味着仅仅要给控件注冊了onTouch事件这个mOnTouchListener就一定会被赋值,接下来(mViewFlags & ENABLED_MASK) == ENABLED是通过位与运算来推断这个View是否是ENABLED的,我们默认控件都是ENABLED的所以这一条也成立,最后一条li.mOnTouchListener.onTouch(this, event)是推断onTouch()的返回值是否为True,我们后面把默觉得False的返回值改成了True,所以这一整系列的推断都是True。那么这个disPatchTouchEvent(MotionEvent ev)方法直接就返回了True,那么接下来的代码都不会被运行,我们以下有这么一段代码:

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

最開始我们onTouch()方法的返回值是False的。那么

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

这里面的推断就不成立。result最開始的默认值也是false,那么此时假设

onTouchEvent(event)

返回值也是True,那么if (!result && onTouchEvent(event))这种方法推断条件成立,disPatchTouchEvent(MotionEvent ev)返回True,否则返回False。

这里我们得到两个结论:

  • OnTouchListener的优先级比onTouchEvent要高,联想到刚才的小Demo也能够得出onTouch方法优先于onClick()方法运行(onClick()是在onTouchEvent(event)方法中被运行的这个待会会说到)
  • 假设控件(View)的onTouch返回False或者mOnTouchListener为null(控件没有设置setOnTouchListener方法)或者控件不是ENABLE的情况下会调用onTouchEvent方法,此时dispatchTouchEvent方法的返回值与onTouchEvent的返回值一样。

那么接下来我们就分析dispatchTouchEvent方法里面onTouchEvent的实现,给出onTouchEvent的源代码:

 /**
     * Implement this method to handle touch screen motion events.
     * <p>
     * If this method is used to detect click actions, it is recommended that
     * the actions be performed by implementing and calling
     * {@link #performClick()}. This will ensure consistent system behavior,
     * including:
     * <ul>
     * <li>obeying click sound preferences
     * <li>dispatching OnClickListener calls
     * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
     * accessibility features are enabled
     * </ul>
     *
     * @param event The motion event.
     * @return True if the event was handled, false otherwise.
     */
    public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

        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);
        }

        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                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;

                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 |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_MOVE:
                    drawableHotspotChanged(x, y);

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();

                            setPressed(false);
                        }
                    }
                    break;
            }

            return true;
        }

        return false;
     }

代码还是非常多,我们依旧一段一段来分析,最前面的一段代码:

  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);
        }

依据前面的分析我们知道这一段代码是对当前View处于不可用状态的情况下的分析。通过凝视我们知道即使是一个不可用状态下的View依旧会消耗点击事件。仅仅是不会对这个点击事件作出响应罢了,另外通过观察这个return返回值,仅仅要这个ViewCLICKABLELONG_CLICKABLE或者CONTEXT_CLICKABLE有一个为True。那么返回值就是True。onTouchEvent方法会消耗当前事件。

看下一段代码:

 if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

这段代码的意思是假设View设置有代理。那么还会运行TouchDelegateonTouchEvent(event)方法,这个onTouchEvent(event)的工作机制看起来和OnTouchListener相似,这里不深入研究。

                     –《Android开发艺术探索》

以下看一下onTouchEvent中对点击事件的详细处理流程:

  if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                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;

                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 |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_MOVE:
                    drawableHotspotChanged(x, y);

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();

                            setPressed(false);
                        }
                    }
                    break;
            }

            return true;
        }

        return false;
    }

我们还是一行行来分解:

  if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
            //省略
            }
             return false;
    }

这个推断之前描写叙述过不再赘述,假设这个推断不成立直接跳到方法尾部返回False,假设推断成立则继续进入方法内部进行一个switch(event)的推断,这里ACTION_DOWN和ACTION_MOVE都仅仅是进行一些必要的设置与置位,我们主要看ACTION_UP:

 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;

首先推断了是否被按下 boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;接下来推断是不是能够获得焦点,同一时候尝试去获取焦点:

 boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

经过种种推断后我们看到这一行:

 if (!post(mPerformClick)) {
                                    performClick();
                                }

这是推断假设不是longPressed则通过post在UI Thread中运行一个PerformClick的Runnable。也就是performClick方法,这种方法的源代码例如以下:

/**
     * Call this view‘s OnClickListener, if it is defined.  Performs all normal
     * actions associated with clicking: reporting accessibility event, playing
     * a sound, etc.
     *
     * @return True there was an assigned OnClickListener that was called, false
     *         otherwise is returned.
     */
    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;
    }

我们发现了什么。那就是当ACTION_UP事件发生时,会触发performClick()方法,假设这个View设置了OnClickListener那么终于会运行到OnClickListener的回调方法onClick(),这也就验证了刚才所说的:onClick()方法是在onTouchEvent内部被调用的。

同我们前面找到mOnTouchListener在哪里赋值的一样。我们也能够找到mOnClickListener在哪里赋值的:

 /**
     * Register a callback to be invoked when this view is clicked. If this view is not
     * clickable, it becomes clickable.
     *
     * @param l The callback that will run
     *
     * @see #setClickable(boolean)
     */
    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

我们知道ViewLONG_CLICKABLE属性默认是False的。须要的话我们能够自己在xml或者java文件里去设置。可是CLICKABLE的False与True是和详细的View有关的,比方我们知道Button是可点击的,可是TextView默认是不可点击的。可是假设给TextView设置了点击事件,那么依据

 if (!isClickable()) {
            setClickable(true);
        }

这几行代码TextView也会被设置为可点击的,同理还有setOnLongClickListener也有这样的作用:

 /**
     * Register a callback to be invoked when this view is clicked and held. If this view is not
     * long clickable, it becomes long clickable.
     *
     * @param l The callback that will run
     *
     * @see #setLongClickable(boolean)
     */
    public void setOnLongClickListener(@Nullable OnLongClickListener l) {
        if (!isLongClickable()) {
            setLongClickable(true);
        }
        getListenerInfo().mOnLongClickListener = l;
    }

总结

到此。View的事件分发机制已经分析完了。整个过程查了很多资料,最基本的是跟着任玉刚老师的《Android开发艺术探索》学习,最后把这个学习的过程记录下来就是这篇博客了,等有时间的时候把ViewGroup的事件分发机制也分析一遍。

主要參考

Android触摸屏事件派发机制详细解释与源代码分析一(View篇)

Android事件传递机制

请看兴许ViewGroup的事件分发机制

时间: 2024-12-16 17:19:25

View的事件分发机制解析的相关文章

Android View的事件分发机制

准备了一阵子,一直想写一篇事件分发的文章总结一下.这个知识点实在是太重要了. 一个应用的布局是丰富的,有TextView,ImageView,Button等.这些子View的外层还有ViewGroup.如RelativeLayout.LinearLayout.作为一个开发人员,我们会思考.当点击一个button,Android系统是如何确定我点的就是button而不是TextView的?然后还正确的响应了button的点击事件. 内部经过了一系列什么过程呢? 先铺垫一些知识能更加清晰的理解事件分

13.View的事件分发机制——dispatchTouchEvent详解

在前面的第二篇文章中,我们提过,View的事件分发是一种委托思想:上层委托下层,父容器委托子元素来处理这个流程.接下来,我们就将深入去学习View的事件分发机制. 1.事件的传递流程 事件,在Android中对应的类是MotionEvent,因此,我们要分析的就是MotionEvent这个类.对点击事件的分发,其实就是对MotionEvent的对象进行处分发.所以,当一个MotionEvent产生以后(从驱动读取),系统需要把这个事件传递给一个具体的View,这个传递的过程就是分发过程,点击事件

Android开发-分析ViewGroup、View的事件分发机制、结合职责链模式

介绍 上一篇博客职责链/责任链模式(Chain of Responsibility)分析理解和在Android的应用 介绍了职责链模式,作为理解View事件分发机制的基础. 套用职责链模式的结构分析,当我们的手指在屏幕上点击或者滑动,就是一个事件,每个显示在屏幕上的View或者ViewGroup就是职责对象,它们通过Android中视图层级组织关系,层层传递事件,直到有职责对象处理消耗事件,或者没有职责对象处理导致事件消失. 关键概念介绍 要理解有关View的事件分发,先要看几个关键概念 Mot

Android View 的事件分发原理解析

作为一名 Android 开发者,每天接触最多的就是 View 了.Android View 虽然不是四大组件,但其并不比四大组件的地位低.而 View 的核心知识点事件分发机制则是不少刚入门同学的拦路虎,也是面试过程中基本上都会问的.理解 View 的事件能够让你写出更好自定义 View 以及解决滑动冲突. 1. View 事件认识 1.1 MotionEvent 事件 当你用手指轻触屏幕,这个过程在 Android 中主要可以分为以下三个过程: ACTION_DOWN:手指刚接触屏幕,按下去

Android View体系(五)从源码解析View的事件分发机制

相关文章 Android View体系(一)视图坐标系 Android View体系(二)实现View滑动的六种方法 Android View体系(三)属性动画 Android View体系(四)从源码解析Scroller 前言 三年前写过事件分发机制的文章但是写的不是很好,所以重新再写一篇,关于事件分发机制的文章已经有很多,但是希望我这篇是最简洁.最易懂的一篇. 1.处理点击事件的方法 View的层级 我们知道View的结构是树形的结构,View可以放在ViewGroup中,这个ViewGro

Android 开发艺术探究V第三章之view的事件分发机制

在介绍点击事件的传递机制,首先我们要分析的对象就是MOtionEvent,即点击事件,(当点击屏幕时由硬件传递过来,关于MotionEvent在View的基础知识中做了介绍),所谓的点击事件的分发就是MotionEvent的分发过程.即当一个MoTionEvent产生以后,系统需要把这个事件具体传递给一个具体的View,而这个传递过程就是分发过程,点击事件传递过程有三个很重要的方法,下面先来介绍这几个方法.  public boolean dispatchTouchEvent(MOtionEve

Android view 的事件分发机制

1 事件的传递顺序是 Activity -> Window -> 顶层View touch 事件产生后,最先由 activity 的 dispatchTouchEvent 处理 /** * Called to process touch screen events. You can override this to * intercept all touch screen events before they are dispatched to the * window. Be sure to

Android中View的事件分发机制

简介 事件也称MotionEvent,事件分发机制就是对MotionEvent事件的分发过程,即当一个MotionEvent发生之后,系统需要把这个事件传递给一个具体的View. 点击事件的分发过程由三个函数共同完成: dispatchTouchEvent(DTE) - 进行事件的分发,如果时间能够传递给当前View,该方法会被调用,返回结果受当前view的onTouchEvent, 子View的dispatchTouchEvent影响,表示是否消耗当前事件. onInterceptTouchE

Android View的事件分发机制探索

概述 Android事件传递机制也是Android系统中比较重要的一块,事件类型有很多种,这里主要讨论TouchEvent的事件在framework层的传递处理机制.因为对于App开发人员来说,理解framework层的事件传递机制,就差不多了. 带着问题来思考整个事件分发过程. 1.为什么要有事件分发过程? 当Android设备的屏幕,接收到触摸的动作时,屏幕驱动把压力信号(包括压力大小,压力位置等)传递给系统底层,然后操作系统经过一系列的处理,然后把触摸事件一层一层的向上传递,最终事件会被准