首页 > 其他 > 详细

View的事件分发

时间:2019-12-12 18:06:15      阅读:100      评论:0      收藏:0      [点我收藏+]

事件分发使用责任链设计模式

1、责任链设计模式个人总结重点

 

1)声明接口规则用于向下传递待处理对象

2)抽象出处理方法,是否对待处理对象进行处理

3)持有链表下游引用

4)实现接口方法:若自己不处理,则传递给下游处理

5)接口方法要返回是否拦截结果

当返回false时,说明自己和下游全都不处理;返回true时,说明自己处理了或可知下游链中有人处理了

6)通过设置持有对象完成责任链

 

2、Demo

Callback.java接口规则

//事件流转协议
public interface Callback<T> {
    //传递某个事件/obj
    boolean dispatch(T obj);
}

Stub.java,抽象出处理和分发

public abstract class Stub<T> implements Callback<T> {

    private Callback next;

    //设置下游对象
    public void setNext(Callback Callback) {
        this.next = Callback;
    }

    @Override
    public boolean dispatch(T obj) {
        //如果自己不处理,就分发给下游处理
        boolean intercept = onIntercept(obj);
        if (!intercept && next != null) {
            //是否处理从链表下游返回
            intercept = next.dispatch(obj);
        }
        return intercept;
    }

    //自己是否处理
    abstract boolean onIntercept(T obj);

}

三个实现类

技术分享图片
public class Stub1 extends Stub<Integer> {
    @Override
    boolean onIntercept(Integer obj) {
        if (obj <= 6) {
            Log.i("stub","stub1 处理了");
            return true;
        }
        return false;
    }
}
Stub1
技术分享图片
public class Stub2 extends Stub<Integer> {
    @Override
    boolean onIntercept(Integer obj) {
        if (obj > 6 && obj <= 66) {
            Log.i("stub", "stub2 处理了");
            return true;
        }
        return false;
    }
}
Stub2
技术分享图片
public class Stub3 extends Stub<Integer> {
    @Override
    boolean onIntercept(Integer obj) {
        if (obj > 66 && obj <= 666) {
            Log.i("stub","stub3 处理了");
            return true;
        }
        return false;
    }
}
Stub3
技术分享图片
Stub1 stub1 = new Stub1();
Stub2 stub2 = new Stub2();
Stub3 stub3 = new Stub3();
//设置责任链
stub1.setNext(stub2);
stub2.setNext(stub3);
stub3.setNext(new Stub() {
            @Override
            boolean onIntercept(Object obj) {
                Log.i("stub", "无论如何由我顶住!");
                return true;
            }
        });
stub1.dispatch(656);
开始分发

最终打印    技术分享图片

 

 

View的事件分发机制

view事件分发采用责任链设计模式,伪代码参考如下。

事件先由Activity-->window-->DecorView进入到顶级View(一般是个ViewGroup),

调用dispatchTouchEvent(MotionEvent ev)方法,在此方法中先调用onInterceptTouchEvent(MotionEvent ev)判断是否拦截,

若拦截事件就调用onTouchEvent(MotionEvent ev)处理事件,否则就调用子View的dispatchTouchEvent(MotionEvent ev),将事件传递给子view开始下一次循环

    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean consume = false;
        if (onInterceptTouchEvent(ev)) {
            consume = onTouchEvent(ev);
        } else {
            consume = child.dispatchTouchEvent(ev);
        }

        return consume;
    }

 

一、ViewGroup对点击事件的分发

1、ViewGroup的  dispatchTouchEvent(MotionEvent ev)

在分析代码前先了解两个变量

mFirstTouchTarget  当事件由ViewGroup的子元素成功处理时,mFirstTouchTarget会被赋值指向子元素

FLAG_DISALLOW_INTERCEPT,这个标记位通过requestDisallowInterceptTouchEvent()方法设置,一般用于子View中,用于干预父View的事件分发

 1             当新事件序列到来时,进行一些重置操作
 2        // Handle an initial down.
 3             if (actionMasked == MotionEvent.ACTION_DOWN) {
 4                 // Throw away all previous state when starting a new touch gesture.
 5                 // The framework may have dropped the up or cancel event for the previous gesture
 6                 // due to an app switch, ANR, or some other state change.
 7                 cancelAndClearTouchTargets(ev);
 8                 resetTouchState();
 9             }
10 
11        判断是否进行事件的拦截
12             // Check for interception.
13             final boolean intercepted;
14             if (actionMasked == MotionEvent.ACTION_DOWN
15                     || mFirstTouchTarget != null) {
16                 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
17                 if (!disallowIntercept) {
18                     intercepted = onInterceptTouchEvent(ev);
19                     ev.setAction(action); // restore action in case it was changed
20                 } else {
21                     intercepted = false;
22                 }
23             } else {
24                 // There are no touch targets and this action is not an initial down
25                 // so this view group continues to intercept touches.
26                 intercepted = true;
27             }

1)当事件为ACTION_DOWN时,也就是一个新的事件序列到来时(事件序列由一个DOWN开始,中间N个MOVE,一个UP结束),会先进行一些重置操作   

 cancelAndClearTouchTargets(ev);   将会对mFirstTouchTarget进行置空

 resetTouchState() 重置FLAG_DISALLOW_INTERCEPT

 

