博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android view事件分发初步
阅读量:7019 次
发布时间:2019-06-28

本文共 19257 字,大约阅读时间需要 64 分钟。

  hot3.png

点击事件用MotionEvent表示,最重要的事件分别是:

手指按下:ACTION_DOWN

手指移动:ACTION_MOVE

手指抬起:ACTION_UP

 

点击事件的分发始于Activity,通过Window,然后再传递到View

MotionEvent最先传递给Activity,通过其dispatchTouchEvent,传递给window

1、下面是Activity部分源码

/** * 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();    }    if (getWindow().superDispatchTouchEvent(ev)) {        return true;    }    return onTouchEvent(ev);}/**     * Retrieve the current {@link android.view.Window} for the activity.     * This can be used to directly access parts of the Window API that     * are not available through Activity/Screen.     *     * @return Window The current window, or null if the activity is not     *         visual.     */    public Window getWindow() {        return mWindow;    }

从上面我们看到,当getWindow().superDispatchTouchEvent(ev)返回false的时候,Activity的onTouchEvent方法会被调用,也就是说分发出去的事件如果下面都不处理就自己处理了。

2、Window是个抽象类,那么superDispatchTouchEvent方法便由其子类实现

/**     * 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);

 

3、那么显然Activity中的mWindow变量便是指向其子类,看下面Activit中的attach函数,其中便给mWindow赋值了

mWindow = new PhoneWindow(this, window, activityConfigCallback);

final void attach(Context context, ActivityThread aThread,            Instrumentation instr, IBinder token, int ident,            Application application, Intent intent, ActivityInfo info,            CharSequence title, Activity parent, String id,            NonConfigurationInstances lastNonConfigurationInstances,            Configuration config, String referrer, IVoiceInteractor voiceInteractor,            Window window, ActivityConfigCallback activityConfigCallback) {        attachBaseContext(context);        mFragments.attachHost(null /*parent*/);        mWindow = new PhoneWindow(this, window, activityConfigCallback);        mWindow.setWindowControllerCallback(this);        mWindow.setCallback(this);        mWindow.setOnWindowDismissedCallback(this);        mWindow.getLayoutInflater().setPrivateFactory(this);        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {            mWindow.setSoftInputMode(info.softInputMode);        }        if (info.uiOptions != 0) {            mWindow.setUiOptions(info.uiOptions);        }        mUiThread = Thread.currentThread();        mMainThread = aThread;        mInstrumentation = instr;        mToken = token;        mIdent = ident;        mApplication = application;        mIntent = intent;        mReferrer = referrer;        mComponent = intent.getComponent();        mActivityInfo = info;        mTitle = title;        mParent = parent;        mEmbeddedID = id;        mLastNonConfigurationInstances = lastNonConfigurationInstances;        if (voiceInteractor != null) {            if (lastNonConfigurationInstances != null) {                mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;            } else {                mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,                        Looper.myLooper());            }        }        mWindow.setWindowManager(                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),                mToken, mComponent.flattenToString(),                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);        if (mParent != null) {            mWindow.setContainer(mParent.getWindow());        }        mWindowManager = mWindow.getWindowManager();        mCurrentConfig = config;        mWindow.setColorMode(info.colorMode);    }

4、PhoneWindow

// This is the top-level view of the window, containing the window decor.    private DecorView mDecor;         @Override    public boolean superDispatchTouchEvent(MotionEvent event) {        return mDecor.superDispatchTouchEvent(event);    }

5、DecorView

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {            void setWindow(PhoneWindow phoneWindow) {        mWindow = phoneWindow;        Context context = getContext();        if (context instanceof DecorContext) {            DecorContext decorContext = (DecorContext) context;            decorContext.setPhoneWindow(mWindow);        }    }    public boolean superDispatchTouchEvent(MotionEvent event) {        return super.dispatchTouchEvent(event);    }}

FrameLayout继承ViewGroup

6、ViewGroup

