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行
- 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;
- }
- }
/** * 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语句
- if (!result && onTouchEvent(event)) {
- result = true;
- }
当然如果返回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。
只要这个
View
的CLICKABLE
和LONG_CLICKABLE
或者CONTEXT_CLICKABLE
有一个为true,那么返回值就是true,onTouchEvent()
方法会消耗当前事件。那么CLICKABLE
和LONG_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,然后判断是不是获取焦点,
如果没有尝试获取焦点,接着看重点:
- 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();
- }
- }
- }
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
- Android 事件分发机制(最新源码6.0分析)--childView
- Android 事件分发机制(最新源码6.0分析)--ViewGrop
- Android事件分发机制源码分析
- Android事件分发机制源码分析
- Android事件分发机制源码分析
- Android事件分发机制源码分析
- 源码分析android的事件分发机制
- Android 事件分发机制-源码分析
- 事件分发机制源码分析
- Android 6.0事件分发机制源码解析
- Android事件分发机制源码分析上----View事件分发分析
- Android事件分发机制源码分析下----ViewGroup事件分发分析
- android 从源码分析view事件分发机制
- Android 从源码角度分析事件分发机制(三)
- 从源码角度分析android事件分发处理机制
- 从源码角度分析android事件分发处理机制
- Android View和ViewGroup事件分发机制源码分析
- Android—— View事件分发机制的源码分析
- cmake
- cifar代码
- Mysql与Sql server在语法和关键字上的区别
- 从什么都不懂开始(二)——创建Project提交到Github需要做什么
- Exception异常处理
- Android 事件分发机制(最新源码6.0分析)--childView
- 字串变换
- QT国际化翻译的使用
- Java笔记之socket中的SSL
- 找字符串中第一个只出现一次的字符
- Redhat Enterprise 7.2 安装 MySQL 5.7
- HDU 2897 Bash博弈变形
- SSM框架——详细整合教程(Spring+SpringMVC+MyBatis)
- SaaS WMS与传统WMS的区别