LinearSnapHelper 的 一个坑

作者: lguipeng | 来源:发表于2016-09-24 11:09 被阅读3279次

前记

我上篇文章 Android Support Library 24.2.0 更新介绍 简单介绍了 LinearSnapHelper,有点类似 ViewPager,比如可以用于横向浏览图片,LinearSnapHelper 保证了每次滚动完自动滑动到中心的 Item.

正题

前几天项目有个需求,看了一下刚好可以用上 LinearSnapHelper,用起来非常简单

SnapHelper snapHelper = new LinearSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);

实际运行效果如图

运行效果图.png

因为有纵横向的滚动,所以用了 RecyclerView 的嵌套,目前看运行良好。

但是实际体验发现有个问题,有时候在手指在内层的 RecyclerView 上下滚动时,触摸事件被吃了,没有反应,按道理讲,横向滚动的 RecyclerView 应该不拦截这种事件才对。而且这个现象并不是必现,发现如果是横向滚动到第二个 Item 在纵向滚 就没有问题,只有第一个 Item (其实还有最后一个 Item)会出现这种现象,简单讲 出现的步骤大概是这样的,先滚动到 第二个 Item 在滚回来第一个 Item 就会出现这个bug。

Fix 之路

首先 debug 了 内层 RecyclerView 的 onInterceptTouchEvent,发现这个时候 return 为 true, 而且这时 RecyclerView 的状态 不是 SCROLL_STATE_IDLE,而是 SCROLL_STATE_SETTLING ,看 RecyclerView 的源码

if (mScrollState == SCROLL_STATE_SETTLING) {
    getParent().requestDisallowInterceptTouchEvent(true);
    setScrollState(SCROLL_STATE_DRAGGING);
}
...
return mScrollState == SCROLL_STATE_DRAGGING;

因此这个时候 RecyclerView 会拦截事件。
事实上导致 RecyclerView 的状态 为 SCROLL_STATE_SETTLING 是因为 LinearSnapHelper。
LinearSnapHelper 原理是监听 RecyclerView 的滚动状态,一旦处于RecyclerView.SCROLL_STATE_IDLE (当然还有 Fling 的情况,这里先不看) 就会计算得到要处于中心的 Item View,然后在计算需要滚动的距离。
处于中心这个点很重要,细心的读者可能会发现第一个 Item 并不会处于中心的,那么这个时候 LinearSnapHelper 会怎么处理呢?
LinearSnapHelper 会根据 calculateDistanceToFinalSnap 得到的距离 利用 RecyclerView 的 smoothScrollBy 来滚动,而这个时候第一个 Item 永远都滚动不到中心的位置,于是不停的 ScrollBy 也就导致 状态 为 SCROLL_STATE_SETTLING。

知道原因后就好办了, 重写 calculateDistanceToFinalSnap 修正距离就可以了
用以下的 FixLinearSnapHelper 代替 LinearSnapHelper 就可以了。

public class FixLinearSnapHelper extends LinearSnapHelper{

    private OrientationHelper mVerticalHelper;

    private OrientationHelper mHorizontalHelper;

    private RecyclerView mRecyclerView;

