一、Android事件的分发机制

这里需要了解下Andorid事件的分发机制。事件分发一般是针对一组事件,即ACTION_DOWN > ACTION_UP 或 ACTION_DOWN > ACTION_MOVE... >ACTION_UP,其中涉及事件分发的主要方法有 dispatchTouchEvent(MotionEvent event)、onInterceptTouchEvent(MotionEvent event) (ViewGroup有,View没有)、onTouchEvent(MotionEvent event),而且事件分发是由上向下传递的,即先到parent,再到child,这里简单以 ViewGroup内包裹一个View为例,大致分析下其事件的分发流程(忽略Activity,Window的传递)

事件首先会传递到ViewGroup.dispatchTouchEvent(MotionEvent event),然后会判断ViewGroup.onInterceptTouchEvent(MotionEvent event)的返回值:

1.如果返回为false,即不拦截,事件则会传递给View.dispatchTouchEvent(MotionEvent event),由于这里View没有子View了,事件则传递给该View的View.onTouchEvent(MotionEvent event)处理,如果View.onTouchEvent(MotionEvent event)没有消耗该事件,则该事件会返回给ViewGroup.onTouchEvent(MotionEvent event)处理,如果View.onTouchEvent(MotionEvent event)消耗了该事件,则该事件不会再返回给ViewGroup,本次事件分发结束。

2.如果ViewGroup.onInterceptTouchEvent(MotionEvent event)返回值为ture,即拦截事件,则事件将由ViewGroup.onTouchEvent(MotionEvent event)处理,本次事件分发结束。

3.即使事件被ViewGroup拦截了,View也可以阻止ViewGroup对事件的拦截。可以通过getParent().requestDisallowInterceptTouchEvent(true)。

大致流程图

前面说到View可以阻止ViewGroup对事件的拦截,但除了ACTION_DOWN,也就是说,对一组事件,除了ACTION_DOWN,子View可以在ViewGroup.onInterceptTouchEvent(MotionEvent event)返回ture的情况下,获取事件的处理权,下面截图android25的源代码。

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 {// There are no touch targets and this action is not an initial down// so this view group continues to intercept touches.intercepted = true;}
@Overridepublic void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {// We're already in this state, assume our ancestors are tooreturn;}if (disallowIntercept) {mGroupFlags |= FLAG_DISALLOW_INTERCEPT;} else {mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;}// Pass it up to our parentif (mParent != null) {mParent.requestDisallowInterceptTouchEvent(disallowIntercept);}}

当actionMasked == MotionEvent.ACTION_DOWN||mFirstTouchTarget !=null时,会进行disallowIntercept的判断,而disallowIntercept取决于mGroupFlags,因为FLAG_DISALLOW_INTERCEPT是一个常量0x80000,而mGroupFlags的赋值是可以通过requestDisallowInterceptTouchEvent来改变的。当requestDisallowInterceptTouchEvent(true),disallowIntercept=true,此时不走onInterceptTouchEvent(ev)的判断,intercepted=false从而达到阻止ViewGroup拦截的效果。

二、以RecyclerView下拉刷新上拉加载更多为例分析滑动冲突及解决

以recyclerView为例,也可以换listView。默认状态时红色部分为可视部分,也就是顶部和底部隐藏看不见,这里我们选择FrameLayout作为容器,因此,FrameLayout和RecyclerView就会产生同向滑动冲突。只有recyclerView内容滑动到顶部并且手势为下滑时,header才会慢慢下滑到可视范围内,或recyclerView内容滑动到底部时,并且手势为上滑,footer才会慢慢上滑到可视范围内。

    private boolean intercept;private float lastInterceptY;@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {float curInterceptY = ev.getY();switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:intercept = false;break;case MotionEvent.ACTION_MOVE:if (isRefreshing || isLoadMore) {intercept = false;} else {boolean isHeaderShow = headerParams.topMargin > -headerHeight;boolean isFooterShow = footerParams.topMargin < height;intercept = touchHelper != null && touchHelper.judgeIntercept(curInterceptY, lastInterceptY, isHeaderShow, isFooterShow, allowLoadMore);}break;case MotionEvent.ACTION_UP:intercept = false;break;}lastInterceptY = curInterceptY;return intercept;}
    @Overridepublic boolean judgeIntercept(float curInterceptY, float lastInterceptY, boolean isHeaderShow, boolean isFooterShow, boolean allowLoadMore) {boolean intercept;int firstVisiblePos = layoutManager.findFirstVisibleItemPosition();View firstView = rv.getChildAt(firstVisiblePos);if (firstVisiblePos == 0 && firstView.getTop() == 0) {intercept = curInterceptY > lastInterceptY || isHeaderShow;} else {if (allowLoadMore && layoutManager.findLastVisibleItemPosition() == layoutManager.getItemCount() - 1) {intercept = curInterceptY < lastInterceptY || isFooterShow;} else {intercept = false;}}return intercept;}

1.MotionEvent.ACTION_DOWN中,必须返回false,不拦截,一旦拦截,后续的ACTION_MOVE和ACTION_UP将直接交由FrameLayout的onTouchEvent(ev)处理

2.MotionEvent.ACTION_UP中也必须返回false,因为一组事件以ACTION_UP结尾,则ACTION_UP这个事件必定会经过FrameLayout.onTouchEvent(ev),如果拦截了,则子View针对ACTION_UP需要处理的事情就无法完成。

3.MotionEvent.ACTION_MOVE中根据实际情况判断是否拦截。

    private float moveDis;@Overridepublic boolean onTouchEvent(MotionEvent ev) {if (touchHelper != null) {float curTouchY = ev.getY();switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:break;case MotionEvent.ACTION_MOVE:moveDis = curTouchY - lastInterceptY;if (Math.abs(moveDis) < touchSlop) {break;}if (isRefreshing || isLoadMore) {break;}moveDis = moveDis * kMoveFactor;if (touchHelper.isContentSlideToTop()) {updateHeaderMargin(moveDis);} else if (touchHelper.isContentSlideToBottom()) {updateFooterMargin(moveDis);}break;case MotionEvent.ACTION_UP:if (moveDis > 0) {if (touchHelper.isContentSlideToTop()) {if (headerParams.topMargin < 0) {scrollHeaderByAnimator(headerParams.topMargin, -headerHeight);if (header != null) {header.onPullToRefresh(moveDis);}} else {scrollHeaderByAnimator(headerParams.topMargin, 0);if (header != null) {header.onRefreshing();}isRefreshing = true;if (listener != null) {listener.onRefresh();}}}} else {if (touchHelper.isContentSlideToBottom()) {if (footerParams.topMargin > height - footerHeight) {scrollFooterByAnimator(false, footerParams.topMargin, height);if (footer != null) {footer.onPullToLoadMore(moveDis);}} else {scrollFooterByAnimator(false, footerParams.topMargin, height - footerHeight);if (footer != null) {footer.onLoadMore();}isLoadMore = true;if (listener != null) {listener.onLoadMore();}}}}break;}}return true;}
