美文网首页
Android View触摸事件分发机制及冲突解决

Android View触摸事件分发机制及冲突解决

作者: M45ter | 来源:发表于2020-01-07 15:38 被阅读0次

    Activity中View的加载

    Activity加载布局时调用setContentView方法来加载布局。看下源码中的代码(android-23,不同版本可能存在差异)

        public void setContentView(@LayoutRes int layoutResID) {
            getWindow().setContentView(layoutResID);
            initWindowDecorActionBar();
        }
    

    找到getWindow方法

        public Window getWindow() {
            return mWindow;
        }
    

    找到mWindow的创建

    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) {
            attachBaseContext(context);
    
            mFragments.attachHost(null /*parent*/);
    
            mWindow = new PhoneWindow(this);
        ...
    }
    

    那么找到PhoneWindowsetContentView方法

        public void setContentView(int layoutResID) {
            if (mContentParent == null) {
                installDecor();//关键代码
            } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                mContentParent.removeAllViews();
            }
    
            if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                        getContext());
                transitionTo(newScene);
            } else {
                mLayoutInflater.inflate(layoutResID, mContentParent);
            }
            mContentParent.requestApplyInsets();
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                cb.onContentChanged();
            }
        }
    

    这里关键是mContentParent创建,把view加载进去,关键代码installDecor方法

    private void installDecor() {
            if (mDecor == null) {
                mDecor = generateDecor();//关键1
                mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
                mDecor.setIsRootNamespace(true);
                if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                    mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
                }
            }
            if (mContentParent == null) {
                mContentParent = generateLayout(mDecor);//关键2
                ...
            }
        ...
    }
    

    这里主要是mDecormContentParent的创建,看注释关键1、关键2代码

        protected DecorView generateDecor() {
            return new DecorView(getContext(), -1);
        }
    

    DecorViewPhoneWindow的内部类,继承FrameLayout

        protected ViewGroup generateLayout(DecorView decor) {
            ...
            // Inflate the window decor.
    
            int layoutResource;
            int features = getLocalFeatures();
            // System.out.println("Features: 0x" + Integer.toHexString(features));
            if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
                layoutResource = ...
            } else if(...) {
                layoutResource = ...
            } else {
                layoutResource = R.layout.screen_simple;
            }
            mDecor.startChanging();
    
            View in = mLayoutInflater.inflate(layoutResource, null);
            decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            mContentRoot = (ViewGroup) in;
    
            ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
            ...
            return contentParent;    
        }
    

    到这里实际上就可以看出mDecor实际上是Activity的root view,根据不同的activity window features加载不同的系统layout布局,content view放在id为com.android.internal.R.id.contentFrameLayout

    Activity中的View

    View触摸事件分发机制

    类型 相关方法 Activity ViewGroup View
    事件分发 dispatchTouchEvent
    事件拦截 onInterceptTouchEvent
    事件消费 onTouchEvent

    触摸事件首先进入ActivitydispatchTouchEvent方法

        public boolean dispatchTouchEvent(MotionEvent ev) {
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                onUserInteraction();
            }
            if (getWindow().superDispatchTouchEvent(ev)) {
                return true;
            }
            return onTouchEvent(ev);
        }
    

    可知先由windowsuperDispatchTouchEvent方法处理,返回true表示已处理,如果没有处理再由ActivityonTouchEvent方法处理,查看源码梳理大致是如下一个流程

    Touch事件大致流程

    重点在ViewGroup中的dispatchTouchEvent方法

        public boolean dispatchTouchEvent(MotionEvent ev) {
            ...
            boolean handled = false;
            if (onFilterTouchEventForSecurity(ev)) {
                ...
                // Check for interception.
                final boolean intercepted;
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || mFirstTouchTarget != null) {
                    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                    if (!disallowIntercept) {
                        //如果没有重写就是false,如果重写返回true,事件就被拦截了
                        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;
                }
                ...
                TouchTarget newTouchTarget = null;
                boolean alreadyDispatchedToNewTouchTarget = false;
                if (!canceled && !intercepted) {
                    View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                            ? findChildWithAccessibilityFocus() : null;
    
                    if (actionMasked == MotionEvent.ACTION_DOWN
                            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                        ...
                        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.
                            //子view按z轴从小到大排列
                            final ArrayList<View> preorderedList = buildOrderedChildList();
                            final boolean customOrder = preorderedList == null
                                    && isChildrenDrawingOrderEnabled();
                            final View[] children = mChildren;
                            //从最上层view开始遍历 
                            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);                        
                            ...
                                //会调用child的dispatchTouchEvent方法
                                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();
                                    //记录响应处理的child
                                    newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                    alreadyDispatchedToNewTouchTarget = true;
                                    break;
                                }
                                ...
                        }
                        ...
                    }
                    ...
                }
                ...
            }
            ...
            return handled;        
        }
    

    再结合ViewdispatchTouchEvent方法

        public boolean dispatchTouchEvent(MotionEvent event) {
            ...
            boolean result = false;
            ...
            if (onFilterTouchEventForSecurity(event)) {
                //noinspection SimplifiableIfStatement
                ListenerInfo li = mListenerInfo;
                if (li != null && li.mOnTouchListener != null
                        && (mViewFlags & ENABLED_MASK) == ENABLED
                    //如果设置了mOnTouchListener优先处理
                        && li.mOnTouchListener.onTouch(this, event)) {
                    result = true;
                }
                //再由onTouchEvent处理
                if (!result && onTouchEvent(event)) {
                    result = true;
                }
            }
            ...
            return result;            
        }
    

    再看看ViewonTouchEvent方法

        public boolean onTouchEvent(MotionEvent event) {
            ...
            final int action = event.getAction();
            ...
            //满足任一条件event将被消费掉,返回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) {
                            ...
                            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)) {
                                        //如果有注册将会执行mOnClickListener的onClick方法
                                        performClick();
                                    }
                                }
                            }
                            ...
                        }
                        ...
                        break;
                    ...    
                }
                return true;            
            }
            return false;        
        }
    

    所以综合来看,触摸事件传递有两个过程:

    1. 由上往下:由顶层ViewGroupdispatchTouchEvent往下层ViewGroupViewdispatchTouchEvent传递,当某一层ViewGroup实现了onInterceptTouchEvent方法并返回true,事件传递终止,如果找到某个底层View执行onTouchEvent方法返回true,事件被消费掉,传递终止。
    2. 由下往上:底层ViewonTouchEvent方法返回false,就会执行父ViewonTouchEvent方法,如果还是false,继续往其父View传递,直到返回true为止。

    触摸冲突解决思路

    父view A中有一个子viewB,A、B触摸事件有冲突

    内部拦截

    在B中重写dispatchTouchEvent方法,在需要自己处理的事件中调用getParent().requestDisallowInterceptTouchEvent(true),在自己不处理的事件中调用getParent().requestDisallowInterceptTouchEvent(false),如果DOWN事件需要处理,则A中确保onInterceptTouchEvent也会返回false(需要时修改代码)

    外部拦截

    确保B需要处理的事件,在A中onInterceptTouchEvent都返回false

    参考《Android进阶之光》

    相关文章

      网友评论

          本文标题:Android View触摸事件分发机制及冲突解决

          本文链接:https://www.haomeiwen.com/subject/vforactx.html