前言
上篇文章RecyclerView还能这样滚动对齐?介绍了itemView和RecyclerView
滚动对齐的做法,但实际使用时还需要对其稍加封装,方便使用
抽象
我们的需求是,让RecyclerView
可以滚动到某个位置并对齐。所以定义一个IndexScroller
接口,提供scrollToPosition
方法滚动到指定position,并且可指定对齐方式
public interface IndexScroller {
void scrollToPosition(int position, @SnapPreference int snapPreference);
}
而对齐方式LinearSmoothScroller
中已经定义了三种,这里我们再多加一种居中对齐
public @interface SnapPreference {
int SNAP_TO_START = LinearSmoothScroller.SNAP_TO_START;
int SNAP_TO_ANY = LinearSmoothScroller.SNAP_TO_ANY;
int SNAP_TO_END = LinearSmoothScroller.SNAP_TO_END;
int SNAP_TO_CENTER = 2;
}
接下来我们用RecyclerView
提供的LinearSmoothScroller
,实现一个可以平滑滚动的IndexScroller
平滑滚动
首先自定义一个LinearSmoothScroller
。因为要支持居中对齐,所以我们还需要重写calculateDtToFit
方法
public class SmoothScroller extends LinearSmoothScroller {
private final int mVerticalSnapPreference;
private final int mHorizontalSnapPreference;
public SmoothScroller(Context context, @SnapPreference int verticalSnapPreference, @SnapPreference int horizontalSnapPreference) {
super(context);
mVerticalSnapPreference = verticalSnapPreference;
mHorizontalSnapPreference = horizontalSnapPreference;
}
@Override
protected int getVerticalSnapPreference() {
return mVerticalSnapPreference;
}
@Override
protected int getHorizontalSnapPreference() {
return mHorizontalSnapPreference;
}
@Override
public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int snapPreference) {
if (snapPreference == SnapPreference.SNAP_TO_CENTER) {
// itemView的中心点 - recyclerView的中心点
return (boxStart + (boxEnd - boxStart) / 2) - (viewStart + (viewEnd - viewStart) / 2);
}
return super.calculateDtToFit(viewStart, viewEnd, boxStart, boxEnd, snapPreference);
}
}
然后就是自定义一个IndexScroller
实现类,并对上面的SmoothScroller
做简单的调用
public class SmoothIndexScroller implements IndexScroller {
private final RecyclerView mRecyclerView;
public SmoothIndexScroller(@NonNull RecyclerView recyclerView) {
mRecyclerView = recyclerView;
}
@NonNull
protected RecyclerView.SmoothScroller createScroller(@NonNull RecyclerView recyclerView, @SnapPreference int snapPreference) {
return new SmoothScroller(recyclerView.getContext(), snapPreference, snapPreference);
}
@Override
public void scrollToPosition(int position, @SnapPreference int snapPreference) {
RecyclerView recyclerView = mRecyclerView;
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager == null || position < 0) {
return;
}
RecyclerView.SmoothScroller scroller = createScroller(recyclerView, snapPreference);
scroller.setTargetPosition(position);
layoutManager.startSmoothScroll(scroller);
}
}
平滑滚动的部分实现完了。使用时只需要创建SmoothIndexScroller
对象,然后调用scrollToPosition
方法即可
IndexScroller indexScroller = new SmoothIndexScroller(mRecyclerView);
indexScroller.scrollToPosition(position, preference);
接下来就是即时滚动的部分了
即时滚动
先来回顾下上篇文章提到的即时滚动核心代码
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
// 1. 调用scrollToPosition()使目标itemView可见
recyclerView.scrollToPosition(targetPosition);
recyclerView.post(new Runnable() {
@Override
public void run() {
// 2. 获取目标itemView
View targetView = layoutManager.findViewByPosition(targetPosition);
if (targetView != null) {
// 3. 通过Rangefinder计算itemView和目的位置的偏移量
Rangefinder rangefinder = new Rangefinder(layoutManager);
final int dx = rangefinder.calculateDxToMakeVisible(targetView, preference);
final int dy = rangefinder.calculateDyToMakeVisible(targetView, preference);
if (dx != 0 || dy != 0) {
// 4. 调用scrollBy()将itemView移动到目的位置
recyclerView.scrollBy(-dx, -dy);
}
}
}
});
根据上面的代码思路,写一个类似LinearSmoothScroller
的scroller
public class ImmediateScroller {
public final RecyclerView recyclerView;
private final int mVerticalSnapPreference;
private final int mHorizontalSnapPreference;
public ImmediateScroller(@NonNull RecyclerView recyclerView, @SnapPreference int verticalSnapPreference, @SnapPreference int horizontalSnapPreference) {
this.recyclerView = recyclerView;
mVerticalSnapPreference = verticalSnapPreference;
mHorizontalSnapPreference = horizontalSnapPreference;
}
public void start(int position) {
if (position == RecyclerView.NO_POSITION) {
return;
}
recyclerView.scrollToPosition(position);
recyclerView.post(new Runnable() {
@Override
public void run() {
View targetView = findViewByPosition(position);
if (targetView != null) {
onTargetFound(targetView);
}
}
});
}
protected View findViewByPosition(int position) {
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
return layoutManager != null ? layoutManager.findViewByPosition(position) : null;
}
protected void onTargetFound(@NonNull View targetView) {
Rangefinder rangefinder = new Rangefinder(recyclerView.getLayoutManager());
final int dx = rangefinder.calculateDxToMakeVisible(targetView, getHorizontalSnapPreference());
final int dy = rangefinder.calculateDyToMakeVisible(targetView, getVerticalSnapPreference());
if (dx != 0 || dy != 0) {
recyclerView.scrollBy(-dx, -dy);
}
}
}
记得补充下Rangefinder
对SNAP_TO_CENTER
的计算,这样才能支持居中对齐
public class Rangefinder {
public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd,
@SnapPreference int snapPreference) {
switch (snapPreference) {
case LinearSmoothScroller.SNAP_TO_START:
return boxStart - viewStart;
case LinearSmoothScroller.SNAP_TO_END:
return boxEnd - viewEnd;
case LinearSmoothScroller.SNAP_TO_ANY:
final int dtStart = boxStart - viewStart;
if (dtStart > 0) {
return dtStart;
}
final int dtEnd = boxEnd - viewEnd;
if (dtEnd < 0) {
return dtEnd;
}
break;
case SnapPreference.SNAP_TO_CENTER:
return (boxStart + (boxEnd - boxStart) / 2) - (viewStart + (viewEnd - viewStart) / 2);
}
return 0;
}
}
最后就是IndexScroller
的实现类,也是简单调用ImmediateScroller
的方法
public class ImmediateIndexScroller implements IndexScroller {
private final RecyclerView mRecyclerView;
public ImmediateIndexScroller(@NonNull RecyclerView recyclerView) {
mRecyclerView = recyclerView;
}
@Override
public void scrollToPosition(int position, @SnapPreference int snapPreference) {
RecyclerView recyclerView = mRecyclerView;
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager == null || position < 0) {
return;
}
ImmediateScroller scroller = createScroller(recyclerView, snapPreference);
scroller.start(position);
}
@NonNull
protected ImmediateScroller createScroller(@NonNull RecyclerView recyclerView, @SnapPreference int snapPreference) {
return new ImmediateScroller(recyclerView, snapPreference, snapPreference);
}
}
即时滚动的部分也实现完了。使用方式和SmoothIndexScroller
一样
IndexScroller indexScroller = new ImmediateIndexScroller(mRecyclerView);
indexScroller.scrollToPosition(position, preference);