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
    111
    public 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
    42
    public 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
    18
    recyclerView.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
    29
    public 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);
    }