private void updateHeaderMargin(float moveDis) {moveDis = moveDis < 0 ? 0 : moveDis;headerParams.topMargin = (int) (-headerHeight + moveDis);headerVG.setLayoutParams(headerParams);setChildViewTopMargin((int) moveDis);if (header != null) {if (moveDis < headerHeight) {header.onPullToRefresh(moveDis);} else {header.onReleaseToRefresh(moveDis);}}}private void setChildViewTopMargin(int topMargin) {LayoutParams childParams = (LayoutParams) childView.getLayoutParams();childParams.topMargin = topMargin;childView.setLayoutParams(childParams);}private void updateFooterMargin(float moveDis) {moveDis = moveDis > 0 ? 0 : moveDis;footerParams.topMargin = (int) (height + moveDis);footerVG.setLayoutParams(footerParams);setChildViewBottomMargin((int) Math.abs(moveDis));scrollContentToBottom((int) -moveDis);if (footer != null) {if (Math.abs(moveDis) < footerHeight) {footer.onPullToLoadMore(moveDis);} else {footer.onReleaseToLoadMore(moveDis);}}}private void setChildViewBottomMargin(int bottomMargin) {LayoutParams childParams = (LayoutParams) childView.getLayoutParams();childParams.bottomMargin = bottomMargin;childView.setLayoutParams(childParams);}private void scrollContentToBottom(int deltaY) {if (childView instanceof RecyclerView) {childView.scrollBy(0, deltaY);} else if (childView instanceof ListView) {((ListView) childView).smoothScrollBy(deltaY, 0);} else if (childView instanceof ScrollView) {childView.scrollBy(0, deltaY);}}private void scrollHeaderByAnimator(float startY, float endY) {ValueAnimator animator = ValueAnimator.ofFloat(startY, endY);animator.setDuration(kDuration);animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {float floatValue = (float) animation.getAnimatedValue();headerParams.topMargin = (int) floatValue;headerVG.setLayoutParams(headerParams);setChildViewTopMargin((int) (headerHeight + floatValue));}});animator.start();}private void scrollFooterByAnimator(final boolean isAuto, float startY, float endY) {ValueAnimator animator = ValueAnimator.ofFloat(startY, endY);animator.setDuration(kDuration);animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {float floatValue = (float) animation.getAnimatedValue();footerParams.topMargin = (int) floatValue;footerVG.setLayoutParams(footerParams);int bottomMargin = (int) (height - floatValue);setChildViewBottomMargin(bottomMargin);if (isAuto) {scrollContentToBottom(bottomMargin);if (footer != null) {footer.onPullToLoadMore(bottomMargin);if (bottomMargin == footerHeight) {footer.onLoadMore();}}}}});animator.start();}

