Android:SwipeRefreshLayout和ViewPager滑动冲突的原因和正确的解决方式

Android:SwipeRefreshLayout和ViewPager滑动冲突的原因和正确的解决方式一、前言急着解决问题的直接看博文的最后面吧,或者点这里跳转过去,正确的解决方式就在那。虽然SwipeRefreshLayout出来已经很久了,但是知道今天我才第一次使用。然后发现两个问题:1.SwipeRefreshLayout会吃掉ViewPager的滑动事件。2.SwipeRefreshLayout需要套在ScrollView和ListView上的时候才表现的比较友好,在其他Vi

大家好,又见面了,我是你们的朋友全栈君。

BUG修复

2016.01.21 用几部真机测试,发现有些手机,手指没有滑动,move也一直执行。这回导致我们的判断出现一些问题。现在已经修复,加入了TouchSlop判断。

2017.6.16 修改文章中的一些错误
##一、前言
急着解决问题的直接看博文的最后面吧,或者点这里跳转过去,正确的解决方式就在那。

虽然SwipeRefreshLayout出来已经很久了,但是知道今天我才第一次使用。
然后发现两个问题:

  1. SwipeRefreshLayout会吃掉ViewPager的滑动事件。
  2. SwipeRefreshLayout需要套在ScrollView和ListView上的时候才表现的比较友好,在其他ViewGroup上有点问题,不知道为什么,到时候去看下源码。(这问题已经被google修复)

今天我只说第一个问题:
很明显如果是往左下或右下滑动的时候,事件就会被SwipeRefreshLayout吃掉。但是平移滑动或者往右上左上滑动就没问题。
这里写图片描述

二、目前网上流传的解决方式

我网上找解决方法的时候,发现无非都是两种方式。
1、监听ViewPager的OnTouch事件,滑动的时候禁用swipeRefreshLayout

mViewPager.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                mSwipeRefreshLayout.setEnabled(false);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mSwipeRefreshLayout.setEnabled(true);
                break;
        }
        return false;
    }
})

2、继承ViewPager,请求父控件不要拦截ViewPager事件

public class CustomViewPager extends ViewPager {

    public CustomViewPager(Context ctx, AttributeSet attrs) {
        super(ctx, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean in = super.onInterceptTouchEvent(ev);
        if (in) {
            getParent().requestDisallowInterceptTouchEvent(true);
            this.requestDisallowInterceptTouchEvent(true);
        }
        return false;
    }
}

这两种方法都会导致一个问题, 在ViewPager无法刷新。
就像这样:
第一种方式,偶尔能滑动,偶尔滑不动。为什么会这样,继续往下看,带你分析源码。
这里写图片描述

第二种方式,连偶尔都不要想,不管在真机还是模拟器,都无法刷新了,这里就不演示了。具体原因请看我的另一篇博客,看懂以后妈妈再也不用担心你的事件分发了。
Android的事件分发源码分析,告别事件冲突

————2017.06.16————
随着版本更新,android的事件分发的机制也原来越完善,老的文章已经不适合了,我已经不知道是我当时写错了还是SwipeRefreshLayout更改了,下面补充下第二种方式。
这里要感谢一下28楼的”GEASS123″网友的提醒.

第二种方式
第二种方式不起作用的原因是,SwipeRefreshLayout重写了requestDisallowInterceptTouchEvent方法

@Override
    public void requestDisallowInterceptTouchEvent(boolean b) {
        // if this is a List < L or another view that doesn't support nested
        // scrolling, ignore this request so that the vertical scroll event
        // isn't stolen
        if ((android.os.Build.VERSION.SDK_INT < 21 && mTarget instanceof AbsListView)
                || (mTarget != null && !ViewCompat.isNestedScrollingEnabled(mTarget))) {
            // Nope.
        } else {
            super.requestDisallowInterceptTouchEvent(b);
        }
    }

因为事件是先从上层往下层传递的,既然ViewPager的事件被吃掉了,那么肯定是在SwipeRefreshLayout中被消费了。
我们去看看SwipeRefreshLayout的源码。

  1. 先看dispatch方法,发现重写此方法。
  2. 然后看onIntercept方法,发现是在这里拦截了。那么onTouchEvent方法就不用看了。下面我们就来分析一下onInterceptTouchEvent方法的源码。

三、SwipeRefreshLayout的onInterceptTouchEvent源码分析。

有目的性的分析,我们只需要分析和事件冲突相关的源码,所以只注释的关键部分。

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
	    // 确保有SwipeRefreshLayout有Target
	    // 遍历所有child,第一个child就是target(除了刷新的那个圈)。
	    // 这就是为啥SwiperefreshLayout只能有一个child的原因。
	    // 先无视掉这句代码,和我们分析目的无关
        ensureTarget();
	