@Override    public boolean dispatchTouchEvent(MotionEvent ev) {        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);        }        // If the event targets the accessibility focused view and this is it, start        // normal event dispatch. Maybe a descendant is what will handle the click.        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {            ev.setTargetAccessibilityFocus(false);        }        boolean handled = false;        if (onFilterTouchEventForSecurity(ev)) {            final int action = ev.getAction();            final int actionMasked = action & MotionEvent.ACTION_MASK;            // Handle an initial 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();            }            // Check for interception.            final boolean intercepted;            if (actionMasked == MotionEvent.ACTION_DOWN                    || mFirstTouchTarget != null) {                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;                if (!disallowIntercept) {                    intercepted = onInterceptTouchEvent(ev);                    ev.setAction(action); // restore action in case it was changed                } else {                    intercepted = false;                }            } else {                // There are no touch targets and this action is not an initial down                // so this view group continues to intercept touches.                intercepted = true;            }            // If intercepted, start normal event dispatch. Also if there is already            // a view that is handling the gesture, do normal event dispatch.            if (intercepted || mFirstTouchTarget != null) {                ev.setTargetAccessibilityFocus(false);            }            // Check for cancelation.            final boolean canceled = resetCancelNextUpFlag(this)                    || actionMasked == MotionEvent.ACTION_CANCEL;            // Update list of touch targets for pointer down, if needed.            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;            TouchTarget newTouchTarget = null;            boolean alreadyDispatchedToNewTouchTarget = false;            if (!canceled && !intercepted) {                // If the event is targeting accessiiblity focus we give it to the                // view that has accessibility focus and if it does not handle it                // we clear the flag and dispatch the event to all children as usual.                // We are looking up the accessibility focused host to avoid keeping                // state since these events are very rare.                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()                        ? findChildWithAccessibilityFocus() : null;                if (actionMasked == MotionEvent.ACTION_DOWN                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {                    final int actionIndex = ev.getActionIndex(); // always 0 for down                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)                            : TouchTarget.ALL_POINTER_IDS;                    // Clean up earlier touch targets for this pointer id in case they                    // have become out of sync.                    removePointersFromTouchTargets(idBitsToAssign);                    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
preorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, 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; } if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { 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); if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. 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(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } // The accessibility focus didn't handle the event, so clear // the flag and do a normal dispatch to all children. ev.setTargetAccessibilityFocus(false); } if (preorderedList != null) preorderedList.clear(); } if (newTouchTarget == null && mFirstTouchTarget != null) { // Did not find a child to receive the event. // Assign the pointer to the least recently added target. newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } } // 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); } else { // Dispatch to touch targets, excluding the new touch target if we already // dispatched to it. Cancel touch targets if necessary. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } // Update list of touch targets for pointer up or cancel, if needed. if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { resetTouchState(); } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { final int actionIndex = ev.getActionIndex(); final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); } } if (!handled && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); } return handled; }

经过上面的步骤MotionEvent便从Activity传递给Window,再传递给DecorView,也就是ViewGroup

 

7、MotionEvent在View直接传递

1)首先ViewGroup的dispatchTouchEvent首先接收到点击事件,如果当前的onInterceptTouchEvent返回true表示当前ViewGroup会处理点击事件,这时候其onTouchEvent会被调用,如果onInterceptTouchEvent返回false,表示事件将传递给其子view处理,从上面的ViewGroup的dispatchTouchEvent函数源码中可以查询出来。

2)默认情况下ViewGroup的onInterceptTouchEvent方法返回false,表示不会消耗事件。

3)View没有onInterceptTouchEvent函数,默认消耗消耗事件,onTouchEvent会被调用。

4)如果都不会消耗事件,则最终Activity会处理点击事件

伪代码如下

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

8、View

/**     * 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)) {            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {                result = true;            }            //noinspection SimplifiableIfStatement            ListenerInfo li = mListenerInfo;//(mark 1 start==========>)            if (li != null && li.mOnTouchListener != null                    && (mViewFlags & ENABLED_MASK) == ENABLED                    && li.mOnTouchListener.onTouch(this, event)) {                result = true;            }            if (!result && onTouchEvent(event)) {                result = true;            }        }//(mark 1 end==========>)        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;    }

9、OnTouchListener

从上面的mark处我们看到如果OnTouchListener有设置,那么li.mOnTouchListener.onTouch会被调用,也就是view如果设置了OnTouchListener的是那么onTouch会被调用,如果onTouch返回true那么事件被其处理,返回false则View的onTouchEvent才会被调用。

也就是OnTouchListener的优先级高于onTouchEvent,之后才是onClickListener

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

转载于:https://my.oschina.net/goboy/blog/1930376

你可能感兴趣的文章
开源倾情奉献:基于.NET打造IP智能网络视频监控系统(二)基础类库介绍
查看>>
sublime text3 自动编译php 适合用于简单的php文件执行
查看>>
git分支管理
查看>>
玩转Google开源C++单元测试框架Google Test系列(gtest)之七 - 深入解析gtest
查看>>
C#代码生成工具:文本模板初体验 Hello,World!
查看>>
[WinAPI] API 11 [创建目录]
查看>>
(C#)多线程-BackgroundWorker组件
查看>>
设计工作-Axure
查看>>
6.4. branch
查看>>
win7下Qt5使用mysql C++编程配置
查看>>
OK335xS PMIC(TPS65910A3A1RSL) reset
查看>>
CentOS6.5下安装JDK
查看>>
Webdis内部解析
查看>>
21.7. SNMP
查看>>
[LeetCode] Plus One
查看>>
Android SDK 中文 (56) —— ViewFlipper
查看>>
你必须要知识的架构知识~第四章 抽象类展现代码的层次感
查看>>
《基于MFC的OpenGL编程》Part 6 Keyboard and Mouse Control
查看>>
Java 实现的各种经典的排序算法小Demo
查看>>
DZ验证码不显示等
查看>>