    @Override
    public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,
                                              @NonNull View targetView) {
        int[] out = new int[2];

        if (layoutManager.canScrollHorizontally()) {
            out[0] = distanceToCenter(targetView, getHorizontalHelper(layoutManager));
        } else {
            out[0] = 0;
        }

        if (layoutManager.canScrollVertically()) {
            out[1] = distanceToCenter(targetView, getVerticalHelper(layoutManager));
        } else {
            out[1] = 0;
        }
        return out;
    }

    @Override
    public void attachToRecyclerView(@Nullable RecyclerView recyclerView) throws IllegalStateException {
        this.mRecyclerView = recyclerView;
        super.attachToRecyclerView(recyclerView);
    }

    private int distanceToCenter(View targetView, OrientationHelper helper) {
        //如果已经滚动到尽头 并且判断是否是第一个item或者是最后一个,直接返回0,不用多余的滚动了 
        if ((helper.getDecoratedStart(targetView) == 0 && mRecyclerView.getChildAdapterPosition(targetView) == 0)
                || (helper.getDecoratedEnd(targetView) == helper.getEndAfterPadding()
                && mRecyclerView.getChildAdapterPosition(targetView) == mRecyclerView.getAdapter().getItemCount() - 1) )
            return 0;

        int viewCenter = helper.getDecoratedStart(targetView) + (helper.getDecoratedEnd(targetView) - helper.getDecoratedStart(targetView))/2;
        int correctCenter = (helper.getEndAfterPadding() - helper.getStartAfterPadding())/2;
        return viewCenter - correctCenter;
    }

    private OrientationHelper getVerticalHelper(RecyclerView.LayoutManager layoutManager) {
        if (mVerticalHelper == null) {
            mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager);
        }
        return mVerticalHelper;
    }

    private OrientationHelper getHorizontalHelper(RecyclerView.LayoutManager layoutManager) {
        if (mHorizontalHelper == null) {
            mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager);
        }
        return mHorizontalHelper;
    }

}

相关文章

  • LinearSnapHelper 的 一个坑

    前记 我上篇文章 Android Support Library 24.2.0 更新介绍 简单介绍了 Linear...

  • SnapHelper :illegalstateexceptio

    用过snaphelper这个类的都知道,当然它还有个子类LinearSnapHelper,snaphelper可以...

  • LinearSnapHelper源码解析

    Google最新发布的Support RecyclerView包更新到24.2.0,这次来聊聊RecyclerVi...

  • Android滚轮实现LinearSnapHelper和Page

    【链接】(1)https://blog.csdn.net/zhangphil/article/details/82...

  • 生活就是一个个坑

    生活就像一个个的坑,过了这个坑,再进入另一个坑。 来到这个厂子,就像是一个无底洞一样的神坑,坑的家徒四壁,坑的人心...

  • 萝卜与坑

    萝卜与坑 文/邱阿丘 一个萝卜一个坑, 拔去萝卜留了坑。 先寻萝卜备空坑, 再拔萝卜补填坑。 萝卜因坑而长成, 坑...

  • 请给自己挖个坑

    一个萝卜一个坑,记得给自己挖个坑。 都说,一个萝卜一个坑,如果你就是那个萝卜,那你就得找个坑跳进去~ 要是不跳到坑...

  • 到处都是坑

    放眼望去 这一个坑 那一个坑 前面也是坑,后面也是坑 坑里全是人,坑里还有坑 坑居然可以嵌套 就像网页一样 进去一...

  • “吃土骚年”需要知道的文具省钱小tips!

    作为一个学生党, 现在的“文具坑”实在太多了, 本子坑, 便签坑, 修正带坑, 各种坑...... 每天都觉得荷包...

  • WebView上传文件无响应的问题:

    坑,坑,坑,坑,坑,坑;注意事项:做完这些可能调用系统相册是没有问题的,但是如果自己写的一个选择图片的页面,我们通...

网友评论

  • 节庆007:如果把recycleview整成无限循环的列表,然后再借助LinearSnapHelper实现居中效果,首次进入不会居中会居左侧,滑动一下之后就正常了,这种状态需要如何解决?
    f5c6e9ce7455:rv.scrollToPosition(targetPosition-1);
    rv.smoothScrollToPosition(targetPosition);

    先滑动到目标item的前一个item,然后再用平滑滚动到目标item,这样可以解决首次不居中的问题。
    但是效果上,会看到一个短暂的滑动过程。
    qian1127:我也遇到这个问题,最后怎么解决的?
    Rc在努力:最后怎么解决?
  • 阿茳小粥257:支持,解决了遇到的问题:+1:

本文标题:LinearSnapHelper 的 一个坑

本文链接:https://www.haomeiwen.com/subject/pcdxyttx.html