Android 事件分发机制(最新源码6.0分析)--childView

1,子View的事件分发机制

2,ViewGroup的事件分发机制

转载请标明出处:http://blog.csdn.net/u014800493/article/details/52047167
在分析事件分发之前,先了解一下View,ViewGroup的API层级结构


从上面的层级关系可以看出ImageView,TextView,ViewGroup继承View,属于同级关系而Button,EditText继承TextView。下面再来看看ViewGroup的子类有什么
上图可以看出平常所用到的layout基本继承于ViewGroup.
好了话不多说,进入主题。首先还是分析一下子View也就是常用的TextView,Button等等的一些View控件。当然分析要用实际用例才能更加明了。下面就是一个Activity 里面就有一个Button。先来看看布局文件
<?xml version="1.0" encoding="utf-8"?>
<com.gordon.shop.view.MyButton
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/button_onclick"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:visibility="visible"
    android:text="按钮点击事件" >
</com.gordon.shop.view.MyButton>
很简单就一个Button.当然这里自定义了一个Button
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.Button;

/**
 * @author Gordon
 * @since 2016/7/27
 * do()
 */
public class MyButton extends Button {
    public MyButton(Context context) {
        super(context);
    }

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

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

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i("button","Button_onTouchEvent");
        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.i("button","Button_dispatchTouchEvent");
        return super.dispatchTouchEvent(event);
    }
}
主要就是就是在onTouchEvent和dispatchEvent中打出相应的Log,从而显示button的事件
再来看看Activity中的代码:
/**
 * @author Gordon
 * @since 2016/7/27
 * do()
 */
public class OnClickEventActivity extends Activity {
    @Bind(R.id.button_onclick)
    MyButton click_button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_onclick_layout);
        ButterKnife.bind(this);
        intView();
    }

    private void intView() {
        click_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.i("button", "Button_OnClick");
            }
        });
        click_button.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                Log.i("button", "Button_setOnTouchListener");
                return false;
            }
        });
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i("activity", "Activity_onTouchEvent");
        return super.onTouchEvent(event);
    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i("activity", "Activity_dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }
}
13行的ButterKnife为引用的第三方的包,其实就是方便,可以省略之前的findViewById()。使用的话就是在.gradle文件中引用:
dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs') 
    compile 'com.jakewharton:butterknife:7.0.0'
}
而在Activity中也有OnTouchEvent和dispatchEvent方法,在这里也重写了一下并且相应的log出来。
好了基本用例已经编写完成。下面就让我们来见证一下吧。先来看看跑出来的Activity图

 
 
好了 先来点击一下空白区域看一下Log
先走的Activity 的dispatchEvent方法,然后是onTouchEvent,继续点击button按钮
为Activity_dispatchEvent----------->onTouchEvent
为Activity_dispatchEvent---------->Button_dispatchEvent---------->Button_setOnTouchListener(也就是onTouch())---------->Button_onTouchEvent
---------->Button_onClick
首先来分析一下几个疑点:
1,为什么没有走Activity的ouTouchEvent方法呢
2,为什么是dispatchEvent--->onTouch---->onTouchEvent这个走向呢
好了直接上源码
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
这是activity的dispatchEvent的源码。代码看似很简单。一步步分析一下第一个if语句ACTION_DOWN,也就是手指按下事件,不多说。看看onUserInteraction()方法,点进去看看是什么
    /**
     * Called whenever a key, touch, or trackball event is dispatched to the
     * activity.  Implement this method if you wish to know that the user has
     * interacted with the device in some way while your activity is running.
     * This callback and {@link #onUserLeaveHint} are intended to help
     * activities manage status bar notifications intelligently; specifically,
     * for helping activities determine the proper time to cancel a notfication.
     *
     * <p>All calls to your activity's {@link #onUserLeaveHint} callback will
     * be accompanied by calls to {@link #onUserInteraction}.  This
     * ensures that your activity will be told of relevant user activity such
     * as pulling down the notification pane and touching an item there.
     *
     * <p>Note that this callback will be invoked for the touch down action
     * that begins a touch gesture, but may not be invoked for the touch-moved
     * and touch-up actions that follow.
     *
     * @see #onUserLeaveHint()
     */
    public void onUserInteraction() {
    }
what?怎么是空的。看一下官方的注释,大致就是可以在此方法总实现用户的一些动作。这里就不多说了 。看下第二个if语句
getWindow()点进去看看
   public Window getWindow() {
        return mWindow;
    }
