为了将指定的 item 平滑的滑动至置顶。
- 简单的直接置顶:
mLayoutManager.scrollToPositionWithOffset(position, 0);//没有滑动,直接置顶
缺点是体验不好,没有滑动的过程。
- RecyclerView 的滑动方法
rv.scrollToPosition(index);//直接定位,没有滑动的过程,不能置顶
rv.smoothScrollToPosition(position);//平滑的滑动,不能置顶
rv.scrollBy(int x, int y);
scrollToPosition();smoothScrollToPosition()
将对应的 item 滑动到屏幕内,当 item 变为可见时则停止滑动。
所以当指定的 item 在当前屏幕的下方时,滑动后目标 item 会出现屏幕的最底部;
当指定 item 在屏幕可见时,则完全没有滑动。
很多时候这个方法是不符合我们预期的,有时候,我们希望能将指定的 item 平滑的滑动到当前的屏幕顶端或中间。
这时候可以配合 scrollBy()
来做一个判断:
private void setSelectPosition(int index) {
//当前可见的第一项和最后一项
int firstItem = linearLayoutManager.findFirstVisibleItemPosition();
int lastItem = linearLayoutManager.findLastVisibleItemPosition();
if (index <= firstItem) {
//当要置顶的项在当前显示的第一个项的前面时,直接调用没有问题
rv.scrollToPosition(index);
} else if (index <= lastItem) {
//当要置顶的项已经在屏幕上显示时,计算需要滑动的距离
int top = rv.getChildAt(index - firstItem).getTop();
rv.scrollBy(0, top);
} else {
//当指定的 item 在当前显示的最后一项的后面时
//这时候一次的滑动不足以将指定 item 放到顶端
rv.scrollToPosition(index);
//记录当前需要在RecyclerView滚动监听里面继续第二次滚动
move = true;
}
}
//在第三种的情况中,scrollToPosition() 结束后该方法被调用
class RecyclerViewListener extends RecyclerView.OnScrollListener{
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//在这里进行第二次滚动
if (move ){
move = false;
int n = mIndex - mLinearLayoutManager.findFirstVisibleItemPosition();
if ( 0 <= n && n < mRecyclerView.getChildCount()){
//要移动的距离
int top = mRecyclerView.getChildAt(n).getTop();
mRecyclerView.scrollBy(0, top);
}
}
}
}
上面这个方面也是看到别人的实现的思路,虽然没什么问题不过还是算是取巧的一种方法,仅记录。
其实 Stack Overflow 上已经有了更好的答案 :RecyclerView - How to smooth scroll to top of item on a certain position?
- 重写 LinearLayoutManager
public class LinearLayoutManagerWithSmoothScroller extends LinearLayoutManager {
public LinearLayoutManagerWithSmoothScroller(Context context) {
super(context, VERTICAL, false);
}
public LinearLayoutManagerWithSmoothScroller(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
//重点方法
@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
int position) {
//也就是说重点在于重写 SmoothScroller,而滑动的调用为 startSmoothScroll()
RecyclerView.SmoothScroller smoothScroller = new TopSnappedSmoothScroller(recyclerView.getContext());
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
private class TopSnappedSmoothScroller extends LinearSmoothScroller {
public TopSnappedSmoothScroller(Context context) {
super(context);
}
@Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return LinearLayoutManagerWithSmoothScroller.this
.computeScrollVectorForPosition(targetPosition);
}
@Override
protected int getVerticalSnapPreference() {
//将指定的 item 滑动至与屏幕的顶端对齐
return SNAP_TO_START;
}
}
}
//使用
recyclerView.smoothScrollToPosition(position);
仔细看可以发现我们也可以选择不重写整个 LinearLayoutManager,只要将 LinearSmoothScroller 的 getVerticalSnapPreference()
重写也可以达到目的。
关于 getVerticalSnapPreference ()
的源码与注释:
/**
* When scrolling towards a child view, this method defines whether we should align the top
* or the bottom edge of the child with the parent RecyclerView.
*
* @return SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY; depending on the current target vector
* @see #SNAP_TO_START
* @see #SNAP_TO_END
* @see #SNAP_TO_ANY
*/
protected int getVerticalSnapPreference() {
return mTargetVector == null || mTargetVector.y == 0 ? SNAP_TO_ANY :
mTargetVector.y > 0 ? SNAP_TO_END : SNAP_TO_START;
}
返回的值决定了指定 item 的对齐方式,与顶部对齐 / 底部对齐。
所以最终代码:
//初始化过程
mLayoutManager= new LinearLayoutManager(getActivity());
mSmoothScroller = new LinearSmoothScroller(getActivity()) {
@Override protected int getVerticalSnapPreference() {
return LinearSmoothScroller.SNAP_TO_START;
}
@Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return mLayoutManager.computeScrollVectorForPosition(targetPosition);
}
};
mRvRetail.setLayoutManager(mLayoutManager);
//滑动
mSmoothScroller.setTargetPosition(position);
mLayoutManager.startSmoothScroll(mSmoothScroller);