事件分发源码解析 2
来源:互联网 发布:淘宝外卖那里送货 编辑:程序博客网 时间:2024/06/05 18:45
#事件分发源码解析
1 .## **Activity的事件分发过程**
>当一个事件发生时,首先传递给Activity,由Activity的dispathTouchEvent来进行转发,具体的工作是由Activity内部的Window来完成的。Window一般就会将事件传递给decor view ,(decor view 一般就是当前界面的底层容器--即setContentView设置的View的父容器),可以通过**Activity.getWindow.getDecorView()**可以获得。
- 首先从Activity的dispatchTouchEvent开始分析
>那么Window是如何将事件传递给ViewGroup的。通过源码我们知道,Window是一个抽象类,它的superDispatchTouchEvent是一个抽象方法,因此我们需要找到Window的实现类。
Activity
` `ViewGroup # dispatchTouchEvent`
ViewGroup # dispatchTransformedTouchEvent`
所以这里就转到了View的dispatchTouchEvent方法,即点击事件开始交给View来处理。
` View#dispatchTouchEvent`
View#onTouchEvent
先看以下View处于不可用状态下点击事件的处理过程,可以看到不可用状态下的View,依然会消耗点击事件,尽管它看起来不可用。 下面就是onTouchEvent对点击事件的具体处理如果View设置了OnTouchListener,那么performClick()方法内部会调用它的onClick方法 `View#performClick`
ListenerInfo是View的一个静态内部类,里面包含可用的监听声明,
>当一个事件发生时,首先传递给Activity,由Activity的dispathTouchEvent来进行转发,具体的工作是由Activity内部的Window来完成的。Window一般就会将事件传递给decor view ,(decor view 一般就是当前界面的底层容器--即setContentView设置的View的父容器),可以通过**Activity.getWindow.getDecorView()**可以获得。
- 首先从Activity的dispatchTouchEvent开始分析
``源码:Activity#dispatchTouchEvent``
` 源码:Window #superDispatchTouchEvent`/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}//Activity内部的Window进行事件分发,如果是true,整个事件结束.
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}//返回false 意味着事件没人处理,所有View的onToucheEvent都返回的false,那么Activity的onTouchEvent方法就会调用
return onTouchEvent(ev);
}
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
* Window类可以控制顶级View的外观和行为策略,它的唯一实现位于android.view.PhoneWindow
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window. 存在唯一的实现类 就是 android.view.PhoeWindow类
*/
public abstract class Window {
/**
* Used by custom windows, such as Dialog, to pass the touch screen event
* further down the view hierarchy. Application developers should
* not need to implement or call this.
*
*/
public abstract boolean superDispatchTouchEvent(MotionEvent event);
` 源码: Activity#attach重载方法`
final void attach···
mWindow = new PhoneWindow(this); //可以找到Window的实例是一个PhoneWindow类 ···
> 我们就找到了Window的实现类 ---》,实际上在Window类的描述中,就已经说明了该类是唯一的实现类
2 . ## **Window的事件分发过程**
>找到了PhoneWindow ,那么 PhoneWindow的superDispatchToucheEvent是具体怎么处理的。
PhoneWindow #superDispatchTouchEvent
>这里逻辑就很清晰了: PhoneWindow 将事件传递个了DecorView,那么这个DecorView是什么?public boolean superDispatchTouchEvent(MotionEvent event){ return mDecor.superDispatchTouchEvent(event);}
private DecorView mDecor;
@Override
public final View getDecorView(){
if(mDecor == null){
installDecor();
}
return mDecor
public final class DecorView extends FrameLayout implements RootViewSurfaceTacker{//This is the top-level view of the window ,containing the wiondow decor.
>我们知道
/**view 就是我们setContentView 的View,是mDecor的子View。
* 获取Activity所设置的View,这个mDecor显然就是getWindow().getDecorView()返回的View.
*/
View view = ((ViewGroup) getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0);
>从这里开始 ,事件已经由mDecor传递给顶级View了(就是我们在Activity中setContentView设置的View),另外顶级View也叫根View,一般来说都是ViewGroup
3.## **顶级View(一般是ViewGroup)的事件分发过程**
1.ViewGroup对点击事件的分发过程。其主要实现在ViewGroup的dispatchTouchEvent方法中。
// Check for interception.
final boolean intercepted;//ViewGroup是否拦截事件。
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {//当是DOWN事件 或者 ViewGroup不拦截子,由子类处理时
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//这个标志为是通过子类调用requestDisallowInterceptTouchEvent方法来设置的。表示父类不要拦截事件。
if (!disallowIntercept) {//当前ViewGroup允许拦截
intercepted = onInterceptTouchEvent(ev);//调用拦截方法,判断是否拦截
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false; //如果子类请求不许拦截,就交给子类处理,并且给mFirstTouTarget 赋值。
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
一旦事件由当前ViewGroup拦截时,那么mFirstTouchTarget 不会赋值。
那么以后的所有事件到来时,Move和up 事件 。down除外。
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
判断都是false 就不会进入,即ViewGroup的onInterceptTouchEvent不会在被调用,同一序列的所有事件都默认交给当前ViewGroup进行处理。
当面对DOWN事件时,ViewGroup会重置FLAG_DISALLOW_INTERCEPT标志位,这就导致View中设置这个标志为无效。ViewGroup总会调用自己的onInterceptTouchEvent方法来询问自己是否拦截事件,所以这样FLAG_DISALLOW_INTERCEPT标志位一旦被子类调用requestDiaallowInterceptTouchEvent设置后,除了DOWN事件,其他事件都可不被拦截。
` `ViewGroup # dispatchTouchEvent`
// Handle an initial down.//每一个down事件,都会被重置状态的操作。
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();//FLAG_DISALLOW_INTERCEPT标志位被充值。
}
两条结论:
/**
* Resets all touch state in preparation for a new cycle.//重置所有的触摸状态准备一个新的序列。
*/
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
1. onInterceptTouchEvent方法不是每次都会被调用,如果我们想提前处理所有的点击事件,我们应在选择dispatchTouchEvent方法。只有这个方法每次都会被调用。
2.FLAG_DISALLOW_INTERCEPT解决滑动冲突。
当ViewGroup不拦截的时候,事件会下发给它的子类进行处理。
`ViewGroup # dispatchTouchEvent`
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);//获取当前事件的点击坐标
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.找一个能够接受事件的子类
// Scan children from front to back.遍历所有的子类
final ArrayList<View> preorderedList = buildOrderedChildList();//构建一个子类列表集合
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}//Returns true if a child view can receive pointer eventif (!canViewReceivePointerEvents(child)//检测child是否能够接受点击事件Returns true if a child view contains the specified point when transformed|| !isTransformedTouchPointInView(x, y, child, null)) {
into its coordinate space.
Child must not be null. //检测 (x,y)点是否在child中
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child); /**
* Transforms a motion event into the coordinate space of a particular child view,// Child wants to receive touch within its bounds.
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.dispatchTransformedTouchEvent实际上就是调用的child的dispatchTouchEvent
*if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY(); //如果子元素的disspatchTouchEvent返回的是ture,那么mFirstTouchTarget就会被赋值,同时跳出for循环。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget= true;
break; //上面3行代码完成了对mFirstTouchTarget的赋值并终止了定义子元素的遍历。如果子元素的dispatchTouchEvent返回的是false,ViewGroup就会把事件传递给下一个子元素(如果有下一个子元素的话)。
} ···// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS); ···
ViewGroup # dispatchTransformedTouchEvent`
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {//如果传入的child为空,就转给View的分发事件方法。
handled = super.dispatchTouchEvent(event);
} else {//否则交给child分发事件
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
/**
* Adds a touch target for specified child to the beginning of the list.
* Assumes the target child is not already present.
*/
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
分析:1.首先遍历ViewGroup的所有子元素,然后判断子元素是否可以接受到点击事件。能否接受点击事件主要有两点衡量:子元素是否在播放动画和点击事件的坐标落在子元素的区域内。如果子元素满足,事件就会传递给它处理。dispatchTransformedTouchEvent实际上就是调用的子元素的dispatchTouchEvent
如果子元素的dispatchTouchEvent返回的是true,那么mFirstTouchTarget就会被赋值。
其实mFirstTouchTarget真正的赋值过程是在addTouchTarget内部完成的。
mFirstTouchTarget是一种单链表结构,mFristTouchTarget是否被赋值,将之间影响ViewGroup对事件的拦截策略。如果mFristTouchTarget == null,ViewGroup就会默认拦截下来同一序列中所有的点击事件。
2。如果遍历所有的子元素后事件都没有被适合的处理,这包含两种情况。
1.ViewGroup没有子元素;
2.子元素处理了点击事件,但是在dispatchTouchEvent中返回了false,这一般是因为子元素在onTouchEvent中返回了false.
这两种情况下,ViewGroup会自己处理点击事件。
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view. //这里判断mFirstTouchTarget== null ,第三个参数传入了null,根据上面的分析,我们知道,如果传入的为null,那么就会调用View的dispatchTouchEvent方法
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
4.## **View对点击事件的处理过程**(这里的View不包含ViewGroup)
View的分发方法 dispatchTouchEvent
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//判断有没有OnTouCHListener
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {//并且返回true
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
View对点击事件的处理过程就比较简单,因为View是一个单独的元素,它没有子元素因下次无法向下传递,所以它只能自己处理事件。
处理过程:首先判断有没有设置OnTouchListener,如果OnTouchListener中的onTouch方法返回true,那么onTouchEvent方法就不会调用。说明OnTouchListener 比 onTouchEvent的优先级高。这样方便在外界处理点击事件。
View的onTouchEvent方法
View的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. 一个可点击但不可用的View依然会消费触摸事件,它只是不能响应他们
return (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
} //如果View设置的代理,那么还会执行TouchDelegate的onTouchEvent方法,这个onTouceEvent的工作机制看起来和OnTouchListener类似。
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//只要View的CLICKABLE和LONG_CLICKABLE有一个为true,那么它就会消耗这个事件。即 onTouchEvent返回的就是true,而不管它是不是DISABLE.
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP://在ACTION_UP事件发生时,会触发performClick方法
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();//如果View设置了OnTouchListener,那么performClick()方法内部会调用它的onClick方法
}
}
}
···
/**
* 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) {//点击监听器不为null
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);//执行点击事件
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
NOTE:
View的LONG_CLICKABLE (长点击)属性默认为false, 而 CLICKABLE属性是否为false和具体的View有关。确切的说,可点击的View其CLICKABLE = true; 不可点击的View的CLICKABLE = false;通过setCLickable 和setLongCLickable 可以分别改变VIew的 CLICKABLE 和 LONG_CLICKABLE 属性。
另外,setOnClickListener会自动将View的CLICKABLE设为true,setOnLongClickListener则会将LONG_CLICKABLE 设为true
/**
* 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;
}
/**
* 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;
}
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
static class ListenerInfo { /* * Listener used to dispatch focus change events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnFocusChangeListener mOnFocusChangeListener;
/**
* Listeners for layout change events.
*/
private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
protected OnScrollChangeListener mOnScrollChangeListener;
/**
* Listeners for attach events.
*/
private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;
/**
* Listener used to dispatch click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
public OnClickListener mOnClickListener;
/**
* Listener used to dispatch long click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnLongClickListener mOnLongClickListener;
/**
* Listener used to dispatch context click events. This field should be made private, so it
* is hidden from the SDK.
* {@hide}
*/
protected OnContextClickListener mOnContextClickListener;
/**
* Listener used to build the context menu.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnCreateContextMenuListener mOnCreateContextMenuListener;
private OnKeyListener mOnKeyListener;
private OnTouchListener mOnTouchListener;
private OnHoverListener mOnHoverListener;
private OnGenericMotionListener mOnGenericMotionListener;
private OnDragListener mOnDragListener;
private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;
OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
}
0 0
- 事件分发源码解析 2
- android_事件分发源码解析
- 事件分发的源码解析
- ViewGropw 事件分发源码解析
- Andorid事件分发源码解析
- Android源码:事件分发源码解析(一)
- Android源码:事件分发源码解析(二)
- Android View 事件分发机制 源码解析
- Android View 事件分发机制 源码解析
- Android View 事件分发机制 源码解析
- Android View 事件分发机制 源码解析
- View的事件分发机制源码解析
- Android View 事件分发机制 源码解析
- Android View 事件分发机制 源码解析
- Android 6.0事件分发机制源码解析
- Android View 事件分发机制 源码解析
- android事件分发源码解析(下)
- android事件分发源码解析(上)
- 剑指Offer——数组中只出现一次的两个数字
- Spring核心技术(四)——Spring的依赖及其注入(续二)
- 位,字节,字,字长概念
- 算法学习【9】判断是否为栈的弹出顺序
- RelativeLayout控件居中详细解析(可能是最完美的方法)
- 事件分发源码解析 2
- 郑州iOS點 - 关于升级Mac系统后cocoapods无法导入第三方的问题解决办法
- /proc
- cygwin添加类似OSX中的`open`命令
- CHttpProxyServer
- hdu1003 Max Sum
- C++实现的Miller-Rabin素性测试程序
- runtime简介和作用(上代码)
- 生活是不需要热爱的