天天看点

ScrollView和ListView滑动冲突

android5.0推出了RecyclerView,在开发中ListView的使用频率就很少了,这里讲ScrollView和ListView滑动冲突主要目的是ViewGroup的事件处理、事件分发及事件拦截。

ScrollView嵌套ListView,当然这里的ListView给的是固定高度,如果不是给的固定高度,给的是match_parent或者wrap_content的话ListView只会显示一个条目的高度;给ListView固定高度后,ScrollView和ListView不做任何处理,会发现ScrollView可以滑动,ListView不可以滑动。

原因:

ScrollView接到滑动事件后直接拦截掉了,并没有给到ListView。

解决:

让ScrollView不拦截掉ListView的滑动事件

解决的方式有很多种,这里就说下两种比较简单的方式;

方式一:

自定义ScrollView,重写dispatchTouchEvent方法:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    // 不要拦截  true为不要拦截   false为拦截
    requestDisallowInterceptTouchEvent(true);
    return super.dispatchTouchEvent(ev);
}
           

方式二:

自定义ScrollView,重写onInterceptTouchEvent方法:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    //返回false为不要拦截  返回true为拦截
    return false;
}
           

这样子就解决掉ScrollView和ListView滑动冲突了

ScrollView和ListView滑动冲突

ViewGroup的事件处理、事件拦截、事件分发主要涉及到这几方法:

dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent、onTouch等方法,首先触发的是dispatchTouchEvent方法;

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ...
    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) {
            //如果是一个新的ACTION_DOWN手势,就会将之前的ACTION_DOWN手势清除掉
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        // Check for interception.开始检测事件拦截
        final boolean intercepted;
        //第一次走这里的时候mFirstTouchTarget是null
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != ;
            if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    // 将改变后的动作进行保存
                    ev.setAction(action); 
            } else {
                intercepted = false;
            }
        } else {
            intercepted = true;
        }

        ...
        // Check for cancelation.检测是否是放弃的
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != ;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        //如果事件被拦截掉了就不会走这里,也就是子view不会响应事件
        if (!canceled && !intercepted) {
            //canceled为false且事件没有被拦截
            ...
            resetCancelNextUpFlag(child);
            //dispatchTransformedTouchEvent 子孩子的事件处理 
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                // Child wants to receive touch within its bounds.
                mLastTouchDownTime = ev.getDownTime();
                    ...
                    break;
            }

            ...
        }
        ...
        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
            //dispatchTransformedTouchEvent 子孩子的事件处理 
            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;
                    //dispatchTransformedTouchEvent 子孩子的事件处理 
                    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;
            }
        }
        ...
    //默认返回false
    return handled;
}
           

所以dispatchTouchEvent方法默认返回的是false;在检测事件拦截的时候会调用到onInterceptTouchEvent方法;

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;
}
           

onInterceptTouchEvent默认返回的也是false,代表不拦截事件;

但是onInterceptTouchEvent调不调用取决于下面这句代码:

//是否拒绝检测事件拦截
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != ;
           

如果disallowIntercept为true,代表拒绝检测事件拦截;disallowIntercept为false,代表需要检测事件拦截,所以不想父view拦截子view的事件的话,可以重写父view的dispatchTouchEvent方法并在return super.dispatchTouchEvent(ev);之前调用requestDisallowInterceptTouchEvent(true);将disallowIntercept值改true,这样就不会去检测事件拦截了。

还有就是将onInterceptTouchEvent返回值改为false,这样父view也不会去拦截子view的事件;

如果父view没有拦截子view的事件,就会去调用dispatchTransformedTouchEvent方法,

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    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) {
        //如果没有子view或者子view为null就会去调用父类也就是view的dispatchTouchEvent方法
            handled = super.dispatchTouchEvent(event);
        } else {
        //否则就调用子view的dispatchTouchEvent方法
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }

    ...
    return handled;
}
           

上面这个大致就是ViewGroup的事件处理,但是会发现ScrollView和RecyclerView并没有滑动的冲突,其实RecyclerView extends ViewGroup,并重写了onInterceptTouchEvent等方法已经做了处理,所以才不会冲突。

关联播客:

View的事件分发和处理

http://blog.csdn.net/wangwo1991/article/details/74858179

ViewGroup的事件拦截、事件分发、事件处理

http://blog.csdn.net/wangwo1991/article/details/74966355