美文网首页Android开发Android 长知识小技巧
ScrollView嵌套ListView手势冲突

ScrollView嵌套ListView手势冲突

作者: 风风风筝 | 来源:发表于2016-09-24 08:39 被阅读1795次

    外层ScrollView,内嵌ListView,都是垂直方向。采用内部拦截法,实现ListView能滚动时则让ListView处理,当ListView滑到顶部或者底部不能滑动时让ScrollView处理


    布局
    上面有一段文本,中间是ListView,下面还有一段文本

    <?xml version="1.0" encoding="utf-8"?>
    <org.icegeneral.scroll.MyScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/sv"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
    
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="AAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA" />
    
            <org.icegeneral.scroll.MyListView
                android:id="@+id/lv"
                android:layout_width="match_parent"
                android:layout_height="500dp"
                android:background="#888888">
    
            </org.icegeneral.scroll.MyListView>
    
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="BBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB" />
        </LinearLayout>
    </org.icegeneral.scroll.MyScrollView>
    

    MyScrollView
    ACTION_DOWN必须让给ListView,ListView才能收到ACTION_MOVE,ListView才能判断自己是否还能滚动
    第二点要注意的是:因为ACTION_DOWN让给ListView,那么ACTION_DOWN就无法进入ScrollView的onTouchEvent, 但是ScrollView的滚动需要在ACTION_DOWN阶段做一些准备,所以主动调用了一次

    public class MyScrollView extends ScrollView {
    
        public MyScrollView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                onTouchEvent(ev);
                return false;
            }
            return true;
        }
    
    }
    

    MyListView

    public class MyListView extends ListView {
    
        public MyListView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        private float lastY;
    
        @RequiresApi(api = Build.VERSION_CODES.KITKAT)
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                getParent().getParent().requestDisallowInterceptTouchEvent(true);
            } else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
                if (lastY > ev.getY()) {
                    // 如果是向上滑动,且不能滑动了,则让ScrollView处理
                    if (!canScrollList(1)) {
                        getParent().getParent().requestDisallowInterceptTouchEvent(false);
                        return false;
                    }
                } else if (ev.getY() > lastY) {
                    // 如果是向下滑动,且不能滑动了,则让ScrollView处理
                    if (!canScrollList(-1)) {
                        getParent().getParent().requestDisallowInterceptTouchEvent(false);
                        return false;
                    }
                }
            }
            lastY = ev.getY();
            return super.dispatchTouchEvent(ev);
        }
    
    }
    

    Activity

    protected void onCreate(Bundle savedInstanceState) {
        ....
        scrollView.smoothScrollTo(0, 0);
    }
    

    ACTION_DOWN的特殊性
    解释下为什么ListView的ACTION_UP无需调用
    getParent().getParent().requestDisallowInterceptTouchEvent(false)
    来看下ViewGroup源码

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        ...
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;
    
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                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 {
                intercepted = true;
            }
            ...
        }
        ...
    }
    
    private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; //移除FLAG_DISALLOW_INTERCEPT
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }
    

    所以如果是ACTION_DOWN,会调用resetTouchState(),移除FLAG_DISALLOW_INTERCEPT,所以ACTION_DOWN时,parent一定会进入onInterceptTouchEvent

    其他
    其实现在都用RecyclerView代替ListView,ScrollView嵌套RecyclerView时,对于手势已经支持得很好,不必自己处理冲突。这个在demo里也有写,做个对比

    代码

    相关文章

      网友评论

      • 38c538a73803:这也敢叫完美,就设置了一个固定高度。完全不兼容。假如就一条数据,你设那么大高度作甚,显得一片空白吗。而且listview不可滑动的时候应该是滑动scrollview,完全没有,。唉,失败的不能再失败了。
      • AlphaGao:非常感谢博主,解决了我的问题,不过你在 ScrollView 的 onInterceptTouchEvent 的末尾 return true 会导致 ScrollView 里面的其他 View 无法再接收到 Event,所以还是应该改成 return super.onInterceptTouchEvent(ev) ,再次感谢
      • 皮球二二:博主你好,如果你不在up里面去将request设置成false的话,scrollview上的点击事件就不能响应了
        风风风筝:@r17171709 刚才解释得有点牵强,具体原因在文章里补上了,其实是ACTION_DOWN会移除disallowIntercept

      本文标题:ScrollView嵌套ListView手势冲突

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