        final int action = MotionEventCompat.getActionMasked(ev);

		// 这个也无视吧, mReturningToStart一直都是false的,源码中并没有赋值
		// 估计原本用于判断是否正在刷新中,后来用了其他方式判断。(猜测)
        if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
            mReturningToStart = false;
        }

        if (!isEnabled() || mReturningToStart || canChildScrollUp() || mRefreshing) {
            // Fail fast if we're not in a state where a swipe is possible
            return false;
        }

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true);
                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
                // 一个记录是否正在进行拖拽的标记,初始化false。
                mIsBeingDragged = false;
                // 获取按下的Y轴位置
                final float initialDownY = getMotionEventY(ev, mActivePointerId);
                if (initialDownY == -1) {
                    return false;
                }
                mInitialDownY = initialDownY;
                break;

            case MotionEvent.ACTION_MOVE:
                if (mActivePointerId == INVALID_POINTER) {
                    Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");
                    return false;
                }
				// 获取当前的Y轴位置
                final float y = getMotionEventY(ev, mActivePointerId);
                if (y == -1) {
                    return false;
                }
                // 获取手指在Y轴的滑动距离
                final float yDiff = y - mInitialDownY;
                // 如果滑动距离大于mTouchSlop(不同手机的值不同,一般为8px)
                // 并且当前不是在拖拽中
                if (yDiff > mTouchSlop && !mIsBeingDragged) {
                    mInitialMotionY = mInitialDownY + mTouchSlop;
                    // 设置当前拖拽标记为true
                    mIsBeingDragged = true;
                    mProgress.setAlpha(STARTING_PROGRESS_ALPHA);
                }
                break;

            case MotionEventCompat.ACTION_POINTER_UP:
                onSecondaryPointerUp(ev);
                break;

            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
	            //当手指抬起的时候设置拖拽标记为false;
                mIsBeingDragged = false;
                mActivePointerId = INVALID_POINTER;
                break;
        }
		// 如果是拖拽中,拦截事件,否则不拦截。
        return mIsBeingDragged;
    }

看不懂的可以再看几遍,主要是mIsBeingDragged这个参数的值是否为true。

四、使用第一种方式,偶尔能拉下小球的原因

1、那么我们来分析下,为什么使用第一种方式的时候,偶尔将小球给拉下来。
首先看这里

// 获取手指在Y轴的滑动距离
                final float yDiff = y - mInitialDownY;
                // 如果滑动距离大于mTouchSlop(不同手机的值不同,一般为8px)
                // 并且当前不是在拖拽中
                if (yDiff > mTouchSlop && !mIsBeingDragged) {
                    mInitialMotionY = mInitialDownY + mTouchSlop;
                    // 设置当前拖拽标记为true
                    mIsBeingDragged = true;
                    mProgress.setAlpha(STARTING_PROGRESS_ALPHA);
                }

当滑动距离大于mTouchSlop的时候才拦截事件。
也就是说

  1. 如果我Y轴滑动距离没有大于这个mTouchSlop,mIsBeingDragged为false,事件就不拦截了,会继续往下分发,那么ViewPager就响应到了move事件,并且将SwipeRefreshLayout设置成Disable了。这就是为什么往下滑动为什么总是不能将小球拉下来的原因。
  2. 如果Y轴滑动距离大于这个mTouchSlop,那么事件就拦拦截了自己处理,小球就可以被拉下来了。这也是偶尔能将小球拉下来的原因。

什么时候Y轴滑动距离会大于mTouchSlop而不被ViewPager响应到事件呢。
要知道两次Touch之间也是有个很短的响应时间的,只要在这个时间内,Y轴滑动距离大于mTouchSlop就可以了,这时候事件就被拦截了,ViewPager没机会响应到move事件,从而不会禁用掉SwipeRefreshLayout。

我们来测试一下,超级快速的往下滑动。
可以看到,慢慢滑动的时候,小球无法拉下来,如果快速下拉,小球就出来了。
这也是因为在模拟器上比较卡的原因,如果在真机上,要更快一些才可以。
这里写图片描述

五、解决方式

写了一大堆有的没的才到了重点,别着急,我觉得看完上面内容会对以后解决相关问题会有帮助,百度谷歌也不是所有问题都能搜的出来。

重写SwipeRefreshLayout的onIntercept方法就可以很简单的解决了。
思路:

  1. 因为下拉刷新,只有纵向滑动的时候才有效,那么我们就判断此时是纵向滑动还是横向滑动就可以了。
  2. 纵向滑动就拦截事件,横向滑动不拦截。
  3. 怎么判断是纵向滑动还是横向滑动,只要判断Y轴的移动距离大于X轴的移动距离那么就判定为纵向滑动就行了。

