Androi事件分发( 二),解决事件冲突

通过《Android事件分发(一)》我们了解了Android的事件分发机制,不熟悉的,可以回头再去看一遍。

有了这方面的知识基础,我们来解决实际研发的过程中,老生常谈的事件冲突问题。

解决这类问题,其实是有方法的。下面先介绍这两种方法,然后再结合以上三种问题,分别进行讲解

方法一:外部拦截法

顾名思义,外部拦截法,就是指在外部进行拦截,让事件不传递下去。其实就是对外部的dispatchTouchEvent和onInterceptTouchEvent动手脚

方法二:内部拦截法

事件冲突大致分为三种

1.方向一致

我们用ScrollView嵌套ListView,同为上下方向的滑动。

我们假设一个需求:

1.listView向下滑时,如果还没有滑到listView的头部的话,listView继续滑动;否则ScrollView滑动。

2.listView向上滑时,如果还没有滑到listView的底部的话,listView继续滑动;否则ScrollView滑动。

<com.example.weights.CustomScrollView
    android:id="@+id/scrollView"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:background="#0000ff"
            android:text="头部" />

        <ListView
            android:id="@+id/list_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#0000ff"
            app:adapter="@{da}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:background="#0000ff"
            android:text="尾部" />
1.1 外部拦截法

重写ScrollView的onInterceptTouchEvent

int tempY;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    boolean intercept = false;//表示是否拦截
    int y = (int) ev.getY();
    switch (ev.getAction()){
        case MotionEvent.ACTION_DOWN:
            tempY = y;//记录按下时的Y坐标
            intercept = super.onInterceptTouchEvent(ev);//这里返回的是false。不然,不会调用其他Action方法
            break;
        case MotionEvent.ACTION_MOVE:
            if(null != listView){
                if(listView.getFirstVisiblePosition() == 0 && y > tempY){//list显示第一个,而且是向下滑
                    intercept = true;//ScrollView拦截
                    break;
                }else if(listView.getLastVisiblePosition() == listView.getCount() - 1 && y < nowY){//list显示最后一个,而且是向上滑
                    intercept = true;
                    break;
                }
                intercept = false;
            }
            break;
        case MotionEvent.ACTION_UP:
            intercept = false;
            break;
        default:
            break;
    }
    return intercept;
}

所谓外部拦截法,其实就是外部父容器做处理。在这里,当父容器ScrollView拦截事件后,就会调用父容器ScrollView自己的onTouchEvent,即ScrollView处理滚动,否则交给ListView消费。

2.1 内部拦截法
int nowY;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {//内部拦截法
    int y = (int) ev.getY();
    switch (ev.getAction()){
        case MotionEvent.ACTION_DOWN:
            nowY = y;
            getParent().requestDisallowInterceptTouchEvent(true);//禁止父容器拦截事件
        break;
        case MotionEvent.ACTION_MOVE:
            View topView = getChildAt(0);
            View bottomView = getChildAt(getChildCount() - 1);
            if(y > nowY && getFirstVisiblePosition() == 0 && null != topView && topView.getTop() == 0){//往下滑,并且是第一项,父容器拦截
                getParent().requestDisallowInterceptTouchEvent(false);
            }else if(y < nowY && getLastVisiblePosition() == getCount() - 1 && null != bottomView && bottomView.getBottom() == getHeight()){//往上滑,并且是第后一项,父容器拦截
                getParent().requestDisallowInterceptTouchEvent(false);
            }
            break;
    }
    return super.dispatchTouchEvent(ev);
}

所谓内部拦截法,其实就是在内部子容器里,通过requestDisallowInterceptTouchEvent方法,控制父容器是否拦截事件。requestDisallowInterceptTouchEvent是ViewParent接口的一个抽象方法,实现是在ViewGroup里面

@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
        // We're already in this state, assume our ancestors are too
        return;
    }

    if (disallowIntercept) {
        mGroupFlags |= FLAG_DISALLOW_INTERCEPT;//注意这里
    } else {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }

    // Pass it up to our parent
    if (mParent != null) {
        mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
    }
}

注意mGroupFlags这个变量,回想下,在ViewGroup的dispatchTouchEvent方法里面有这么一段代码

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

这里说明一点,在使用内部拦截法的时候,需要重写父容器的onInterceptTouchEvent,

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if(ev.getAction() == MotionEvent.ACTION_DOWN){//这里需要返回false,因为如果这里返回true,事件就不会往下传递,导致子View无法接收事件
        return false;
    }else{//其他事件拦截。已配合子容器调用requestDisallowInterceptTouchEvent使用
        return true;
    }
}

这里的ScrollView没有重写,是因为ScrollView本身已经重写了onInterceptTouchEvent方法

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    ...
    final int action = ev.getAction();
    if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
        return true;
    }

    if (super.onInterceptTouchEvent(ev)) {
        return true;
    }
...
    return mIsBeingDragged;
}

2.方向不一致

典型的例子,就是ViewPager和ListView。不过,ViewPager已经做了处理,有兴趣的可以看看ViewPager的源码。

解决套路还是外部拦截和内部拦截,在判断条件上,需要做些调整。归根结底还是根据X和Y的滑动距离来判断是否消费事件。

3.多层嵌套,方向不一致

解决套路还是外部拦截和内部拦截,根据需要,把之拆开成以上两种情况,然后解决,这里就不多做介绍了,有兴趣的朋友,可以自行尝试。


胖子总结

  • 自己写个demo,试一试,比看十遍文章都有用
  • 温馨提示:点成线,线成面,切勿贪心,否则一脸懵逼
  • 胖子有什么理解错误的,欢迎大家指出来,一起讨论、学习、进步
  • 期待胖子为了巩固、加深基础的《Java虚拟机(JVM)》
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值