RecyclerView (GridLayout) 滑动到指定位置
我的 RecyclerView 才不会这么笨!
RecyclerView 布局有三类,列表(横竖)、网格、瀑布流(
不规则),让某一个 item 显示在最上方(非最后一项),使用 scrollPosition 将某一个位置设置可见第一项,或者计算显示的 item 距离第一项距离,用 scrollBy 移动过去,常用使用下面几个方法实现该功能:
LayoutManager
1. scrollToPosition(int position)
2.smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
int position)
RecyclerView 滑动实际是调用设置的 LayoutManager 的滑动 toPosition.
1. scrollBy(int x, int y)
2. smoothScrollToPosition(int position)
3. scrollToPosition(int position)
注:RecyclerView 的 scrollTo(int x, int y) 这个方法不具备滑动的功能,具体见源码:
public void scrollTo(int x, int y) {
Log.w(TAG, "RecyclerView does not support scrolling to an absolute position. Use scrollToPosition instead");
}
如果要使 RecyclerView 滑动到某一个位置,会有下面几种情况:
1. 目标项在屏幕内
2. 目标项在屏幕上,即显示的位置小于当前显示的第一项位置
3. 目标项在屏幕下,即显示项在大于当前屏幕显示最后一项
处理思路:
- 条件 1,使用 scrollToPosition / smoothScrollToPosition 均无效,因为目标项已经在屏幕可见范围,所以不会有滑动效果,因此需要计算目标项距离父布局的高度, scrollBy(0,dy)
- 条件 2,使用 scrollToPosition /smoothScrollToPosition,在不可见项在上方,这两个方法均可用,一个没有滑动效果,一个有。
- 条件 3,使用 scrollToPosition / smoothScrollToPosition,但会出现一个问题,目标项会显示在屏幕中,但不是在列表的第一项,仅是显示而已。注释显示可使用 scrollToPositionWithOffset 代替,需要的话可以尝试一下
- 在条件 3 下,滑动目标项到屏幕中,但不是显示在第一的位置(假设目标相不在列表最后一屏中),即到了条件 1 的情况,这时需要监听滑动状态,计算缺少的滑动距离,将最后一点距离 scrollBy(0,dy) 过去。
使用 “smoothScrollToPosition ” 是触发滑动器 SmoothScroller 来重绘界面,滑动结束会触发滑动监听器,而 scrollToPosition 是界面的直接重新绘制,不涉及滑动,也就无法获取滑动结束的状态,这就是不使用 scrollToPosition 方法的原因。
条件3 效果:
修复条件3 效果:
代码块:
//条件3 是否需增加滑动距离
private boolean isMove = false;
//条件3 滑动的位置
private int scrollPosition =-1;
private void moveToPosition(int position) {
if (position > data.size())//越界判断
return;
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int first = layoutManager.findFirstVisibleItemPosition();
int end = layoutManager.findLastVisibleItemPosition();
if (first == -1 || end == -1)
return;
if (position <= first) {//条件2
layoutManager.scrollToPosition(position);
} else if (position >= end) {//条件3
isMove = true;
scrollPosition = position;
layoutManager.smoothScrollToPosition(recyclerView, null, position);
} else {//条件1,中间部分
int n = position - layoutManager.findFirstVisibleItemPosition();
if (n > 0 && n < data.size()) {
int top = layoutManager.findViewByPosition(position).getTop();
recyclerView.scrollBy(0, top);
}
}
}
滑动监听器
private RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (isMove && newState == RecyclerView.SCROLL_STATE_IDLE) {//滑动结束
isMove = false;
int top = layoutManager.findViewByPosition(scrollPosition).getTop();
recyclerView.scrollBy(0, top);
}
}
};
关于条件 3 为什么 RecyclerView 指将目标项显示在屏幕就结束了呢?
定位 scrollToPosition / smoothScrollToPosition
- scrollToPosition
在 LinearLayoutManager 中滑动无效,以后再补充 == - smoothScrollToPosition
RecyclerView 源码,在内部类 ViewFlinger 中 onAnimation(int dx, int dy),代码块如下:
private void onAnimation(int dx, int dy) {
...
if (mTargetView != null) {
// verify target position
//如果目标项被找到,则停止,即目标项在屏幕中,就结束
//这也解释条件1 下,scrollToPosition / smoothScrollToPosition 无效原因
if (getChildPosition(mTargetView) == mTargetPosition) {
onTargetFound(mTargetView, recyclerView.mState, mRecyclingAction);
mRecyclingAction.runIfNecessary(recyclerView);
stop();
} else {
Log.e(TAG, "Passed over target position while smooth scrolling.");
mTargetView = null;
}
}
...
}
这两个滑动添加后,即可解决滑动导致的位置不准,飘的的问题,如果有解答你的疑虑,是我荣幸。
代码已上传至 github 在AndroidDemo 包 RecyclerViewScroll 中 点击这里跳转
也可下载整个工程 ,里面包含部分 kotlin 配置,如果不能用,删除 kotlin 配置即可。