大家好,在前段时间我写过用ScrollView实现了自定义滚轮,但是在循环的效果不是特别好。( 这次文章底部附上了Demo。O(∩_∩)O~ )
用ScrollView 循环有什么问题呢。
- 因为我们是重复建立数据,比如数据是[A,B,C,D,E,F],你可以做成假循环,比如变为[A,B,C,D,E,F][A,B,C,D,E,F][A,B,C,D,E,F],变为三遍,但是变到上面一组后,因为要重新回到中间,所以你会发现闪动一下的感觉,因为比如冲第一组的A(position为0)到了第二组的A(position为6)。
- 而且如果你手指快速的滑动,不停的滚动,你就会滑到顶部的位置。因为我们的是ScrollView 最后选中哪一项,才让它滚动到中间相应的那一项。
- 那有些人可能会说,那我就不只弄这几组。我就多弄几组不就好了。别人快速滑动也滑不到顶部了。Too young Too Simple。比如我用11组。但是你会发现,你的界面加载直接很久很久,因为ScrollView内的控件都直接要初始化好,因为你设置了11组。等于有66个Item在加载完。就会让界面卡死在那里。所以体验就更差了。
最后感谢 黑马飞马 同学给的意见。
对啊。我们的RecyclerView 是只会加载界面当前显示的Item,然后不管数量再多,也只是在复用相同的View而已。这样我们上面的问题不就解决了。因为比如我们建立一千组一万组数据,我不需要考虑要重新滚回中间,问题1和2就解决了。问题3因为RecyclerView 的特性,也被解决了。是一个很理想的循环滚动的滚轮。
于是就使用RecycleViewer来进行相关的开发。正式起航。
原理分析
- 滚轮的高度和Item的高度 比如我们确定一个页面显示5项,item的布局高度为100dp,那滚轮高度就设定为500dp.
-
怎么确定RecyclerView 停止滚动
自定义ScrollerListener 继承
RecyclerView.OnScrollListener
,复写里面的 @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); switch (newState) { case RecyclerView.SCROLL_STATE_IDLE: ..... ..... ..... break; } }复制代码 当state变为了RecyclerView.SCROLL_STATE_IDLE
就说明了RecyclerView已经停止了。
3.比如只划一部分,如何让它自动滚到相应的Item(重点)
方法还是一样,通过当前获取到的滚到的Y值,然后除以每项的Item的高度,就能知道当前顶部是处于第几项,然后求余数就知道了当前顶部那项有多少是显示的。在上文我们ScrollView 中,我们使用的是
getScrollY()
方法来获取的,我本来在
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
switch (newState) {
case RecyclerView.SCROLL_STATE_IDLE:
recyclerView.getScrollY();
break;
}复制代码
所以我在
onScrollStateChanged
方法中通过
getScrollY()
方法去获取,多么Easy,哈哈,结果这次是我Too young Too simple,获取到的值一直为0。WTF!!然后就只能通过其他方式来获取滚动的距离。
获取滚动的距离:
public int getScollYDistance(RecyclerView recyclerView) {
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int position = layoutManager.findFirstVisibleItemPosition();
View firstVisiableChildView = layoutManager.findViewByPosition(position);
int itemHeight = firstVisiableChildView.getHeight();
return (position) * itemHeight - firstVisiableChildView.getTop();
}复制代码
我想这个大家应该看得懂吧。我画个图解释一下就可以了:
我来大致解释下:如上图所示,我们现在一个Item是100的高度,那我们现在滑到了第二个的20的位置,那是不是一共滑动了120的距离。因为我们当前获取到该手机界面上显示的第一个的position是1,说明position为0的已经被滑出去了。外加这个当前界面的显示的position为1的item有部分被滑出去,所以我们获取它的getTop值为-20,所以是不是正好是当前界面显示的第一个Item的position,乘以itemHeight,减去这个item 的getTop的值。(1 * 100 - (-20) = 120)
好的,我们已经解决了滚动距离的问题。那现在就是我们要让他滚动到一定距离,自动调整自己的位置,来正好显示某个Item项,而不会出现某个Item在界面上显示一半。
滚动后调整距离让RecyclerView 滚到特定的position位置:
我简单介绍,就只分二种情况来谈下(正好滑到一个标准的距离,让Item正好完全显示这种情况我就去除了):
-
顶部的Item有小于一半ItemHeight的距离滚到了屏幕的外面:
这时候很简单,大家说获取到第一个Item的
Position
值,然后调用RecyclerView.smoothScrollToPosition(Position)
,跳到这个positionItem就可以了么。没错。这个是可以。但是调用这个方法,在接下去的第二种情况下就出现问题了。- 顶部的Item有大于一半ItemHeight的距离滚到了屏幕外面:
这时候大家也知道,应该是让当前的屏幕内获取到的first Item 滚动出界面,所以大家一想就说获取第一个Item的Position值,然后调用
RecyclerView.smoothScrollToPosition(Position + 1)
不就可以了么。。完美!!。但是结果是不会滚动,原来这个方法当我们的Position + 1
已经出现在屏幕上了。不管是不是第一个,不管处于屏幕的哪个位置,这个RecyclerView就不会滚动。我忍不住又一句 WHF!!。那应该怎么处理呢。RecyclerView.ScrollBy方法 其实很简单。我直接抛弃了
RecyclerView.smoothScrollToPosition
方法,我们看到了,其实我们是不是可以通过判断,第一个Item有没有滚出一半的ItemHeight的距离在外面。无非是二种情况(假设一个ItemHeight为100):- 已经有80滚动在外面了。我就通过ScrollBy 再向上过给它滚动20到外面。
- 已经有20滚动在外面了。我就通过ScrollBy 再向下返回20到里面。
所以代码如下:
private void smoothMoveToPosition(int n, RecyclerView mRecyclerView, LinearLayoutManager mLinearLayoutManager) { int firstItem = mLinearLayoutManager.findFirstVisibleItemPosition(); if (firstItem == n) { int top = mRecyclerView.getChildAt(n - firstItem + 1).getTop(); mRecyclerView.smoothScrollBy(0, -(itemHeight - top));