当前位置: 首页 > news >正文

修复垂直滑动RecyclerView嵌套水平滑动RecyclerView水平滑动不灵敏问题

在 Android 应用中,大部分情况下都会使用一个垂直滚动的 View 来显示内容(比如 ListView、RecyclerView 等)。但是有时候你还希望垂直滚动的View 里面的内容可以水平滚动。如果直接在垂直滚动的 View 里面使用水平滚动的 View,则滚动操作并不是很流畅。

比如下图中的示例:

为什么会出现这个问题呢?

上图中的布局为一个 RecyclerView 使用的是垂直滚动的 LinearLayoutManager 布局管理器,而里面每个 Item 为另外一个 RecyclerView 使用的是水平滚动的 LinearLayoutManager。而在 Android系统的事件分发 中,即使最上层的 View 只能垂直滚动,当用户水平拖动的时候,最上层的 View 依然会拦截点击事件。下面是 RecyclerView.java 中 onInterceptTouchEvent 的相关代码:

@Override
public boolean onInterceptTouchEvent(MotionEvent e) {  
  ...
 
  switch (action) {
    case MotionEvent.ACTION_DOWN:         ...       case MotionEvent.ACTION_MOVE: {         ...           if (mScrollState != SCROLL_STATE_DRAGGING) {           boolean startScroll = false;           if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {             ...             startScroll = true;           }           if (canScrollVertically && Math.abs(dy) > mTouchSlop) {             ...             startScroll = true;           }           if (startScroll) {             setScrollState(SCROLL_STATE_DRAGGING);           }       }     } break;       ...     }   return mScrollState == SCROLL_STATE_DRAGGING; }   

注意上面的 if 判断:

if(canScrollVertically && Math.abs(dy) > mTouchSlop) {...}  
 

RecyclerView 并没有判断用户拖动的角度, 只是用来判断拖动的距离是否大于滚动的最小尺寸。 如果是一个只能垂直滚动的 View,这样实现是没有问题的。如果我们在里面再放一个 水平滚动的 RecyclerView ,则就出现问题了。

可以通过如下的方式来修复该问题:

if(canScrollVertically && Math.abs(dy) > mTouchSlop && (canScrollHorizontally || Math.abs(dy) > Math.abs(dx))) {...}  
 

下面是一个完整的实现 BetterRecyclerView.java :

public class BetterRecyclerView extends RecyclerView{   private static final int INVALID_POINTER = -1;   private int mScrollPointerId = INVALID_POINTER;   private int mInitialTouchX, mInitialTouchY;   private int mTouchSlop;   public BetterRecyclerView(Contextcontext) {     this(context, null);   }     public BetterRecyclerView(Contextcontext, @Nullable AttributeSetattrs) {     this(context, attrs, 0);   }     public BetterRecyclerView(Contextcontext, @Nullable AttributeSetattrs, int defStyle) {     super(context, attrs, defStyle);     final ViewConfigurationvc = ViewConfiguration.get(getContext());     mTouchSlop = vc.getScaledTouchSlop();   }     @Override   public void setScrollingTouchSlop(int slopConstant) {     super.setScrollingTouchSlop(slopConstant);     final ViewConfigurationvc = ViewConfiguration.get(getContext());     switch (slopConstant) {       case TOUCH_SLOP_DEFAULT:         mTouchSlop = vc.getScaledTouchSlop();         break;       case TOUCH_SLOP_PAGING:         mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(vc);         break;       default:         break;     }   }     @Override   public boolean onInterceptTouchEvent(MotionEvent e) {     final int action = MotionEventCompat.getActionMasked(e);     final int actionIndex = MotionEventCompat.getActionIndex(e);       switch (action) {       case MotionEvent.ACTION_DOWN:         mScrollPointerId = MotionEventCompat.getPointerId(e, 0);         mInitialTouchX = (int) (e.getX() + 0.5f);         mInitialTouchY = (int) (e.getY() + 0.5f);         return super.onInterceptTouchEvent(e);         case MotionEventCompat.ACTION_POINTER_DOWN:         mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex);         mInitialTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f);         mInitialTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f);         return super.onInterceptTouchEvent(e);         case MotionEvent.ACTION_MOVE: {         final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);         if (index < 0) {           return false;         }           final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);         final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);         if (getScrollState() != SCROLL_STATE_DRAGGING) {           final int dx = x - mInitialTouchX;           final int dy = y - mInitialTouchY;           final boolean canScrollHorizontally = getLayoutManager().canScrollHorizontally();           final boolean canScrollVertically = getLayoutManager().canScrollVertically();           boolean startScroll = false;           if (canScrollHorizontally && Math.abs(dx) > mTouchSlop && (Math.abs(dx) >= Math.abs(dy) || canScrollVertically)) {             startScroll = true;           }           if (canScrollVertically && Math.abs(dy) > mTouchSlop && (Math.abs(dy) >= Math.abs(dx) || canScrollHorizontally)) {             startScroll = true;           }           return startScroll && super.onInterceptTouchEvent(e);         }         return super.onInterceptTouchEvent(e);       }         default:         return super.onInterceptTouchEvent(e);     }   } }   

其他问题

当用户快速滑动(fling)RecyclerView 的时候, RecyclerView 需要一段时间来确定其最终位置。 如果用户在快速滑动一个子的水平 RecyclerView,在子 RecyclerView 还在滑动的过程中,如果用户垂直滑动,则是无法垂直滑动的。原因是子 RecyclerView 依然处理了这个垂直滑动事件。

