Android RecyclerView 滑动冲突
01.如何判断RecyclerView控件滑动到顶部和底部
有一种使用场景,购物商城的购物车页面,当RecyclerView滑动到顶部时,让刷新控件消费事件;当RecyclerView滑动到底部时,让下一页控件[猜你喜欢]消费事件。
- 代码如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111public class VerticalRecyclerView extends RecyclerView {
private float downX;
private float downY;
/** 第一个可见的item的位置 */
private int firstVisibleItemPosition;
/** 第一个的位置 */
private int[] firstPositions;
/** 最后一个可见的item的位置 */
private int lastVisibleItemPosition;
/** 最后一个的位置 */
private int[] lastPositions;
private boolean isTop;
private boolean isBottom;
public VerticalRecyclerView(Context context) {
this(context, null);
}
public VerticalRecyclerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public VerticalRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
LayoutManager layoutManager = getLayoutManager();
if (layoutManager != null) {
if (layoutManager instanceof GridLayoutManager) {
lastVisibleItemPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();
firstVisibleItemPosition = ((GridLayoutManager) layoutManager).findFirstVisibleItemPosition();
} else if (layoutManager instanceof LinearLayoutManager) {
lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
firstVisibleItemPosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
if (lastPositions == null) {
lastPositions = new int[staggeredGridLayoutManager.getSpanCount()];
firstPositions = new int[staggeredGridLayoutManager.getSpanCount()];
}
staggeredGridLayoutManager.findLastVisibleItemPositions(lastPositions);
staggeredGridLayoutManager.findFirstVisibleItemPositions(firstPositions);
lastVisibleItemPosition = findMax(lastPositions);
firstVisibleItemPosition = findMin(firstPositions);
}
} else {
throw new RuntimeException("Unsupported LayoutManager used. Valid ones are LinearLayoutManager, GridLayoutManager and StaggeredGridLayoutManager");
}
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = ev.getX();
downY = ev.getY();
//如果滑动到了最底部,就允许继续向上滑动加载下一页,否者不允许
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
float dx = ev.getX() - downX;
float dy = ev.getY() - downY;
boolean allowParentTouchEvent;
if (Math.abs(dy) > Math.abs(dx)) {
if (dy > 0) {
//位于顶部时下拉,让父View消费事件
allowParentTouchEvent = isTop = firstVisibleItemPosition == 0 && getChildAt(0).getTop() >= 0;
} else {
//位于底部时上拉,让父View消费事件
int visibleItemCount = layoutManager.getChildCount();
int totalItemCount = layoutManager.getItemCount();
allowParentTouchEvent = isBottom = visibleItemCount > 0 && (lastVisibleItemPosition) >= totalItemCount - 1 && getChildAt(getChildCount() - 1).getBottom() <= getHeight();
}
} else {
//水平方向滑动
allowParentTouchEvent = true;
}
getParent().requestDisallowInterceptTouchEvent(!allowParentTouchEvent);
}
return super.dispatchTouchEvent(ev);
}
private int findMax(int[] lastPositions) {
int max = lastPositions[0];
for (int value : lastPositions) {
if (value >= max) {
max = value;
}
}
return max;
}
private int findMin(int[] firstPositions) {
int min = firstPositions[0];
for (int value : firstPositions) {
if (value < min) {
min = value;
}
}
return min;
}
public boolean isTop() {
return isTop;
}
public boolean isBottom() {
return isBottom;
}
}
02.RecyclerView嵌套RecyclerView 条目自动上滚的Bug
- RecyclerViewA嵌套RecyclerViewB 进入页面自动跳转到RecyclerViewB上面页面会自动滚动。
- 两种解决办法
- 一,recyclerview去除焦点
- recyclerview.setFocusableInTouchMode(false);
- recyclerview.requestFocus();
- 二,在代码里面 让处于ScrollView或者RecyclerView1 顶端的某个控件获得焦点即可
- 比如顶部的一个textview
- tv.setFocusableInTouchMode(true);
- tv.requestFocus();
03.ScrollView嵌套RecyclerView滑动冲突
第一种方式:
- 重写父控件,让父控件 ScrollView 直接拦截滑动事件,不向下分发给 RecyclerView,具体是定义一个ScrollView子类,重写其 onInterceptTouchEvent()方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42public class NoNestedScrollview extends NestedScrollView {
private int downX;
private int downY;
private int mTouchSlop;
public NoNestedScrollview(Context context) {
super(context);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
public NoNestedScrollview(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
public NoNestedScrollview(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
int action = e.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
downX = (int) e.getRawX();
downY = (int) e.getRawY();
break;
case MotionEvent.ACTION_MOVE:
//判断是否滑动,若滑动就拦截事件
int moveY = (int) e.getRawY();
if (Math.abs(moveY - downY) > mTouchSlop) {
return true;
}
break;
default:
break;
}
return super.onInterceptTouchEvent(e);
}
}第二种解决方式
- a.禁止RecyclerView滑动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18recyclerView.setLayoutManager(new GridLayoutManager(mContext,2){
@Override
public boolean canScrollVertically() {
return false;
}
@Override
public boolean canScrollHorizontally() {
return super.canScrollHorizontally();
}
});
recyclerView.setLayoutManager(new LinearLayoutManager(mContext, LinearLayout.VERTICAL,false){
@Override
public boolean canScrollVertically() {
return false;
}
});- b.重写LayoutManager
- 代码设置LayoutManager.setScrollEnabled(false);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29public class ScrollLayoutManager extends LinearLayoutManager {
private boolean isScrollEnable = true;
public ScrollLayoutManager(Context context) {
super(context);
}
public ScrollLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public ScrollLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public boolean canScrollVertically() {
return isScrollEnable && super.canScrollVertically();
}
/**
* 设置 RecyclerView 是否可以垂直滑动
* @param isEnable
*/
public void setScrollEnable(boolean isEnable) {
this.isScrollEnable = isEnable;
}
}可能会出现的问题
- 虽然上面两种方式解决了滑动冲突,但是有的手机上出现了RecyclerView会出现显示不全的情况。
- 针对这种情形,使用网上的方法一种是使用 RelativeLayout 包裹 RecyclerView 并设置属性:android:descendantFocusability=”blocksDescendants”
- android:descendantFocusability=”blocksDescendants”,该属>性是当一个view 获取焦点时,定义 ViewGroup 和其子控件直接的关系,常用来>解决父控件的焦点或者点击事件被子空间获取。
- beforeDescendants: ViewGroup会优先其子控件获取焦点
- afterDescendants: ViewGroup只有当其子控件不需要获取焦点时才获取焦点
- blocksDescendants: ViewGroup会覆盖子类控件而直接获得焦点
1
2
3
4
5
6
7
8
9
10<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants">
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_hot_review"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foregroundGravity="center" />
</RelativeLayout>
04.ViewPager嵌套水平RecyclerView横向滑动到底后不滑动ViewPager
继承RecyclerView,重写dispatchTouchEvent,根据ACTION_MOVE的方向判断是否调用getParent().requestDisallowInterceptTouchEvent去阻止父view拦截点击事件
1
2
3
4
5
6
7
8
9@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
/*---解决垂ViewPager嵌套直RecyclerView嵌套水平RecyclerView横向滑动到底后不滑动ViewPager start ---*/
ViewParent parent = this;
while(!((parent = parent.getParent()) instanceof ViewPager));
// 循环查找viewPager
parent.requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(ev);
}
05.RecyclerView嵌套RecyclerView的滑动冲突问题
06.RecyclerView使用Glide加载图片导致图片错乱问题解决
为何会导致图片加载后出现错乱效果
- 因为有ViewHolder的重用机制,每一个item在移除屏幕后都会被重新使用以节省资源,避免滑动卡顿。而在图片的异步加载过程中,从发出网络请求到完全下载并加载成Bitmap的图片需要花费很长时间,而这时候很有可能原先需要加载图片的item已经划出界面并被重用了。而原先下载的图片在被加载进ImageView的时候没有判断当前的ImageView是不是原先那个要求加载的,故可能图片被加载到被重用的item上,就产生了图片错位的问题。解决思路也很简单,就是在下载完图片,准备给ImageView装上的时候检查一下这个ImageView。
第一种方法
- 使用settag()方式,这种方式还是比较好的,但是,需要注意的是,Glide图片加载也是使用将这个方法的,所以当你在Bindviewholder()使用时会直接抛异常,你需要使用settag(key,value)方式进行设置,这种方式是不错的一种解决方式,注意取值的时候应该是gettag(key)这个方法哈,当异步请求回来的时候对比下tag是否一样在判断是否显示图片。这边直接复制博主的代码了。
1
2
3
4
5
6
7
8
9
10//给ImageView打上Tag作为特有标记
imageView.setTag(tag);
//下载图片
loadImage();
//根据tag判断是不是需要设置给ImageView
if(tag == iamgeView.getTag()) {
imageView.setBitmapImage(iamge);
}