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

来源:互联网 发布:unity3d litjson 编辑:程序博客网 时间:2024/06/07 00:25

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的事件处理机制,已经分析完了,之间找了很多的资料。主要还是要自己动手去验证。接下来
就分析一下ViewGroup的事件分发机制咯。












1 0