所以,在快速滑动后的滚动到静止的状态中,子 View 不应该响应滑动事件了,再次看看 RecyclerView 的 onInterceptTouchEvent() 代码:

@Override
public boolean onInterceptTouchEvent(MotionEvent e) {  
    ...
 
    switch (action) {
        case MotionEvent.ACTION_DOWN:             ...               if (mScrollState == SCROLL_STATE_SETTLING) {                 getParent().requestDisallowInterceptTouchEvent(true);                 setScrollState(SCROLL_STATE_DRAGGING);             }               ...     }     return mScrollState == SCROLL_STATE_DRAGGING; }   

可以看到,当 RecyclerView 的状态为 SCROLL_STATE_SETTLING (快速滑动后到滑动静止之间的状态)时, RecyclerView 告诉父控件不要拦截事件。

同样的,如果只有一个方向固定,这样处理是没问题的。

针对我们这个嵌套的情况,父 RecyclerView 应该只拦截垂直滚动事件,所以可以这么修改父 RecyclerView:

public class FeedRootRecyclerView extends BetterRecyclerView{     public FeedRootRecyclerView(Contextcontext) {     this(context, null);   }     public FeedRootRecyclerView(Contextcontext, @Nullable AttributeSetattrs) {     this(context, attrs, 0);   }     public FeedRootRecyclerView(Contextcontext, @Nullable AttributeSetattrs, int defStyle) {     super(context, attrs, defStyle);   }     @Override   public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {     /* do nothing */   } }   

下图为最终的结果:

如果感兴趣可以下载 示例项目 ,注意示例项目中使用 kotlin,所以需要配置 kotlin 插件。

原文:http://nerds.headout.com/fix-horizontal-scrolling-in-your-android-app/





    本文转自 一点点征服   博客园博客,原文链接:http://www.cnblogs.com/ldq2016/p/5952726.html,如需转载请自行联系原作者



相关文章:

  • 无线网络覆盖 郑州大学第三届acm比赛试题 n 199
  • 回数
  • 生成备案号例如80-027-1-001 规则为:企业编号-所在区号-产品类别-序号
  • 颠覆你的认知,带你领略史上最为齐全的微软黑科技之旅
  • 时代杂志评年度十大科技产品 iPad 2居首
  • 成功恢复FAT32误格式化后所有碎片文件(已覆盖的除外)
  • maya羽毛制作插件
  • 如何使用sendEmail发送邮件
  • TightVNC 企业内部部署
  • 请读者帮忙投个票喔
  • 网吧游戏的三层更新
  • DOM常用属性和方法汇总
  • 享元模式(Flyweight)解析例子
  • msdb.dbo.suspect_pages
  • apache做反向代理服务器
  • “大数据应用场景”之隔壁老王(连载四)
  • 【干货分享】SpringCloud微服务架构分布式组件如何共享session对象
  • 【跃迁之路】【699天】程序员高效学习方法论探索系列(实验阶段456-2019.1.19)...
  • Android框架之Volley
  • Angular6错误 Service: No provider for Renderer2
  • create-react-app项目添加less配置
  • css系列之关于字体的事
  • Eureka 2.0 开源流产,真的对你影响很大吗?
  • FastReport在线报表设计器工作原理
  • Java 内存分配及垃圾回收机制初探
  • Java,console输出实时的转向GUI textbox
  • JS创建对象模式及其对象原型链探究(一):Object模式
  • Laravel深入学习6 - 应用体系结构:解耦事件处理器
  • select2 取值 遍历 设置默认值
  • Spring思维导图,让Spring不再难懂(mvc篇)
  • tab.js分享及浏览器兼容性问题汇总
  • VUE es6技巧写法(持续更新中~~~)
  • vue自定义指令实现v-tap插件
  • WordPress 获取当前文章下的所有附件/获取指定ID文章的附件(图片、文件、视频)...
  • 第十八天-企业应用架构模式-基本模式
  • 对话:中国为什么有前途/ 写给中国的经济学
  • 开源地图数据可视化库——mapnik
  • 面试遇到的一些题
  • 译有关态射的一切
  • #if 1...#endif
  • (+3)1.3敏捷宣言与敏捷过程的特点
  • (Redis使用系列) Springboot 使用Redis+Session实现Session共享 ,简单的单点登录 五
  • (草履虫都可以看懂的)PyQt子窗口向主窗口传递参数,主窗口接收子窗口信号、参数。
  • (附源码)ssm航空客运订票系统 毕业设计 141612
  • (附源码)计算机毕业设计高校学生选课系统
  • (十三)Flask之特殊装饰器详解
  • (一) springboot详细介绍
  • (已更新)关于Visual Studio 2019安装时VS installer无法下载文件,进度条为0,显示网络有问题的解决办法
  • (译)2019年前端性能优化清单 — 下篇
  • .NET CORE Aws S3 使用
  • .NET Core 版本不支持的问题
  • .NET 编写一个可以异步等待循环中任何一个部分的 Awaiter
  • .NET 动态调用WebService + WSE + UsernameToken
  • .Net 中Partitioner static与dynamic的性能对比
  • .NET/ASP.NETMVC 大型站点架构设计—迁移Model元数据设置项(自定义元数据提供程序)...