修复垂直滑动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/
修复垂直滑动RecyclerView嵌套水平滑动RecyclerView水平滑动不灵敏问题的更多相关文章
- RecyclerView嵌套ScrollView导致RecyclerView内容显示不全
我们在使用RecyclerView嵌套至ScrollView内的时候 RecyclerView不在屏幕内的数据会不显示出来,这里是一个坑,我们需要重写RecyclerView /** * Create ...
- Android RecyclerView(瀑布流)水平/垂直方向分割线
Android RecyclerView(瀑布流)水平/垂直方向分割线 Android RecyclerView不像过去的ListView那样随意的设置水平方向的分割线,如果要实现Recycle ...
- RecyclerView借助ItemTouchHelper实现拖动和滑动删除功能
RecyclerView是官方推荐代替ListView的空间,怎样实现RecyclerView列表元素的拖动呢? 官方提供了ItemTouchHelper类使用过程例如以下: 定义ItemTouchH ...
- ItemTouchHelper(实现RecyclerView上添加拖动排序与滑动删除的所有事情)
简单介绍: ItemTouchHelper是一个强大的工具,它处理好了关于在RecyclerView上添加拖动排序与滑动删除的所有事情.它是RecyclerView.ItemDecoration的子类 ...
- Android RecyclerView嵌套RecyclerView
原理 RecyclerView嵌套RecyclerView的条目,项目中可能会经常有这样的需求,但是我们将子条目设置为RecyclerView之后,却显示不出来.自己试了很久,终于找到了原因:必须先设 ...
- 关于RecyclerView嵌套导致item复用异常,界面异常的问题
常规需求: 外层RecyclerView嵌套内层RecyclerView , 在上下滑动的时候会出现item数据以及view的显示异常. 解决办法: 1.重写 getItemViewType 方法 ...
- RecyclerView嵌套TextView时显示文字不全的解决方法之一
先描述一下这个小bug:简单的TextView嵌套RecyclerView作为itemView时,可能会在文本中出现布局覆盖的现象,itemView的布局其实很简单,就是一个RelativeLayou ...
- Android NestedScrollView与RecyclerView嵌套,以及NestedScrollView不会滚动到屏幕顶部解决
①NestedScrollView与RecyclerView嵌套,导致滚动惯性消失 解决:mRecyclerView.setNestedScrollingEnabled(false); ②Nested ...
- 自定义RecyclerView.ItemDecoration,实现RecyclerView的分割线效果
[转] 原文 自定义RecyclerView.ItemDecoration,实现RecyclerView的分割线效果 字数1598 阅读302 评论2 喜欢23 1.背景 RecyclerView ...
随机推荐
- Oracle11g 统计信息——统计信息自动收集任务
参考文献: Oracle11g 统计信息(一)-----统计信息自动收集任务 背景: 在使用cacti监控oracle数据库IO的时候发现每天晚上10点钟的时候oracle数据库读写明显增加,如下图所 ...
- [Android]使用AdapterTypeRender对不同类型的item数据到UI的渲染
以下内容为原创,转载请注明: 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/3992843.html 本文讲的工具均放在AndroidBucket开源 ...
- 对Xcode菜单选项的详细探索(干货)
本文调研Xcode的版本是 7.1,基本是探索了菜单的每一个按钮.虽然从xcode4一直用到了xcode7,但是一般都只是用了一些基础的功能,说来也惭愧.在一次偶然的机遇突然发现了“显示调用层级”的选 ...
- tomcat部署java servlet的3种方式
1.将编译好的class文件按照与工程中的package的目录结构一致的文件夹底下 2.将你的servlet封装成 .war(web application archive格式的后缀名) 格式的文档直 ...
- android am命令以及hotkey文件的编写
1.拨打电话:am start -a android.intent.action.CALL -d tel:10086 这里-a表示动作,-d表述传入的数据,还有-t表示传入的类型. 2. 打开一个网页 ...
- 从DACPAC文件中读取元数据
SQL数据库项目生成时会生成dacpac文件,可从中读出所需的元数据,进行一些转换(如生成数据字典) var model = new TSqlModel(@"D:\kljob\CardL ...
- 深入理解Angular中的$Apply()以及$Digest()
$apply()和$digest()在AngularJS中是两个核心概念,但是有时候它们又让人困惑.而为了了解AngularJS的工作方式,首先需要了解$apply()和$digest()是如何工作的 ...
- HashSet vs TreeSet vs LinkedHashSet
使用Set集合的主要原因是因为Set集合里面没有重复的元素.Set集合有三个常见的实现类:HashSet,TreeSet,LinkedHashSet.什么时候,选择哪一个使用非常重要.简单的说,如果你 ...
- ASP.NET MVC 监控诊断、本地化和缓存
这篇博客主要是针对asp.net mvc项目的一些常用的东东做一个讲解,他们分别是监控诊断.本地化和缓存.虽然前两者跟asp.net mvc看上去好像是没什么关联. 但其实如果真正需要做asp.net ...
- C#设计模式(21)——责任链模式
一.引言 在现实生活中,有很多请求并不是一个人说了就算的,例如面试时的工资,低于1万的薪水可能技术经理就可以决定了,但是1万~1万5的薪水可能技术经理就没这个权利批准,可能就需要请求技术总监的批准,所 ...