这个mWindow是什么呢。在来搜索一下
        mWindow = new PhoneWindow(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
原来是PhoneWindow啊,而PhoneWindow又是什么呢,他的.superDispatchEvent(et)方法源码又是什么呢?
这个肯定要去看看PnoneWindow的源码咯:这里主要看下superDispatchEvent()方法:
public class PhoneWindow extends Window implements MenuBuilder.Callback {
....................   
 @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
................
}
这TM的mDecor又是什么鬼。原来是DecorView。是PhoneWindow的内部类
    private final class DecorView extends FrameLayout { 
    ...................
      public boolean superDispatchKeyEvent(KeyEvent event) {
            return super.dispatchKeyEvent(event);
        }
        public boolean superDispatchTouchEvent(MotionEvent event) {
            return super.dispatchTouchEvent(event);
        }
        public boolean superDispatchTrackballEvent(MotionEvent event) {
            return super.dispatchTrackballEvent(event);
        }
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            return onInterceptTouchEvent(event);
        }      
     ..............     
    }
只给出了DecorView的部分源码,原来DecorView父类为FrameLayout,而FrameLayout的父类是ViewGroup。
好了再回头看一下Activity的dispatchEvent方法的第二个if语句getWindow().superDispatchEvent()这里居然是ViewGroup的
事件分发机制。简单的说一下其实是被ViewGroup的子View (Button)是事件给拦截了直接return true。所以才没有执行activty的onTouchEvent()方法
至于怎么拦截的。下一章具体再说。还有人问了,这个PhoneWindow,DecorView,Activity到底是什么关系呢,大家可以网上搜一下。大致的结构图为

也就是Activity用PhoneWindow来设置RootView(DecorView)。好了继续探讨一下子VIew(Button)的点击事件。也就是前面的第二个问题
2,为什么是dispatchEvent--->onTouch---->onTouchEvent---->onClick这个走向呢.先瞅瞅Button的dispatchEvent源码:
 /**
     * Pass the touch screen motion event down to the target view, or this
     * view if it is the target.
     *
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    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;
    }
先看看第10行的if语句,看注释,判断event是否能获取焦点如果不能或者不存在这个View,直接返回false跳出循环。
第21-26行的if语句处理一些手势如:action_down,up,move判断手势,手势的传递,看看stopNestedScroll()方法
 /**
     * Stop a nested scroll in progress.
     *
     * <p>Calling this method when a nested scroll is not currently in progress is harmless.</p>
     *
     * @see #startNestedScroll(int)
     */
    public void stopNestedScroll() {
        if (mNestedScrollingParent != null) {
            mNestedScrollingParent.onStopNestedScroll(this);
            mNestedScrollingParent = null;
        }
    }
当Action_Down的时候处理之前的手势问题,这些不是重点。下面看看31-39行
  1.  if (onFilterTouchEventForSecurity(event)) {  
  2.            //noinspection SimplifiableIfStatement  
  3.            ListenerInfo li = mListenerInfo;  
  4.            if (li != null && li.mOnTouchListener != null  
  5.                    && (mViewFlags & ENABLED_MASK) == ENABLED  
  6.                    && li.mOnTouchListener.onTouch(this, event)) {  
  7.                result = true;  
  8.            }  
  9.   
  10.            if (!result && onTouchEvent(event)) {  
  11.                result = true;  
  12.            }  
  13.        }  
看看onFilterTouchEventForSecurity()这个方法是什么
    /**
     * Filter the touch event to apply security policies.
     *
     * @param event The motion event to be filtered.
     * @return True if the event should be dispatched, false if the event should be dropped.
     *
     * @see #getFilterTouchesWhenObscured
     */
    public boolean onFilterTouchEventForSecurity(MotionEvent event) {
        //noinspection RedundantIfStatement
        if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
                && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
            // Window is obscured, drop this touch.
            return false;
        }
        return true;
    }
过滤一些时间比如Window隐藏或者遮挡了直接返回false,正常情况下返回true,进而去处理Event事件。
好了进入if语句内部ListenerInfo是View的静态内部类用来定义listener,也就是view添加的一些listener。
继续往下又一个if语句首先是li和lTouchistener的非空判断,因为之前的Activity里面已经设置了button
的ontouchListener事件。接着(mViewFlags & ENABLED_MASK) == ENABLED判断view是
否为Enable。当然View默认都是Enable的,接着就是 li.mOnTouchListener.onTouch(this, event)
这个了,也即是说如果ouTouch返回true,那么result为true就不会进入下面if语句

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