2)ViewGroup判断是否进行事件的拦截

由14行代码得出    actionMasked != MotionEvent.ACTION_DOWN && mFirstTouchTarget != null 时 intercepted = true;

当事件是MOVE、UP(不是一个新的事件序列)并且没有子View处理,说明ViewGroup自己已拦截了DOWN事件,onInterceptTouchEvent()不会被调用,ViewGroup直接拦截。

由此看出,ViewGroup一旦拦截了DOWN事件,那么整个事件序列都由ViewGroup拦截处理。


已知事件为DOWN时会进行清空操作,FLAG_DISALLOW_INTERCEPT已被重置,disallowIntercept == false,此时ViewGroup会调用onInterceptTouchEvent()进行是否拦截的判断

由此看出,每次DOWN事件到来时ViewGroup都会进行拦截判断


当 mFirstTouchTarget  != null  时,ViewGroup正常情况下会判断是否要拦截事件,但如果子View设置了FLAG_DISALLOW_INTERCEPT(由上可知此时一定不是Down事件),disallowIntercept == true,ViewGroup不拦截事件,onInterceptTouchEvent()也不会被调用

通过设置FLAG_DISALLOW_INTERCEPT,可以让ViewGroup无法拦截除DOWN事件外的其他事件。(滑动冲突解决方案)

 

2、ViewGroup的 onInterceptTouchEvent(MotionEvent ev)

 public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)         //来自鼠标,一般不会有这个操作
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
    }

ev.isFromSource(InputDevice.SOURCE_MOUSE) 表示事件来自鼠标,一般不会由此操作,所以onInterceprTouchEvent()返回false

可知ViewGroup默认不拦截事件

由上知onInterceptTouchEvent()在DOWN事件来临时一定会被调用,如果返回true进行拦截,那么接下来的MOVE、UP事件也都会被拦截交给ViewGroup的onTouchEvent处理,此时onInterceptTouchEvent()并不会被调用

所以重写此方法处理事件时需要注意,它并不会每次都会被调用

 

3、确认不拦截后,ViewGroup具体怎样分发事件到子元素呢

 1 public boolean dispatchTouchEvent(MotionEvent ev) {
 2     ......
 3      if (!canceled && !intercepted) {
 4          final View[] children = mChildren;
 5                         for (int i = childrenCount - 1; i >= 0; i--) {
 6                             final int childIndex = getAndVerifyPreorderedIndex(
 7                                     childrenCount, i, customOrder);
 8                             final View child = getAndVerifyPreorderedView(
 9                                     preorderedList, children, childIndex);
10 
11                             // If there is a view that has accessibility focus we want it
12                             // to get the event first and if not handled we will perform a
13                             // normal dispatch. We may do a double iteration but this is
14                             // safer given the timeframe.
15                             if (childWithAccessibilityFocus != null) {
16                                 if (childWithAccessibilityFocus != child) {
17                                     continue;
18                                 }
19                                 childWithAccessibilityFocus = null;
20                                 i = childrenCount - 1;
21                             }
22 
23                             if (!canViewReceivePointerEvents(child)
24                                     || !isTransformedTouchPointInView(x, y, child, null)) {
25                                 ev.setTargetAccessibilityFocus(false);
26                                 continue;
27                             }
28 
29                             newTouchTarget = getTouchTarget(child);
30                             if (newTouchTarget != null) {
31                                 // Child is already receiving touch within its bounds.
32                                 // Give it the new pointer in addition to the ones it is handling.
33                                 newTouchTarget.pointerIdBits |= idBitsToAssign;
34                                 break;
35                             }
36 
37                             resetCancelNextUpFlag(child);
38                             if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
39                                 // Child wants to receive touch within its bounds.
40                                 mLastTouchDownTime = ev.getDownTime();
41                                 if (preorderedList != null) {
42                                     // childIndex points into presorted list, find original index
43                                     for (int j = 0; j < childrenCount; j++) {
44                                         if (children[childIndex] == mChildren[j]) {
45                                             mLastTouchDownIndex = j;
46                                             break;
47                                         }
48                                     }
49                                 } else {
50                                     mLastTouchDownIndex = childIndex;
51                                 }
52                                 mLastTouchDownX = ev.getX();
53                                 mLastTouchDownY = ev.getY();
54                                 newTouchTarget = addTouchTarget(child, idBitsToAssign);  //完成对mFitstTouchEvent的赋值
55                                 alreadyDispatchedToNewTouchTarget = true;
56                                 break;
57                             }
58 
59     ....
60     }
61 ......
62 }

首先遍历ViewGroup所有子元素,判断子元素是否能够接收到点击事件,接收条件:子元素在播动画或者是VISIBLE 并且 点击事件的坐标是否落在子元素的区域内。如代码23行 if 判断,子元素不满足条件continue开始下一轮判断

满足条件继续向下走,38行代码,在 dispatchTransformedTouchEvent() 中调用子元素的dispatchTouchEvent(MotionEvent ev),将事件分发给子元素。

