码迷,mamicode.com
首页 > 其他好文 > 详细

View的事件分发机制解析

时间:2017-07-19 17:55:13      阅读:227      评论:0      收藏:0      [点我收藏+]

标签:分析   time   手机   tin   触摸   打印   and   make   android事件   

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的事件分发机制開始分析。然后在分析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()方法传递了,那么事件分发机制最基本的几条我们已经了解了,以下我们来分析产生这样的机制的根本原因。

首先我们给出一个结论: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的事件分发机制

View的事件分发机制解析

标签:分析   time   手机   tin   触摸   打印   and   make   android事件   

原文地址:http://www.cnblogs.com/llguanli/p/7206569.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!