源码下载: 点击打开链接

不完善点还待指正!!!

Android滑动冲突解决方式(下拉刷新上拉加载更多,适配RecyclerView/ListView/ScrollView)相关推荐

  1. uni-app下拉刷新触底加载更多

    首先在pages.json 配置文件中配置    "enablePullDownRefresh": true  需要在哪用加载就配置在路由的style里 两个事件 //下拉刷新 o ...

  2. recyclerview的数据刷新(下拉刷新和自动加载更多)以及添加提示语(例如:“数据已加载完毕”)

    下拉加载更多的核心是SwipeRefreshLayout搭配Recyclerview进行使用.布局为 <android.support.v4.widget.SwipeRefreshLayout ...

  3. Android 自定义 ListView 上下拉动“刷新最新”和“加载更多”歌曲列表

    本文内容 环境 测试数据 项目结构 演示 参考资料 本文演示,上拉刷新最新的歌曲列表,和下拉加载更多的歌曲列表.所谓"刷新最新"和"加载更多"是指日期.演示代码 ...

  4. Android 下拉刷新上拉加载可以左右滑动

    下面是下拉刷新上拉加载可以左右滑动的实例,下面是效果图: GitHub 下载地址:https://github.com/wuqingsen/MySlidingNested CSDN 下载地址:http ...

  5. Android ListView 实现下拉刷新上拉加载

    转载请注明出处:http://blog.csdn.net/allen315410/article/details/39965327 1.简介 无疑,在Android开发中,ListView是使用非常频 ...

  6. Android 下拉刷新上拉载入 多种应用场景 超级大放送(上)

    转载请标明原文地址:http://blog.csdn.net/yalinfendou/article/details/47707017 关于Android下拉刷新上拉载入,网上的Demo太多太多了,这 ...

  7. Android自定义控件实战——实现仿IOS下拉刷新上拉加载 PullToRefreshLayout

    下拉刷新控件,网上有很多版本,有自定义Layout布局的,也有封装控件的,各种实现方式的都有.但是很少有人告诉你具体如何实现的,今天我们就来一步步实现自己封装的 PullToRefreshLayout ...

  8. Android 下拉刷新上拉加载 多种应用场景 超级大放送(上)

    转载请标明原文地址:http://blog.csdn.net/yalinfendou/article/details/47707017 关于Android下拉刷新上拉加载,网上的Demo太多太多了,这 ...

  9. Android滑动冲突解决方法

    Android滑动冲突解决方法 滑动冲突 首先讲解一下什么是滑动冲突.当你需要在一个ScrollView中嵌套使用ListView或者RecyclerView的时候你会发现只有ScrollView能够 ...

最新文章

  1. 亚马逊面部识别闹大笑话:竟28名美国议员识别为罪犯
  2. 听李宏毅点评GPT-3:来自猎人暗黑大陆的模型
  3. No IP specified. Please specify IP with ‘objects’
  4. leetcode_Jump Game II
  5. [转载]出了国才明白的10件事~(MITBBS ZT)
  6. 写给后端程序员的HTTP缓存原理介绍
  7. 《Python Cookbook 3rd》笔记(5.16):增加或改变已打开文件的编码
  8. 数据库快照的工作方式
  9. 【Android Studio安装部署系列】二十二、Android studio自动生成set、get方法
  10. c# json 汉字乱码_C# 读取Json内的数据,中文乱码,怎么解决
  11. mysql可视化操作系统_MySQL的可视化操作工具workbench的安装
  12. 阿里面试算法题(一)
  13. 红米ac2100有ipv6吗_【0107-多功能版OpenWrt】红米小米AC2100|IPV6|酸奶|SmartDNS|多拨|猫咪,附教程...
  14. Hadoop基本原理
  15. HTML header 标签的用法
  16. 5G牌照发放了,但需要购买5G手机的用户应等明年再购买
  17. 最优化问题的Matlab优化工具箱求解总结
  18. ENVI App Store
  19. linux centos系统安装
  20. Android ToggleButton:状态切换的Button

热门文章

  1. loss 为nan???
  2. IDEA 复原不小心被误删除的本地代码
  3. mysql将查询结果写入另一张表_将一张表的查询结果插入到另一张表(转)
  4. java计算机毕业设计会展中心招商服务平台源码+mysql数据库+系统+lw文档+部署
  5. 微信小程序项目实例——2048小游戏
  6. 在Ubuntu中手动安装nginx+nextcloud
  7. 【如何快速获得一本书的结构(一)】
  8. CSDN日报190805:一线城市or二线城市,该如何抉择?
  9. 喜讯,太阳能发电站维护神方案,联网在线监控系统新鲜出炉
  10. 论简化三维流水线和逼近真实流水线快速构造引擎