以下就是重写后的SwipeRefreshLayout,直接复制到项目就可以使用了。

/**
 * Created by AItsuki on 2016/1/20.
 */
public class VpSwipeRefreshLayout extends SwipeRefreshLayout {

    private float startY;
    private float startX;
    // 记录viewPager是否拖拽的标记
    private boolean mIsVpDragger;
    private final int mTouchSlop;

    public VpSwipeRefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                // 记录手指按下的位置
                startY = ev.getY();
                startX = ev.getX();
                // 初始化标记
                mIsVpDragger = false;
                break;
            case MotionEvent.ACTION_MOVE:
                // 如果viewpager正在拖拽中,那么不拦截它的事件,直接return false;
                if(mIsVpDragger) {
                    return false;
                }

                // 获取当前手指位置
                float endY = ev.getY();
                float endX = ev.getX();
                float distanceX = Math.abs(endX - startX);
                float distanceY = Math.abs(endY - startY);
                // 如果X轴位移大于Y轴位移,那么将事件交给viewPager处理。
                if(distanceX > mTouchSlop && distanceX > distanceY) {
                    mIsVpDragger = true;
                    return false;
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                // 初始化标记
                mIsVpDragger = false;
                break;
        }
        // 如果是Y轴位移大于X轴,事件交给swipeRefreshLayout处理。
        return super.onInterceptTouchEvent(ev);
    }
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/152956.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • linux下载文件命令sz_linux下载整个文件夹命令

    linux下载文件命令sz_linux下载整个文件夹命令wget是linux下一个从网络上自动下载文件的常用自由工具。它支持HTTP,HTTPS和FTP协议,可以使用HTTP代理。一般的使用方法是:wget+空格+参数+要下载文件的url路径,例如:1wget http://www.linuxsense.org/xxxx/xxx.tar.gz-c参数,这个也非常

  • 数仓拉链表[通俗易懂]

    数仓拉链表[通俗易懂]拉链表一丶什么是拉链表拉链表是一种数据模型,主要是针对数据仓库设计中表存储数据的方式而定义的,顾名思义,所谓拉链,就是记录历史。记录一个事物从开始,一直到当前状态的所有变化的信息。拉链表可以避免按每一天存储所有记录造成的海量存储问题,同时也是处理缓慢变化数据的一种常见方式。百度百科的解释:拉链表是维护历史状态,以及最新状态数据的一种表,拉链表根据拉链粒度的不同,实际上相当于快照,只不过做了优化,去除了一部分不变的记录,通过拉链表可以很方便的还原出拉链时点的客户记录。二丶拉链表的产生背景在数据仓库的

    2022年10月16日
  • 我是如何学习和工作的(1) – 番茄工作法(1)[通俗易懂]

    我是如何学习和工作的(1) – 番茄工作法(1)[通俗易懂]我是如何学习和工作的(1) – 番茄工作法(1)

  • 电容分类_电解电容和薄膜电容的区别

    电容分类_电解电容和薄膜电容的区别一、按照功能  1.名称:聚酯(涤纶)电容   符号:(CL)  电容量:40p–4μ  额定电压:63–630V  主要特点:小体积,大容量,耐热耐湿,稳定性差  应用:对稳定性和损耗要求不高的低频电路  2.名称:聚苯乙烯电容  符号:(CB)  电容量:10p–1μ  额定电压:100V–30KV

  • ping不是内部或外部命令也不是可运行win10_电脑关掉防火墙仍然ping不通

    ping不是内部或外部命令也不是可运行win10_电脑关掉防火墙仍然ping不通前段时间配置Java环境将Path配置错误了计算机右键点击属性高级系统设置->环境变量系统变量编辑Path添加语句;C:\Windows\System32重新打开cmdpingwww.baidu.comnetstat-a出现上图表示配置成功…

    2022年10月30日
  • plc编程小技巧_plc编程入门培训

    plc编程小技巧_plc编程入门培训PLC是一种在工业中被广泛应用的自动控制元件。然而PLC应用与编程人才短缺已经成为制约我国自动控制领域发展的瓶颈。通过学习PLC可编程控制器程序设计使学员掌握可编程序控制器的工作原理和PLC控制系统硬件软件的设计方法,可编写复杂的PLC程序,为从事PLC控制系统的设计、调试和改造工作打下基础。可以独立承担相关项目的设计与开发。让您由一位电工升级为货真价实的工程师![1]随着现代工业设备自动化,越来越多的工厂设备将采用PLC人机界面等自动化器件来控制,因此设备自动化程度越来越高。对设备的维护人员的技术要求越

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号