若child.dispatchTouchEvent(transformedEvent) 返回true,表示子元素成功处理事件,在addTouchTarget中完成对mFirstTouchEvent的赋值然后跳出for循环。

 

 1  /**
 2      * Transforms a motion event into the coordinate space of a particular child view,
 3      * filters out irrelevant pointer ids, and overrides its action if necessary.
 4      * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
 5      */
 6     private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
 7             View child, int desiredPointerIdBits) {
 8     ....
 9 
10         // Perform any necessary transformations and dispatch.
11         if (child == null) {
12             handled = super.dispatchTouchEvent(transformedEvent);
13         } else {
14             final float offsetX = mScrollX - child.mLeft;
15             final float offsetY = mScrollY - child.mTop;
16             transformedEvent.offsetLocation(offsetX, offsetY);
17             if (! child.hasIdentityMatrix()) {
18                 transformedEvent.transform(child.getInverseMatrix());
19             }
20             //此时child不为null,将事件分发给子元素
21             handled = child.dispatchTouchEvent(transformedEvent);   
22         }
23    ....
24 }

 

而如果遍历所有子元素都没人处理事件,存在两种情况: 1)viewGroup没有子元素    2)子元素处理了点击事件,但在dispatchTouchEvent() 中返回了false,这一般是子元素在onTouchEvent中返回了false。

在这两种情况下ViewGroup将自己处理点击事件: 调用dispatchTransformedTouchEvent(),但传入的child为null,由上段代码可知,当child为null时,调用的是super.dispatchTouchEvent(transformedEvent),此时事件交由View来处理

 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
......
            // 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);
            } 
....
}

 

二、View对点击事件的分发

 

1、dispatchTouchEvent(MotionEvent ev)

View(不包含ViewGroup)是一个单独的元素,它没有子元素无需向下传递事件,只能自己处理,所以它并没有onInterceptTouchEvent(MotionEvent ev)

17行代码,View首先判断有没有设置onTouchListener,如果设置了onTouchListener且onTouch()返回了true,则不再调用onTouchEvent(),但有个前提 view是ENABLE

 1 /**
 2      * Pass the touch screen motion event down to the target view, or this
 3      * view if it is the target.
 4      *
 5      * @param event The motion event to be dispatched.
 6      * @return True if the event was handled by the view, false otherwise.
 7      */
 8     public boolean dispatchTouchEvent(MotionEvent event) {
 9        ......
10 
11         if (onFilterTouchEventForSecurity(event)) {
12             if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
13                 result = true;
14             }
15             //noinspection SimplifiableIfStatement
16             ListenerInfo li = mListenerInfo;
17             if (li != null && li.mOnTouchListener != null
18                     && (mViewFlags & ENABLED_MASK) == ENABLED
19                     && li.mOnTouchListener.onTouch(this, event)) {
20                 result = true;
21             }
22 
23             if (!result && onTouchEvent(event)) {
24                 result = true;
25             }
26         }
27 
28     ......
29         return result;
30     }

 

 2、View的OnTouchEvent()

View是否可以消费事件首先看它是否是可点击的(CLICKABLE、LONG_CLICKABLE任意一个为true)

18行代码,即使View被设置为DISAVBLED状态,只要clickable为true它仍是可以消费事件的,只是不会对事件进行处理。

21行代码,如果View设置了代理,事件将交由代理View的onTouchEvent执行,如果代理View成功处理事件,当前View将直接返回true表示消耗事件,若代理View没有处理事件,代码向下执行,由当前View继续处理事件,这个和onTouchListener机制很像

45行代码,只要view是可点击的,那么它就会消耗事件,即onTouchEvent()返回true;

在UP事件中,如果View设置了onClickListener,将会调用它的onClick方法。可见 onTouchListener > onTouchEvent > onClickListener

 1  public boolean onTouchEvent(MotionEvent event) {
 2         final float x = event.getX();
 3         final float y = event.getY();
 4         final int viewFlags = mViewFlags;
 5         final int action = event.getAction();
 6 
 7         final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
 8                 || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
 9                 || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
10 
11         if ((viewFlags & ENABLED_MASK) == DISABLED) {
12             if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
13                 setPressed(false);
14             }
15             mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
16             // A disabled view that is clickable still consumes the touch
17             // events, it just doesn‘t respond to them.
18             return clickable;
19         }
20         if (mTouchDelegate != null) {
21             if (mTouchDelegate.onTouchEvent(event)) {
22                 return true;
23             }
24         }
25 
26         if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
27             switch (action) {
28                 case MotionEvent.ACTION_UP:
29                     ....
30                     break;
31 
32                 case MotionEvent.ACTION_DOWN:
33                    ....
34                     break;
35 
36                 case MotionEvent.ACTION_CANCEL:
37                    ....
38                     break;
39 
40                 case MotionEvent.ACTION_MOVE:
41                     ....
42                     break;
43             }
44 
45             return true;
46         }
47 
48         return false;
49     }

 

如有错误,请指正,谢谢!

 

感谢《Android开发艺术探索》!

View的事件分发

原文:https://www.cnblogs.com/mariana/p/11994389.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!