RecyclerView定位到具体position

RecyclerView是Android开发中经常遇到的一个控件,在使用RecyclerView的时候,直接滑动到某个position的位置的需求也经常碰到。
遇到这个需求了,我们就会去找RecyclerView是否有类似于ListView中的smoothScrollToPosition的方法。在代码中一敲,果然有,兴奋的运行调试。运行之后就开始沮丧了,怎么效果和期待的不一样的,甚至有时候调用smoothScrollToPosition方法根本没有任何动静。
这可咋整?只能到网上找找资料了,经过一番搜索,发现原来RecyclerView的smoothScrollToPosition方法是有坑的。

通过LayoutManager的srcollToPositionWithOffset方法进行定位

当然,直接定位的话,如下方式可以实现,而且定位相当精准。

1
2
3
// 通过LayoutManager的srcollToPositionWithOffset方法进行定位

mLayoutManager.scrollToPositionWithOffset(position, 0);

但是,它比较粗暴,直接定位过去了,而不是滑动过去。当然,如果你的产品经理能够接受这个效果那也就不需要这么折腾了。
然而,这么粗暴的体验,几乎没有哪个产品能够接受的。所以不得不继续寻找体验更好的方式。

通过smoothScrollToPosition结合smoothScrollBy实现

重新回到RecyclerView的smoothScrollToPosition方法。
它的滚动分三种情况:

  • 要定位position在第一个可见项之前
    这种情况调用smoothScrollToPosition能够平滑的滚动到指定位置,并且置顶。
  • 要定位position在第一个可见项之后,并且在最后一个可见项之前
    这种情况调用smoothScrollToPosition,没有任何反应,因为RecyclerView认为要定位的项已经在屏幕中了。
  • 要定位position在最后一个可见项之后
    这种情况调用smoothScrollToPosition,会有滑动但是定位不准确,通常情况下,只要要定位的position下的item出现在屏幕上就停止了,也就是说,item会在屏幕最下端。

瞬间感觉RecyclerView好坑爹!

那如何解决呢?

既然知道RecyclerView的smoothScrollToPosition方法分为上面三种情况了,我们可以针对不同情况做不同的处理。

代码如下:

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
private boolean mShouldScroll;
private int mToPosition;
private void smoothMoveToPosition(final int position) {
int firstItem = mPhotosRV.getChildLayoutPosition(mPhotosRV.getChildAt(0));
int lastItem = mPhotosRV.getChildLayoutPosition(mPhotosRV.getChildAt(mPhotosRV.getChildCount() -1));
if (position < firstItem ) {
// 如果要跳转的位置在第一个可见项之前,则smoothScrollToPosition可以直接跳转
mPhotosRV.smoothScrollToPosition(position);
} else if (position <= lastItem) {
// 如果要跳转的位置在第一个可见项之后,且在最后一个可见项之前
// smoothScrollToPosition根本不会动,此时调用smoothScrollBy来滑动到指定位置
int movePosition = position - firstItem;
if (movePosition >= 0 && movePosition < mPhotosRV.getChildCount()) {
int top = mPhotosRV.getChildAt(movePosition).getTop();
mPhotosRV.smoothScrollBy(0, top);
}
} else {
// 如果要跳转的位置在最后可见项之后,则先调用smoothScrollToPosition将要跳转的位置滚动到可见位置
// 再通过onScrollStateChanged控制再次调用smoothMoveToPosition,进入上一个控制语句
mPhotosRV.smoothScrollToPosition(position);
mShouldScroll = true;
mToPosition = position;
}
}

private RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}

@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
mAutoScrolling = false;
if (mShouldScroll) {
mShouldScroll = false;
smoothMoveToPosition(mToPosition);
}
}
}
};

这种解决方法的原理就是,判断需要定位的position所在的位置。

  • 如果要跳转的位置在第一个可见项之前,则直接调用smoothScrollToPosition跳转。
  • 如果要跳转的位置在第一个可见项之后,且在最后一个可见项之前,通过计算要跳转的item的top,然后在使用smoothScrollBy进行跳转
  • 如果要跳转的位置在最后一个可见项之后,则先调用一下smoothScrollToPosition,让要跳转的item位于屏幕可见范围之内,然后再通过smoothScrollBy进行跳转

这样能够精准的定位了,不过有一个小小的缺点,就是如果是上面第三种情况,就会出现滑动一下再滑动一下的现象,不是很连贯,但也还是能够接受,毕竟比起直接定位那样粗暴的体验还是要好的多。
如果有更好的RecyclerView定位方式,欢迎探讨。

参考:http://blog.csdn.net/tyzlmjj/article/details/49227601