美文网首页
Android-从重叠view响应问题到安卓事件分发机制

Android-从重叠view响应问题到安卓事件分发机制

作者: 九号锅炉 | 来源:发表于2019-01-02 16:42 被阅读0次

    重叠view事件响应问题

    工作当中遇到一个需求:有两个重叠并且全屏的Framelayout,交互逻辑是点击上层的button使得上层消失,下层可见;再点击下层使得下层消失上层出现。但是发现当点击上层非button区域时,下层也会响应点击事件,但是点击button区域则不会。这意味着点击事件穿过上层到达下层。对于安卓事件机制一知半解的我无从下手,上网搜了一下发现在上层FrameLayout下增加:

    android:clickable="true"
    

    这样的确可以保证上层点击事件不会到达下层,可是这是为什么呢?最初步的猜想是当上层FrameLayout的clickable属性为true时,点击事件已经被上层拦截了。这个和当你点击button时响应onclick()时的原理是不是一样?这就需要从源码分析了。

    安卓事件分发机制基础

    分析源码前先大致了解一下安卓事件分发机制。

    • 当点击事件到达一个viewgroup,首先会调用dispatchTouchEvent。如果该viewgroup的onInterceptTouchEvent返回true,表明拦截该事件,接着事件就会交给viewgoup来处理,意味着它的onTouchEvent会进行事件的处理。如果返回false,表明不拦截当前事件。这时事件会继续传递给它的子view,遍历调用子view的dispatchTouchEvent,直到事件被某个子view处理。如果所有子view都没有处理该事件,则事件会传递给父容器的onTouchEvent。以此类推,如果所有元素都不处理该事件,事件最终会传递给Activity处理,即Activity的onTouchEvent;
    • 当一个view调用dispatchTouchEvent进行事件处理时,由于没有子元素无法进行向下传递只能自己处理。首先会判断是否有设置OnTouchListener,如果OnTouchListener的onTouch返回true,那么onTouchEvent就不会调用。
    /**
         * 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) {
            ...
            boolean result = false;
            if (onFilterTouchEventForSecurity(event)) {
                if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                    result = true;
                }
                //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;
                }
            }
            ...
            return result;
        }
    
    • 当view调用onTouchEvent处理事件时,从代码中可以看出只要view的Clickable和long_clickable中有一个为true,那么它就会消耗这个事件,即onTouchEvent返回true.
    public boolean onTouchEvent(MotionEvent event) {
       ...
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                    if ((mPrivateFlags & 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 (!mHasPerformedLongPress) {
                            // 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();
                                }
                            }
                        }
       ..
    }
    

    结论

    综合上述对安卓事件分发的源码分析知道,当在上层FrameLayout中将clickable设置为true时,其实就是
    当点击事件到达上层framelayout时,在onTouchEvent中对事件进行拦截,使得点击事件不会继续向下传递。当然,除了这个方法以外,也可以重写FrameLayout的onInterceptTouchEvent,在将这个函数的返回值为true,也可以实现拦截事件的作用。

    相关文章

      网友评论

          本文标题:Android-从重叠view响应问题到安卓事件分发机制

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