也即是不会继续走onTouchEvent()方法。因为&&当第一个条件false的时候就不会继续往下判断执行了。
当然如果返回false,就会走onTouchEvent()方法。让我们来看看ouTouchEvent()源码:
  /**
     * 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;
    }
源码有些长。慢慢解读先来看看24-33行的if语句。判断view是不是Enable状态。当view为disable状态时,看下return。
只要这个ViewCLICKABLELONG_CLICKABLE或者CONTEXT_CLICKABLE有一个为true,那么返回值就是true,
onTouchEvent()方法会消耗当前事件。那么CLICKABLELONG_CLICKABLE或者CONTEXT_CLICKABLE。在哪设置的呢:
    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }
   public void setClickable(boolean clickable) {
        setFlags(clickable ? CLICKABLE : 0, CLICKABLE);
    }
这里只列出了onClickListener的源码,onLongClickListener和onContextClickListener是一样的就不一一列举了
也就是说只要你设置了这三类listener。无论View是否是Disable的此View就会在ouTouchEvent()方法中消耗此事件。
也就是返回true。也不会 交给parent的View去处理了
继续看35-39行的if语句。这个TouchDelegate类是什么鬼。看下源码:
/**
 * Helper class to handle situations where you want a view to have a larger touch area than its
 * actual view bounds. The view whose touch area is changed is called the delegate view. This
 * class should be used by an ancestor of the delegate. To use a TouchDelegate, first create an
 * instance that specifies the bounds that should be mapped to the delegate and the delegate
 * view itself.
 * <p>
 * The ancestor should then forward all of its touch events received in its
 * {@link android.view.View#onTouchEvent(MotionEvent)} to {@link #onTouchEvent(MotionEvent)}.
 * </p>
 */
public class TouchDelegate {............}
里面的内容就不贴上来了,主要看下官方去这个类的注释。主要就是设置view的代理touch区域,
也就是说当你的view很小时,你可以设置他的代理touch区域。好了继续往下解读。
41行if语句的判断前面已经解读过了。看下if语句里面的内容:
先看先case MotionEvent.Action_down:先把长按判断设为false。继续
if (performButtonActionOnTouchDown(event)) {
    break;
}
这个performButtonActionOnTouchDown()方法又是干啥的呢。进去看看
 /**
     * Performs button-related actions during a touch down event.
     *
     * @param event The event.
     * @return True if the down was consumed.
     *
     * @hide
     */
    protected boolean performButtonActionOnTouchDown(MotionEvent event) {
        if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE &&
            (event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
            showContextMenu(event.getX(), event.getY(), event.getMetaState());
            mPrivateFlags |= PFLAG_CANCEL_NEXT_UP_EVENT;
            return true;
        }
        return false;
    }
用来判断是不是鼠标或者触碰板来消耗down事件。下面的代码就不解读了,接着看case MotionEvent.Action_move:
这里面移除单击以及长按事件。主要看下case MotionEvent.Action_UP:
首先判断是不是按下boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0,然后判断是不是获取焦点,
如果没有尝试获取焦点,接着看重点:
  1.  if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {  
  2.                           // This is a tap, so remove the longpress check  
  3.                           removeLongPressCallback();  
  4.   
  5.                           // Only perform take click actions if we were in the pressed state  
  6.                           if (!focusTaken) {  
  7.                               // Use a Runnable and post this rather than calling  
  8.                               // performClick directly. This lets other visual state  
  9.                               // of the view update before click actions start.  
  10.                               if (mPerformClick == null) {  
  11.                                   mPerformClick = new PerformClick();  
  12.                               }  
  13.                               if (!post(mPerformClick)) {  
  14.                                   performClick();  
  15.                               }  
  16.                           }  
  17.                       }  
手下判断是不是tap也就是单击事件。然后去post一个Runnable,也就是PerformClick。看下源码:
 private final class PerformClick implements Runnable {
        @Override
        public void run() {
            performClick();
        }
    }
也就是执行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;
    }
ListenerInfo前面已经说过了,看下if语句,li.mOnClickListener!=null也就是Button设置了setOnClickListener()方法。执行onClick()方法
到此为止 :子View (button)的事件传递为
Button_dispatchEvent---------->Button_setOnTouchListener(也就是onTouch())---------->Button_onTouchEvent
---------->Button_onClick。也被证实了。
小结:
button先执行dispatchEvent方法,接着判断有没有设置setOnTouchListener方法,如果在listener.onTouch()中return true。
就不会继续执行ouTouchEvent方法。如果返回false 继续走onTouchEvent方法。在此方法的case Action_up:中。
判断是不是点击事件以后从而执行了onClick()方法了。
总结:至此 子View的事件处理机制,已经分析完了,之间找了很多的资料。主要还是要自己动手去验证。接下来












评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值